winemac: Implement support for owned windows.
[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     + (void) flipRect:(NSRect*)rect;
69
70 @end
71
72
73 @implementation WineContentView
74
75     - (BOOL) isFlipped
76     {
77         return YES;
78     }
79
80 @end
81
82
83 @implementation WineWindow
84
85     @synthesize disabled, noActivate, floating, latentParentWindow;
86
87     + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)wf
88                                  windowFrame:(NSRect)window_frame
89     {
90         WineWindow* window;
91         WineContentView* contentView;
92
93         [self flipRect:&window_frame];
94
95         window = [[[self alloc] initWithContentRect:window_frame
96                                           styleMask:style_mask_for_features(wf)
97                                             backing:NSBackingStoreBuffered
98                                               defer:YES] autorelease];
99
100         if (!window) return nil;
101         window->normalStyleMask = [window styleMask];
102
103         /* Standardize windows to eliminate differences between titled and
104            borderless windows and between NSWindow and NSPanel. */
105         [window setHidesOnDeactivate:NO];
106         [window setReleasedWhenClosed:NO];
107
108         [window disableCursorRects];
109         [window setShowsResizeIndicator:NO];
110         [window setHasShadow:wf->shadow];
111         [window setDelegate:window];
112
113         contentView = [[[WineContentView alloc] initWithFrame:NSZeroRect] autorelease];
114         if (!contentView)
115             return nil;
116         [contentView setAutoresizesSubviews:NO];
117
118         [window setContentView:contentView];
119
120         return window;
121     }
122
123     - (void) dealloc
124     {
125         [latentParentWindow release];
126         [super dealloc];
127     }
128
129     + (void) flipRect:(NSRect*)rect
130     {
131         rect->origin.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - NSMaxY(*rect);
132     }
133
134     - (void) adjustFeaturesForState
135     {
136         NSUInteger style = normalStyleMask;
137
138         if (self.disabled)
139             style &= ~NSResizableWindowMask;
140         if (style != [self styleMask])
141             [self setStyleMask:style];
142
143         if (style & NSClosableWindowMask)
144             [[self standardWindowButton:NSWindowCloseButton] setEnabled:!self.disabled];
145         if (style & NSMiniaturizableWindowMask)
146             [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:!self.disabled];
147     }
148
149     - (void) setWindowFeatures:(const struct macdrv_window_features*)wf
150     {
151         normalStyleMask = style_mask_for_features(wf);
152         [self adjustFeaturesForState];
153         [self setHasShadow:wf->shadow];
154     }
155
156     - (void) setMacDrvState:(const struct macdrv_window_state*)state
157     {
158         NSInteger level;
159
160         self.disabled = state->disabled;
161         self.noActivate = state->no_activate;
162
163         self.floating = state->floating;
164         level = state->floating ? NSFloatingWindowLevel : NSNormalWindowLevel;
165         if (level != [self level])
166             [self setLevel:level];
167     }
168
169     /* Returns whether or not the window was ordered in, which depends on if
170        its frame intersects any screen. */
171     - (BOOL) orderBelow:(WineWindow*)prev orAbove:(WineWindow*)next
172     {
173         BOOL on_screen = frame_intersects_screens([self frame], [NSScreen screens]);
174         if (on_screen)
175         {
176             [NSApp transformProcessToForeground];
177
178             if (prev)
179                 [self orderWindow:NSWindowBelow relativeTo:[prev windowNumber]];
180             else
181                 [self orderWindow:NSWindowAbove relativeTo:[next windowNumber]];
182             if (latentParentWindow)
183             {
184                 [latentParentWindow addChildWindow:self ordered:NSWindowAbove];
185                 self.latentParentWindow = nil;
186             }
187         }
188
189         return on_screen;
190     }
191
192     - (void) doOrderOut
193     {
194         self.latentParentWindow = [self parentWindow];
195         [latentParentWindow removeChildWindow:self];
196         [self orderOut:nil];
197     }
198
199     - (BOOL) setFrameIfOnScreen:(NSRect)contentRect
200     {
201         NSArray* screens = [NSScreen screens];
202         BOOL on_screen = [self isVisible];
203         NSRect frame, oldFrame;
204
205         if (![screens count]) return on_screen;
206
207         /* Origin is (left, top) in a top-down space.  Need to convert it to
208            (left, bottom) in a bottom-up space. */
209         [[self class] flipRect:&contentRect];
210
211         if (on_screen)
212         {
213             on_screen = frame_intersects_screens(contentRect, screens);
214             if (!on_screen)
215                 [self doOrderOut];
216         }
217
218         oldFrame = [self frame];
219         frame = [self frameRectForContentRect:contentRect];
220         if (!NSEqualRects(frame, oldFrame))
221         {
222             if (NSEqualSizes(frame.size, oldFrame.size))
223                 [self setFrameOrigin:frame.origin];
224             else
225                 [self setFrame:frame display:YES];
226         }
227
228         return on_screen;
229     }
230
231     - (void) setMacDrvParentWindow:(WineWindow*)parent
232     {
233         if ([self parentWindow] != parent)
234         {
235             [[self parentWindow] removeChildWindow:self];
236             self.latentParentWindow = nil;
237             if ([self isVisible] && parent)
238                 [parent addChildWindow:self ordered:NSWindowAbove];
239             else
240                 self.latentParentWindow = parent;
241         }
242     }
243
244     - (void) setDisabled:(BOOL)newValue
245     {
246         if (disabled != newValue)
247         {
248             disabled = newValue;
249             [self adjustFeaturesForState];
250         }
251     }
252
253
254     /*
255      * ---------- NSWindow method overrides ----------
256      */
257     - (BOOL) canBecomeKeyWindow
258     {
259         if (self.disabled || self.noActivate) return NO;
260         return YES;
261     }
262
263     - (BOOL) canBecomeMainWindow
264     {
265         return [self canBecomeKeyWindow];
266     }
267
268
269     /*
270      * ---------- NSWindowDelegate methods ----------
271      */
272     - (BOOL)windowShouldClose:(id)sender
273     {
274         return NO;
275     }
276
277 @end
278
279
280 /***********************************************************************
281  *              macdrv_create_cocoa_window
282  *
283  * Create a Cocoa window with the given content frame and features (e.g.
284  * title bar, close box, etc.).
285  */
286 macdrv_window macdrv_create_cocoa_window(const struct macdrv_window_features* wf,
287         CGRect frame)
288 {
289     __block WineWindow* window;
290
291     OnMainThread(^{
292         window = [[WineWindow createWindowWithFeatures:wf
293                                            windowFrame:NSRectFromCGRect(frame)] retain];
294     });
295
296     return (macdrv_window)window;
297 }
298
299 /***********************************************************************
300  *              macdrv_destroy_cocoa_window
301  *
302  * Destroy a Cocoa window.
303  */
304 void macdrv_destroy_cocoa_window(macdrv_window w)
305 {
306     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
307     WineWindow* window = (WineWindow*)w;
308
309     [window close];
310     [window release];
311
312     [pool release];
313 }
314
315 /***********************************************************************
316  *              macdrv_set_cocoa_window_features
317  *
318  * Update a Cocoa window's features.
319  */
320 void macdrv_set_cocoa_window_features(macdrv_window w,
321         const struct macdrv_window_features* wf)
322 {
323     WineWindow* window = (WineWindow*)w;
324
325     OnMainThread(^{
326         [window setWindowFeatures:wf];
327     });
328 }
329
330 /***********************************************************************
331  *              macdrv_set_cocoa_window_state
332  *
333  * Update a Cocoa window's state.
334  */
335 void macdrv_set_cocoa_window_state(macdrv_window w,
336         const struct macdrv_window_state* state)
337 {
338     WineWindow* window = (WineWindow*)w;
339
340     OnMainThread(^{
341         [window setMacDrvState:state];
342     });
343 }
344
345 /***********************************************************************
346  *              macdrv_set_cocoa_window_title
347  *
348  * Set a Cocoa window's title.
349  */
350 void macdrv_set_cocoa_window_title(macdrv_window w, const unsigned short* title,
351         size_t length)
352 {
353     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
354     WineWindow* window = (WineWindow*)w;
355     NSString* titleString;
356
357     if (title)
358         titleString = [NSString stringWithCharacters:title length:length];
359     else
360         titleString = @"";
361     OnMainThreadAsync(^{
362         [window setTitle:titleString];
363     });
364
365     [pool release];
366 }
367
368 /***********************************************************************
369  *              macdrv_order_cocoa_window
370  *
371  * Reorder a Cocoa window relative to other windows.  If prev is
372  * non-NULL, it is ordered below that window.  Else, if next is non-NULL,
373  * it is ordered above that window.  Otherwise, it is ordered to the
374  * front.
375  *
376  * Returns true if the window has actually been ordered onto the screen
377  * (i.e. if its frame intersects with a screen).  Otherwise, false.
378  */
379 int macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
380         macdrv_window next)
381 {
382     WineWindow* window = (WineWindow*)w;
383     __block BOOL on_screen;
384
385     OnMainThread(^{
386         on_screen = [window orderBelow:(WineWindow*)prev
387                                orAbove:(WineWindow*)next];
388     });
389
390     return on_screen;
391 }
392
393 /***********************************************************************
394  *              macdrv_hide_cocoa_window
395  *
396  * Hides a Cocoa window.
397  */
398 void macdrv_hide_cocoa_window(macdrv_window w)
399 {
400     WineWindow* window = (WineWindow*)w;
401
402     OnMainThread(^{
403         [window doOrderOut];
404     });
405 }
406
407 /***********************************************************************
408  *              macdrv_set_cocoa_window_frame
409  *
410  * Move a Cocoa window.  If the window has been moved out of the bounds
411  * of the desktop, it is ordered out.  (This routine won't ever order a
412  * window in, though.)
413  *
414  * Returns true if the window is on screen; false otherwise.
415  */
416 int macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame)
417 {
418     WineWindow* window = (WineWindow*)w;
419     __block BOOL on_screen;
420
421     OnMainThread(^{
422         on_screen = [window setFrameIfOnScreen:NSRectFromCGRect(*new_frame)];
423     });
424
425     return on_screen;
426 }
427
428 /***********************************************************************
429  *              macdrv_set_cocoa_parent_window
430  *
431  * Sets the parent window for a Cocoa window.  If parent is NULL, clears
432  * the parent window.
433  */
434 void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
435 {
436     WineWindow* window = (WineWindow*)w;
437
438     OnMainThread(^{
439         [window setMacDrvParentWindow:(WineWindow*)parent];
440     });
441 }