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