2 * MACDRV Cocoa window code
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #import "cocoa_window.h"
23 #include "macdrv_cocoa.h"
25 #import "cocoa_event.h"
28 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
30 NSUInteger style_mask;
34 style_mask = NSTitledWindowMask;
35 if (wf->close_button) style_mask |= NSClosableWindowMask;
36 if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
37 if (wf->resizable) style_mask |= NSResizableWindowMask;
38 if (wf->utility) style_mask |= NSUtilityWindowMask;
40 else style_mask = NSBorderlessWindowMask;
46 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
49 for (screen in screens)
51 if (NSIntersectsRect(frame, [screen frame]))
58 @interface WineContentView : NSView
62 @interface WineWindow ()
64 @property (nonatomic) BOOL disabled;
65 @property (nonatomic) BOOL noActivate;
66 @property (nonatomic) BOOL floating;
67 @property (retain, nonatomic) NSWindow* latentParentWindow;
69 @property (nonatomic) void* hwnd;
70 @property (retain, nonatomic) WineEventQueue* queue;
72 @property (nonatomic) void* surface;
73 @property (nonatomic) pthread_mutex_t* surface_mutex;
75 @property (copy, nonatomic) NSBezierPath* shape;
76 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
77 @property (readonly, nonatomic) BOOL needsTransparency;
79 @property (nonatomic) BOOL colorKeyed;
80 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
81 @property (nonatomic) BOOL usePerPixelAlpha;
83 + (void) flipRect:(NSRect*)rect;
88 @implementation WineContentView
95 - (void) drawRect:(NSRect)rect
97 WineWindow* window = (WineWindow*)[self window];
99 if (window.surface && window.surface_mutex &&
100 !pthread_mutex_lock(window.surface_mutex))
105 if (!get_surface_region_rects(window.surface, &rects, &count) || count)
110 imageRect = NSRectToCGRect(rect);
111 image = create_surface_image(window.surface, &imageRect, FALSE);
115 CGContextRef context;
119 NSBezierPath* surfaceClip = [NSBezierPath bezierPath];
121 for (i = 0; i < count; i++)
122 [surfaceClip appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
123 [surfaceClip addClip];
126 [window.shape addClip];
128 if (window.colorKeyed)
130 CGImageRef maskedImage;
131 CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
132 window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
133 window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
134 maskedImage = CGImageCreateWithMaskingColors(image, components);
137 CGImageRelease(image);
142 context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
143 CGContextSetBlendMode(context, kCGBlendModeCopy);
144 CGContextDrawImage(context, imageRect, image);
146 CGImageRelease(image);
148 if (window.shapeChangedSinceLastDraw || window.colorKeyed ||
149 window.usePerPixelAlpha)
151 window.shapeChangedSinceLastDraw = FALSE;
152 [window invalidateShadow];
157 pthread_mutex_unlock(window.surface_mutex);
164 @implementation WineWindow
166 @synthesize disabled, noActivate, floating, latentParentWindow, hwnd, queue;
167 @synthesize surface, surface_mutex;
168 @synthesize shape, shapeChangedSinceLastDraw;
169 @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
170 @synthesize usePerPixelAlpha;
172 + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
173 windowFrame:(NSRect)window_frame
175 queue:(WineEventQueue*)queue
178 WineContentView* contentView;
180 [self flipRect:&window_frame];
182 window = [[[self alloc] initWithContentRect:window_frame
183 styleMask:style_mask_for_features(wf)
184 backing:NSBackingStoreBuffered
185 defer:YES] autorelease];
187 if (!window) return nil;
188 window->normalStyleMask = [window styleMask];
190 /* Standardize windows to eliminate differences between titled and
191 borderless windows and between NSWindow and NSPanel. */
192 [window setHidesOnDeactivate:NO];
193 [window setReleasedWhenClosed:NO];
195 [window disableCursorRects];
196 [window setShowsResizeIndicator:NO];
197 [window setHasShadow:wf->shadow];
198 [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
199 [window setDelegate:window];
201 window.queue = queue;
203 contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
206 [contentView setAutoresizesSubviews:NO];
208 [window setContentView:contentView];
210 /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
211 event. The back end will ignore it if nothing actually changed. */
212 [window windowDidResize:nil];
220 [latentParentWindow release];
225 + (void) flipRect:(NSRect*)rect
227 rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
230 - (void) adjustFeaturesForState
232 NSUInteger style = normalStyleMask;
235 style &= ~NSResizableWindowMask;
236 if (style != [self styleMask])
237 [self setStyleMask:style];
239 if (style & NSClosableWindowMask)
240 [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
241 if (style & NSMiniaturizableWindowMask)
242 [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
245 - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
247 normalStyleMask = style_mask_for_features(wf);
248 [self adjustFeaturesForState];
249 [self setHasShadow:wf->shadow];
252 - (void) setMacDrvState:(const struct macdrv_window_state*)state
255 NSWindowCollectionBehavior behavior;
257 self.disabled = state->disabled;
258 self.noActivate = state->no_activate;
260 self.floating = state->floating;
261 level = state->floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
262 if (level != [self level])
263 [self setLevel:level];
265 behavior = NSWindowCollectionBehaviorDefault;
266 if (state->excluded_by_expose)
267 behavior |= NSWindowCollectionBehaviorTransient;
269 behavior |= NSWindowCollectionBehaviorManaged;
270 if (state->excluded_by_cycle)
272 behavior |= NSWindowCollectionBehaviorIgnoresCycle;
273 if ([self isVisible])
274 [NSApp removeWindowsItem:self];
278 behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
279 if ([self isVisible])
280 [NSApp addWindowsItem:self title:[self title] filename:NO];
282 [self setCollectionBehavior:behavior];
285 /* Returns whether or not the window was ordered in, which depends on if
286 its frame intersects any screen. */
287 - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
289 BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
292 [NSApp transformProcessToForeground];
295 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
297 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
298 if (latentParentWindow)
300 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
301 self.latentParentWindow = nil;
304 /* Cocoa may adjust the frame when the window is ordered onto the screen.
305 Generate a frame-changed event just in case. The back end will ignore
306 it if nothing actually changed. */
307 [self windowDidResize:nil];
309 if (![self isExcludedFromWindowsMenu])
310 [NSApp addWindowsItem:self title:[self title] filename:NO];
318 self.latentParentWindow = [self parentWindow];
319 [latentParentWindow removeChildWindow:self];
321 [NSApp removeWindowsItem:self];
324 - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
326 NSArray* screens = [NSScreen screens];
327 BOOL on_screen = [self isVisible];
328 NSRect frame, oldFrame;
330 if (![screens count]) return on_screen;
332 /* Origin is (left, top) in a top-down space. Need to convert it to
333 (left, bottom) in a bottom-up space. */
334 [[self class] flipRect:&contentRect];
338 on_screen = frame_intersects_screens(contentRect, screens);
343 oldFrame = [self frame];
344 frame = [self frameRectForContentRect:contentRect];
345 if (!NSEqualRects(frame, oldFrame))
347 if (NSEqualSizes(frame.size, oldFrame.size))
348 [self setFrameOrigin:frame.origin];
350 [self setFrame:frame display:YES];
353 /* In case Cocoa adjusted the frame we tried to set, generate a frame-changed
354 event. The back end will ignore it if nothing actually changed. */
355 [self windowDidResize:nil];
360 - (void) setMacDrvParentWindow:(WineWindow*)parent
362 if ([self parentWindow] != parent)
364 [[self parentWindow] removeChildWindow:self];
365 self.latentParentWindow = nil;
366 if ([self isVisible] && parent)
367 [parent addChildWindow:self ordered:NSWindowAbove];
369 self.latentParentWindow = parent;
373 - (void) setDisabled:(BOOL)newValue
375 if (disabled != newValue)
378 [self adjustFeaturesForState];
382 - (BOOL) needsTransparency
384 return self.shape || self.colorKeyed || self.usePerPixelAlpha;
387 - (void) checkTransparency
389 if (![self isOpaque] && !self.needsTransparency)
391 [self setBackgroundColor:[NSColor windowBackgroundColor]];
392 [self setOpaque:YES];
394 else if ([self isOpaque] && self.needsTransparency)
396 [self setBackgroundColor:[NSColor clearColor]];
401 - (void) setShape:(NSBezierPath*)newShape
403 if (shape == newShape) return;
404 if (shape && newShape && [shape isEqual:newShape]) return;
408 [[self contentView] setNeedsDisplayInRect:[shape bounds]];
412 [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
414 shape = [newShape copy];
415 self.shapeChangedSinceLastDraw = TRUE;
417 [self checkTransparency];
422 * ---------- NSWindow method overrides ----------
424 - (BOOL) canBecomeKeyWindow
426 if (self.disabled || self.noActivate) return NO;
430 - (BOOL) canBecomeMainWindow
432 return [self canBecomeKeyWindow];
435 - (BOOL) isExcludedFromWindowsMenu
437 return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
440 - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
442 if ([menuItem action] == @selector(makeKeyAndOrderFront:))
443 return [self isKeyWindow] || (!self.disabled && !self.noActivate);
444 return [super validateMenuItem:menuItem];
449 * ---------- NSWindowDelegate methods ----------
451 - (void)windowDidMove:(NSNotification *)notification
453 [self windowDidResize:notification];
456 - (void)windowDidResize:(NSNotification *)notification
459 NSRect frame = [self contentRectForFrameRect:[self frame]];
461 [[self class] flipRect:&frame];
463 /* Coalesce events by discarding any previous ones still in the queue. */
464 [queue discardEventsMatchingMask:event_mask_for_type(WINDOW_FRAME_CHANGED)
467 event.type = WINDOW_FRAME_CHANGED;
468 event.window = (macdrv_window)[self retain];
469 event.window_frame_changed.frame = NSRectToCGRect(frame);
470 [queue postEvent:&event];
473 - (BOOL)windowShouldClose:(id)sender
476 event.type = WINDOW_CLOSE_REQUESTED;
477 event.window = (macdrv_window)[self retain];
478 [queue postEvent:&event];
485 /***********************************************************************
486 * macdrv_create_cocoa_window
488 * Create a Cocoa window with the given content frame and features (e.g.
489 * title bar, close box, etc.).
491 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
492 CGRect frame, void* hwnd, macdrv_event_queue queue)
494 __block WineWindow* window;
497 window = [[WineWindow createWindowWithFeatures:wf
498 windowFrame:NSRectFromCGRect(frame)
500 queue:(WineEventQueue*)queue] retain];
503 return (macdrv_window)window;
506 /***********************************************************************
507 * macdrv_destroy_cocoa_window
509 * Destroy a Cocoa window.
511 void macdrv_destroy_cocoa_window(macdrv_window w)
513 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
514 WineWindow* window = (WineWindow*)w;
516 [window.queue discardEventsMatchingMask:-1 forWindow:window];
523 /***********************************************************************
524 * macdrv_get_window_hwnd
526 * Get the hwnd that was set for the window at creation.
528 void* macdrv_get_window_hwnd(macdrv_window w)
530 WineWindow* window = (WineWindow*)w;
534 /***********************************************************************
535 * macdrv_set_cocoa_window_features
537 * Update a Cocoa window's features.
539 void macdrv_set_cocoa_window_features(macdrv_window w,
540 const struct macdrv_window_features* wf)
542 WineWindow* window = (WineWindow*)w;
545 [window setWindowFeatures:wf];
549 /***********************************************************************
550 * macdrv_set_cocoa_window_state
552 * Update a Cocoa window's state.
554 void macdrv_set_cocoa_window_state(macdrv_window w,
555 const struct macdrv_window_state* state)
557 WineWindow* window = (WineWindow*)w;
560 [window setMacDrvState:state];
564 /***********************************************************************
565 * macdrv_set_cocoa_window_title
567 * Set a Cocoa window's title.
569 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
572 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
573 WineWindow* window = (WineWindow*)w;
574 NSString* titleString;
577 titleString = [NSString stringWithCharacters:title length:length];
581 [window setTitle:titleString];
582 if ([window isVisible] && ![window isExcludedFromWindowsMenu])
583 [NSApp changeWindowsItem:window title:titleString filename:NO];
589 /***********************************************************************
590 * macdrv_order_cocoa_window
592 * Reorder a Cocoa window relative to other windows. If prev is
593 * non-NULL, it is ordered below that window. Else, if next is non-NULL,
594 * it is ordered above that window. Otherwise, it is ordered to the
597 * Returns true if the window has actually been ordered onto the screen
598 * (i.e. if its frame intersects with a screen). Otherwise, false.
600 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
603 WineWindow* window = (WineWindow*)w;
604 __block BOOL on_screen;
607 on_screen = [window orderBelow:(WineWindow*)prev
608 orAbove:(WineWindow*)next];
614 /***********************************************************************
615 * macdrv_hide_cocoa_window
617 * Hides a Cocoa window.
619 void macdrv_hide_cocoa_window(macdrv_window w)
621 WineWindow* window = (WineWindow*)w;
628 /***********************************************************************
629 * macdrv_set_cocoa_window_frame
631 * Move a Cocoa window. If the window has been moved out of the bounds
632 * of the desktop, it is ordered out. (This routine won't ever order a
633 * window in, though.)
635 * Returns true if the window is on screen; false otherwise.
637 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
639 WineWindow* window = (WineWindow*)w;
640 __block BOOL on_screen;
643 on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
649 /***********************************************************************
650 * macdrv_get_cocoa_window_frame
652 * Gets the frame of a Cocoa window.
654 void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame)
656 WineWindow* window = (WineWindow*)w;
661 frame = [window contentRectForFrameRect:[window frame]];
662 [[window class] flipRect:&frame];
663 *out_frame = NSRectToCGRect(frame);
667 /***********************************************************************
668 * macdrv_set_cocoa_parent_window
670 * Sets the parent window for a Cocoa window. If parent is NULL, clears
673 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
675 WineWindow* window = (WineWindow*)w;
678 [window setMacDrvParentWindow:(WineWindow*)parent];
682 /***********************************************************************
683 * macdrv_set_window_surface
685 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
687 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
688 WineWindow* window = (WineWindow*)w;
691 window.surface = surface;
692 window.surface_mutex = mutex;
698 /***********************************************************************
699 * macdrv_window_needs_display
701 * Mark a window as needing display in a specified rect (in non-client
704 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
706 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
707 WineWindow* window = (WineWindow*)w;
710 [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
716 /***********************************************************************
717 * macdrv_set_window_shape
719 * Sets the shape of a Cocoa window from an array of rectangles. If
720 * rects is NULL, resets the window's shape to its frame.
722 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
724 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
725 WineWindow* window = (WineWindow*)w;
728 if (!rects || !count)
735 path = [NSBezierPath bezierPath];
736 for (i = 0; i < count; i++)
737 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
745 /***********************************************************************
746 * macdrv_set_window_alpha
748 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
750 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
751 WineWindow* window = (WineWindow*)w;
753 [window setAlphaValue:alpha];
758 /***********************************************************************
759 * macdrv_set_window_color_key
761 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
764 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
765 WineWindow* window = (WineWindow*)w;
768 window.colorKeyed = TRUE;
769 window.colorKeyRed = keyRed;
770 window.colorKeyGreen = keyGreen;
771 window.colorKeyBlue = keyBlue;
772 [window checkTransparency];
778 /***********************************************************************
779 * macdrv_clear_window_color_key
781 void macdrv_clear_window_color_key(macdrv_window w)
783 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
784 WineWindow* window = (WineWindow*)w;
787 window.colorKeyed = FALSE;
788 [window checkTransparency];
794 /***********************************************************************
795 * macdrv_window_use_per_pixel_alpha
797 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
799 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
800 WineWindow* window = (WineWindow*)w;
803 window.usePerPixelAlpha = use_per_pixel_alpha;
804 [window checkTransparency];