winemac: Implement layered windows: SetLayeredWindowAttributes() and UpdateLayeredWin...
[wine] / dlls / winemac.drv / cocoa_window.m
1 /*
2  * MACDRV Cocoa window code
3  *
4  * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #import "cocoa_window.h"
22
23 #include "macdrv_cocoa.h"
24 #import "cocoa_app.h"
25
26
27 static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf)
28 {
29     NSUInteger style_mask;
30
31     if (wf->title_bar)
32     {
33         style_mask = NSTitledWindowMask;
34         if (wf->close_button) style_mask |= NSClosableWindowMask;
35         if (wf->minimize_button) style_mask |= NSMiniaturizableWindowMask;
36         if (wf->resizable) style_mask |= NSResizableWindowMask;
37         if (wf->utility) style_mask |= NSUtilityWindowMask;
38     }
39     else style_mask = NSBorderlessWindowMask;
40
41     return style_mask;
42 }
43
44
45 static BOOL frame_intersects_screens(NSRect frame, NSArray* screens)
46 {
47     NSScreen* screen;
48     for (screen in screens)
49     {
50         if (NSIntersectsRect(frame, [screen frame]))
51             return TRUE;
52     }
53     return FALSE;
54 }
55
56
57 @interface WineContentView : NSView
58 @end
59
60
61 @interface WineWindow ()
62
63 @property (nonatomic) BOOL disabled;
64 @property (nonatomic) BOOL noActivate;
65 @property (nonatomic) BOOL floating;
66 @property (retain, nonatomic) NSWindow* latentParentWindow;
67
68 @property (nonatomic) void* surface;
69 @property (nonatomic) pthread_mutex_t* surface_mutex;
70
71 @property (copy, nonatomic) NSBezierPath* shape;
72 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
73 @property (readonly, nonatomic) BOOL needsTransparency;
74
75 @property (nonatomic) BOOL colorKeyed;
76 @property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
77 @property (nonatomic) BOOL usePerPixelAlpha;
78
79     + (void) flipRect:(NSRect*)rect;
80
81 @end
82
83
84 @implementation WineContentView
85
86     - (BOOL) isFlipped
87     {
88         return YES;
89     }
90
91     - (void) drawRect:(NSRect)rect
92     {
93         WineWindow* window = (WineWindow*)[self window];
94
95         if (window.surface && window.surface_mutex &&
96             !pthread_mutex_lock(window.surface_mutex))
97         {
98             const CGRect* rects;
99             int count;
100
101             if (!get_surface_region_rects(window.surface, &rects, &count) || count)
102             {
103                 CGRect imageRect;
104                 CGImageRef image;
105
106                 imageRect = NSRectToCGRect(rect);
107                 image = create_surface_image(window.surface, &imageRect, FALSE);
108
109                 if (image)
110                 {
111                     CGContextRef context;
112
113                     if (rects && count)
114                     {
115                         NSBezierPath* surfaceClip = [NSBezierPath bezierPath];
116                         int i;
117                         for (i = 0; i < count; i++)
118                             [surfaceClip appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
119                         [surfaceClip addClip];
120                     }
121
122                     [window.shape addClip];
123
124                     if (window.colorKeyed)
125                     {
126                         CGImageRef maskedImage;
127                         CGFloat components[] = { window.colorKeyRed - 0.5, window.colorKeyRed + 0.5,
128                                                  window.colorKeyGreen - 0.5, window.colorKeyGreen + 0.5,
129                                                  window.colorKeyBlue - 0.5, window.colorKeyBlue + 0.5 };
130                         maskedImage = CGImageCreateWithMaskingColors(image, components);
131                         if (maskedImage)
132                         {
133                             CGImageRelease(image);
134                             image = maskedImage;
135                         }
136                     }
137
138                     context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
139                     CGContextSetBlendMode(context, kCGBlendModeCopy);
140                     CGContextDrawImage(context, imageRect, image);
141
142                     CGImageRelease(image);
143
144                     if (window.shapeChangedSinceLastDraw || window.colorKeyed ||
145                         window.usePerPixelAlpha)
146                     {
147                         window.shapeChangedSinceLastDraw = FALSE;
148                         [window invalidateShadow];
149                     }
150                 }
151             }
152
153             pthread_mutex_unlock(window.surface_mutex);
154         }
155     }
156
157 @end
158
159
160 @implementation WineWindow
161
162     @synthesize disabled, noActivate, floating, latentParentWindow;
163     @synthesize surface, surface_mutex;
164     @synthesize shape, shapeChangedSinceLastDraw;
165     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
166     @synthesize usePerPixelAlpha;
167
168     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
169                                  windowFrame:(NSRect)window_frame
170     {
171         WineWindow* window;
172         WineContentView* contentView;
173
174         [self flipRect:&window_frame];
175
176         window = [[[self alloc] initWithContentRect:window_frame
177                                           styleMask:style_mask_for_features(wf)
178                                             backing:NSBackingStoreBuffered
179                                               defer:YES] autorelease];
180
181         if (!window) return nil;
182         window->normalStyleMask = [window styleMask];
183
184         /* Standardize windows to eliminate differences between titled and
185            borderless windows and between NSWindow and NSPanel. */
186         [window setHidesOnDeactivate:NO];
187         [window setReleasedWhenClosed:NO];
188
189         [window disableCursorRects];
190         [window setShowsResizeIndicator:NO];
191         [window setHasShadow:wf->shadow];
192         [window setColorSpace:[NSColorSpace genericRGBColorSpace]];
193         [window setDelegate:window];
194
195         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
196         if (!contentView)
197             return nil;
198         [contentView setAutoresizesSubviews:NO];
199
200         [window setContentView:contentView];
201
202         return window;
203     }
204
205     - (void) dealloc
206     {
207         [latentParentWindow release];
208         [shape release];
209         [super dealloc];
210     }
211
212     + (void) flipRect:(NSRect*)rect
213     {
214         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
215     }
216
217     - (void) adjustFeaturesForState
218     {
219         NSUInteger style = normalStyleMask;
220
221         if (self.disabled)
222             style &= ~NSResizableWindowMask;
223         if (style != [self styleMask])
224             [self setStyleMask:style];
225
226         if (style & NSClosableWindowMask)
227             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
228         if (style & NSMiniaturizableWindowMask)
229             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
230     }
231
232     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
233     {
234         normalStyleMask = style_mask_for_features(wf);
235         [self adjustFeaturesForState];
236         [self setHasShadow:wf->shadow];
237     }
238
239     - (void) setMacDrvState:(const struct macdrv_window_state*)state
240     {
241         NSInteger level;
242         NSWindowCollectionBehavior behavior;
243
244         self.disabled = state->disabled;
245         self.noActivate = state->no_activate;
246
247         self.floating = state->floating;
248         level = state->floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
249         if (level != [self level])
250             [self setLevel:level];
251
252         behavior = NSWindowCollectionBehaviorDefault;
253         if (state->excluded_by_expose)
254             behavior |= NSWindowCollectionBehaviorTransient;
255         else
256             behavior |= NSWindowCollectionBehaviorManaged;
257         if (state->excluded_by_cycle)
258         {
259             behavior |= NSWindowCollectionBehaviorIgnoresCycle;
260             if ([self isVisible])
261                 [NSApp removeWindowsItem:self];
262         }
263         else
264         {
265             behavior |= NSWindowCollectionBehaviorParticipatesInCycle;
266             if ([self isVisible])
267                 [NSApp addWindowsItem:self title:[self title] filename:NO];
268         }
269         [self setCollectionBehavior:behavior];
270     }
271
272     /* Returns whether or not the window was ordered in, which depends on if
273        its frame intersects any screen. */
274     - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
275     {
276         BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
277         if (on_screen)
278         {
279             [NSApp transformProcessToForeground];
280
281             if (prev)
282                 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
283             else
284                 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
285             if (latentParentWindow)
286             {
287                 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
288                 self.latentParentWindow = nil;
289             }
290
291             if (![self isExcludedFromWindowsMenu])
292                 [NSApp addWindowsItem:self title:[self title] filename:NO];
293         }
294
295         return on_screen;
296     }
297
298     - (void) doOrderOut
299     {
300         self.latentParentWindow = [self parentWindow];
301         [latentParentWindow removeChildWindow:self];
302         [self orderOut:nil];
303         [NSApp removeWindowsItem:self];
304     }
305
306     - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
307     {
308         NSArray* screens = [NSScreen screens];
309         BOOL on_screen = [self isVisible];
310         NSRect frame, oldFrame;
311
312         if (![screens count]) return on_screen;
313
314         /* Origin is (left, top) in a top-down space.  Need to convert it to
315            (left, bottom) in a bottom-up space. */
316         [[self class] flipRect:&contentRect];
317
318         if (on_screen)
319         {
320             on_screen = frame_intersects_screens(contentRect, screens);
321             if (!on_screen)
322                 [self doOrderOut];
323         }
324
325         oldFrame = [self frame];
326         frame = [self frameRectForContentRect:contentRect];
327         if (!NSEqualRects(frame, oldFrame))
328         {
329             if (NSEqualSizes(frame.size, oldFrame.size))
330                 [self setFrameOrigin:frame.origin];
331             else
332                 [self setFrame:frame display:YES];
333         }
334
335         return on_screen;
336     }
337
338     - (void) setMacDrvParentWindow:(WineWindow*)parent
339     {
340         if ([self parentWindow] != parent)
341         {
342             [[self parentWindow] removeChildWindow:self];
343             self.latentParentWindow = nil;
344             if ([self isVisible] && parent)
345                 [parent addChildWindow:self ordered:NSWindowAbove];
346             else
347                 self.latentParentWindow = parent;
348         }
349     }
350
351     - (void) setDisabled:(BOOL)newValue
352     {
353         if (disabled != newValue)
354         {
355             disabled = newValue;
356             [self adjustFeaturesForState];
357         }
358     }
359
360     - (BOOL) needsTransparency
361     {
362         return self.shape || self.colorKeyed || self.usePerPixelAlpha;
363     }
364
365     - (void) checkTransparency
366     {
367         if (![self isOpaque] && !self.needsTransparency)
368         {
369             [self setBackgroundColor:[NSColor windowBackgroundColor]];
370             [self setOpaque:YES];
371         }
372         else if ([self isOpaque] && self.needsTransparency)
373         {
374             [self setBackgroundColor:[NSColor clearColor]];
375             [self setOpaque:NO];
376         }
377     }
378
379     - (void) setShape:(NSBezierPath*)newShape
380     {
381         if (shape == newShape) return;
382         if (shape && newShape && [shape isEqual:newShape]) return;
383
384         if (shape)
385         {
386             [[self contentView] setNeedsDisplayInRect:[shape bounds]];
387             [shape release];
388         }
389         if (newShape)
390             [[self contentView] setNeedsDisplayInRect:[newShape bounds]];
391
392         shape = [newShape copy];
393         self.shapeChangedSinceLastDraw = TRUE;
394
395         [self checkTransparency];
396     }
397
398
399     /*
400      * ---------- NSWindow method overrides ----------
401      */
402     - (BOOL) canBecomeKeyWindow
403     {
404         if (self.disabled || self.noActivate) return NO;
405         return YES;
406     }
407
408     - (BOOL) canBecomeMainWindow
409     {
410         return [self canBecomeKeyWindow];
411     }
412
413     - (BOOL) isExcludedFromWindowsMenu
414     {
415         return !([self collectionBehavior] & NSWindowCollectionBehaviorParticipatesInCycle);
416     }
417
418     - (BOOL) validateMenuItem:(NSMenuItem *)menuItem
419     {
420         if ([menuItem action] == @selector(makeKeyAndOrderFront:))
421             return [self isKeyWindow] || (!self.disabled && !self.noActivate);
422         return [super validateMenuItem:menuItem];
423     }
424
425
426     /*
427      * ---------- NSWindowDelegate methods ----------
428      */
429     - (BOOL)windowShouldClose:(id)sender
430     {
431         return NO;
432     }
433
434 @end
435
436
437 /***********************************************************************
438  *              macdrv_create_cocoa_window
439  *
440  * Create a Cocoa window with the given content frame and features (e.g.
441  * title bar, close box, etc.).
442  */
443 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
444         CGRect frame)
445 {
446     __block WineWindow* window;
447
448     OnMainThread(^{
449         window = [[WineWindow createWindowWithFeatures:wf
450                                            windowFrame:NSRectFromCGRect(frame)] retain];
451     });
452
453     return (macdrv_window)window;
454 }
455
456 /***********************************************************************
457  *              macdrv_destroy_cocoa_window
458  *
459  * Destroy a Cocoa window.
460  */
461 void macdrv_destroy_cocoa_window(macdrv_window w)
462 {
463     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
464     WineWindow* window = (WineWindow*)w;
465
466     [window close];
467     [window release];
468
469     [pool release];
470 }
471
472 /***********************************************************************
473  *              macdrv_set_cocoa_window_features
474  *
475  * Update a Cocoa window's features.
476  */
477 void macdrv_set_cocoa_window_features(macdrv_window w,
478         const struct macdrv_window_features* wf)
479 {
480     WineWindow* window = (WineWindow*)w;
481
482     OnMainThread(^{
483         [window setWindowFeatures:wf];
484     });
485 }
486
487 /***********************************************************************
488  *              macdrv_set_cocoa_window_state
489  *
490  * Update a Cocoa window's state.
491  */
492 void macdrv_set_cocoa_window_state(macdrv_window w,
493         const struct macdrv_window_state* state)
494 {
495     WineWindow* window = (WineWindow*)w;
496
497     OnMainThread(^{
498         [window setMacDrvState:state];
499     });
500 }
501
502 /***********************************************************************
503  *              macdrv_set_cocoa_window_title
504  *
505  * Set a Cocoa window's title.
506  */
507 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
508         size_t length)
509 {
510     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
511     WineWindow* window = (WineWindow*)w;
512     NSString* titleString;
513
514     if (title)
515         titleString = [NSString stringWithCharacters:title length:length];
516     else
517         titleString = @"";
518     OnMainThreadAsync(^{
519         [window setTitle:titleString];
520         if ([window isVisible] && ![window isExcludedFromWindowsMenu])
521             [NSApp changeWindowsItem:window title:titleString filename:NO];
522     });
523
524     [pool release];
525 }
526
527 /***********************************************************************
528  *              macdrv_order_cocoa_window
529  *
530  * Reorder a Cocoa window relative to other windows.  If prev is
531  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
532  * it is ordered above that window.  Otherwise, it is ordered to the
533  * front.
534  *
535  * Returns true if the window has actually been ordered onto the screen
536  * (i.e. if its frame intersects with a screen).  Otherwise, false.
537  */
538 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
539         macdrv_window next)
540 {
541     WineWindow* window = (WineWindow*)w;
542     __block BOOL on_screen;
543
544     OnMainThread(^{
545         on_screen = [window orderBelow:(WineWindow*)prev
546                                orAbove:(WineWindow*)next];
547     });
548
549     return on_screen;
550 }
551
552 /***********************************************************************
553  *              macdrv_hide_cocoa_window
554  *
555  * Hides a Cocoa window.
556  */
557 void macdrv_hide_cocoa_window(macdrv_window w)
558 {
559     WineWindow* window = (WineWindow*)w;
560
561     OnMainThread(^{
562         [window doOrderOut];
563     });
564 }
565
566 /***********************************************************************
567  *              macdrv_set_cocoa_window_frame
568  *
569  * Move a Cocoa window.  If the window has been moved out of the bounds
570  * of the desktop, it is ordered out.  (This routine won't ever order a
571  * window in, though.)
572  *
573  * Returns true if the window is on screen; false otherwise.
574  */
575 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
576 {
577     WineWindow* window = (WineWindow*)w;
578     __block BOOL on_screen;
579
580     OnMainThread(^{
581         on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
582     });
583
584     return on_screen;
585 }
586
587 /***********************************************************************
588  *              macdrv_set_cocoa_parent_window
589  *
590  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
591  * the parent window.
592  */
593 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
594 {
595     WineWindow* window = (WineWindow*)w;
596
597     OnMainThread(^{
598         [window setMacDrvParentWindow:(WineWindow*)parent];
599     });
600 }
601
602 /***********************************************************************
603  *              macdrv_set_window_surface
604  */
605 void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
606 {
607     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
608     WineWindow* window = (WineWindow*)w;
609
610     OnMainThread(^{
611         window.surface = surface;
612         window.surface_mutex = mutex;
613     });
614
615     [pool release];
616 }
617
618 /***********************************************************************
619  *              macdrv_window_needs_display
620  *
621  * Mark a window as needing display in a specified rect (in non-client
622  * area coordinates).
623  */
624 void macdrv_window_needs_display(macdrv_window w, CGRect rect)
625 {
626     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
627     WineWindow* window = (WineWindow*)w;
628
629     OnMainThreadAsync(^{
630         [[window contentView] setNeedsDisplayInRect:NSRectFromCGRect(rect)];
631     });
632
633     [pool release];
634 }
635
636 /***********************************************************************
637  *              macdrv_set_window_shape
638  *
639  * Sets the shape of a Cocoa window from an array of rectangles.  If
640  * rects is NULL, resets the window's shape to its frame.
641  */
642 void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
643 {
644     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
645     WineWindow* window = (WineWindow*)w;
646
647     OnMainThread(^{
648         if (!rects || !count)
649             window.shape = nil;
650         else
651         {
652             NSBezierPath* path;
653             unsigned int i;
654
655             path = [NSBezierPath bezierPath];
656             for (i = 0; i < count; i++)
657                 [path appendBezierPathWithRect:NSRectFromCGRect(rects[i])];
658             window.shape = path;
659         }
660     });
661
662     [pool release];
663 }
664
665 /***********************************************************************
666  *              macdrv_set_window_alpha
667  */
668 void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
669 {
670     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
671     WineWindow* window = (WineWindow*)w;
672
673     [window setAlphaValue:alpha];
674
675     [pool release];
676 }
677
678 /***********************************************************************
679  *              macdrv_set_window_color_key
680  */
681 void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
682                                  CGFloat keyBlue)
683 {
684     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
685     WineWindow* window = (WineWindow*)w;
686
687     OnMainThread(^{
688         window.colorKeyed       = TRUE;
689         window.colorKeyRed      = keyRed;
690         window.colorKeyGreen    = keyGreen;
691         window.colorKeyBlue     = keyBlue;
692         [window checkTransparency];
693     });
694
695     [pool release];
696 }
697
698 /***********************************************************************
699  *              macdrv_clear_window_color_key
700  */
701 void macdrv_clear_window_color_key(macdrv_window w)
702 {
703     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
704     WineWindow* window = (WineWindow*)w;
705
706     OnMainThread(^{
707         window.colorKeyed = FALSE;
708         [window checkTransparency];
709     });
710
711     [pool release];
712 }
713
714 /***********************************************************************
715  *              macdrv_window_use_per_pixel_alpha
716  */
717 void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha)
718 {
719     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
720     WineWindow* window = (WineWindow*)w;
721
722     OnMainThread(^{
723         window.usePerPixelAlpha = use_per_pixel_alpha;
724         [window checkTransparency];
725     });
726
727     [pool release];
728 }