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