support for custom app controllers, override OSXCreateAppController in wxApp subclass
[wxWidgets.git] / src / osx / cocoa / utils.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/osx/cocoa/utils.mm
3 // Purpose:     various cocoa utility functions
4 // Author:      Stefan Csomor
5 // Modified by:
6 // Created:     1998-01-01
7 // RCS-ID:      $Id$
8 // Copyright:   (c) Stefan Csomor
9 // Licence:     wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #include "wx/utils.h"
15
16 #ifndef WX_PRECOMP
17     #include "wx/intl.h"
18     #include "wx/app.h"
19     #if wxUSE_GUI
20         #include "wx/dialog.h"
21         #include "wx/toplevel.h"
22         #include "wx/font.h"
23     #endif
24 #endif
25
26 #include "wx/apptrait.h"
27
28 #include "wx/osx/private.h"
29
30 #if wxUSE_GUI
31 #if wxOSX_USE_COCOA_OR_CARBON
32     #include <CoreServices/CoreServices.h>
33     #include "wx/osx/dcclient.h"
34     #include "wx/osx/private/timer.h"
35 #endif
36 #endif // wxUSE_GUI
37
38 #if wxOSX_USE_COCOA
39
40 #if wxUSE_GUI
41
42 // Emit a beeeeeep
43 void wxBell()
44 {
45     NSBeep();
46 }
47
48 @implementation wxNSAppController
49
50 - (void)applicationWillFinishLaunching:(NSNotification *)application {  
51     wxUnusedVar(application);
52     
53     // we must install our handlers later than setting the app delegate, because otherwise our handlers
54     // get overwritten in the meantime
55
56     NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
57     
58     [appleEventManager setEventHandler:self andSelector:@selector(handleGetURLEvent:withReplyEvent:)
59                          forEventClass:kInternetEventClass andEventID:kAEGetURL];
60     
61     [appleEventManager setEventHandler:self andSelector:@selector(handleOpenAppEvent:withReplyEvent:)
62                          forEventClass:kCoreEventClass andEventID:kAEOpenApplication];
63     
64 }
65
66 - (void)application:(NSApplication *)sender openFiles:(NSArray *)fileNames
67 {
68     wxUnusedVar(sender);
69     wxArrayString fileList;
70     size_t i;
71     const size_t count = [fileNames count];
72     for (i = 0; i < count; i++)
73     {
74         fileList.Add( wxCFStringRef::AsStringWithNormalizationFormC([fileNames objectAtIndex:i]) );
75     }
76
77     wxTheApp->MacOpenFiles(fileList);
78 }
79
80 - (BOOL)application:(NSApplication *)sender printFile:(NSString *)filename
81 {
82     wxUnusedVar(sender);
83     wxCFStringRef cf(wxCFRetain(filename));
84     wxTheApp->MacPrintFile(cf.AsString()) ;
85     return YES;
86 }
87
88 - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
89 {
90     wxUnusedVar(flag);
91     wxUnusedVar(sender);
92     wxTheApp->MacReopenApp() ;
93     return NO;
94 }
95
96 - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
97            withReplyEvent:(NSAppleEventDescriptor *)replyEvent
98 {
99     wxUnusedVar(replyEvent);
100     NSString* url = [[event descriptorAtIndex:1] stringValue];
101     wxCFStringRef cf(wxCFRetain(url));
102     wxTheApp->MacOpenURL(cf.AsString()) ;
103 }
104
105 - (void)handleOpenAppEvent:(NSAppleEventDescriptor *)event
106            withReplyEvent:(NSAppleEventDescriptor *)replyEvent
107 {
108     wxUnusedVar(replyEvent);
109     wxTheApp->MacNewFile() ;
110 }
111
112 /*
113     Allowable return values are:
114         NSTerminateNow - it is ok to proceed with termination
115         NSTerminateCancel - the application should not be terminated
116         NSTerminateLater - it may be ok to proceed with termination later.  The application must call -replyToApplicationShouldTerminate: with YES or NO once the answer is known
117             this return value is for delegates who need to provide document modal alerts (sheets) in order to decide whether to quit.
118 */
119 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
120 {
121     wxUnusedVar(sender);
122     wxCloseEvent event;
123     wxTheApp->OnQueryEndSession(event);
124     if ( event.GetVeto() )
125         return NSTerminateCancel;
126     
127     return NSTerminateNow;
128 }
129
130 - (void)applicationWillTerminate:(NSNotification *)application {
131     wxUnusedVar(application);
132     wxCloseEvent event;
133     event.SetCanVeto(false);
134     wxTheApp->OnEndSession(event);
135 }
136
137 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
138 {
139     wxUnusedVar(sender);
140     // let wx do this, not cocoa
141     return NO;
142 }
143
144 - (void)applicationDidBecomeActive:(NSNotification *)notification
145 {
146     wxUnusedVar(notification);
147
148     for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(),
149          end = wxTopLevelWindows.end();
150          i != end;
151          ++i )
152     {
153         wxTopLevelWindow * const win = static_cast<wxTopLevelWindow *>(*i);
154         wxNonOwnedWindowImpl* winimpl = win ? win->GetNonOwnedPeer() : NULL;
155         WXWindow nswindow = win ? win->GetWXWindow() : nil;
156         
157         if ( nswindow && [nswindow hidesOnDeactivate] == NO && winimpl)
158             winimpl->RestoreWindowLevel();
159     }
160     if ( wxTheApp )
161         wxTheApp->SetActive( true , NULL ) ;
162 }
163
164 - (void)applicationWillResignActive:(NSNotification *)notification
165 {
166     wxUnusedVar(notification);
167     for ( wxWindowList::const_iterator i = wxTopLevelWindows.begin(),
168          end = wxTopLevelWindows.end();
169          i != end;
170          ++i )
171     {
172         wxTopLevelWindow * const win = static_cast<wxTopLevelWindow *>(*i);
173         WXWindow nswindow = win ? win->GetWXWindow() : nil;
174         
175         if ( nswindow && [nswindow level] == kCGFloatingWindowLevel && [nswindow hidesOnDeactivate] == NO )
176             [nswindow setLevel:kCGNormalWindowLevel];
177     }
178 }
179
180 - (void)applicationDidResignActive:(NSNotification *)notification
181 {
182     wxUnusedVar(notification);
183     if ( wxTheApp )
184         wxTheApp->SetActive( false , NULL ) ;
185 }
186
187 @end
188
189 /*
190     allows ShowModal to work when using sheets.
191     see include/wx/osx/cocoa/private.h for more info
192 */
193 @implementation ModalDialogDelegate
194 - (id)init
195 {
196     self = [super init];
197     sheetFinished = NO;
198     resultCode = -1;
199     impl = 0;
200     return self;
201 }
202
203 - (void)setImplementation: (wxDialog *)dialog
204 {
205     impl = dialog;
206 }
207
208 - (BOOL)finished
209 {
210     return sheetFinished;
211 }
212
213 - (int)code
214 {
215     return resultCode;
216 }
217
218 - (void)waitForSheetToFinish
219 {
220     while (!sheetFinished)
221     {
222         wxSafeYield();
223     }
224 }
225
226 - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
227 {
228     wxUnusedVar(contextInfo);
229     resultCode = returnCode;
230     sheetFinished = YES;
231     // NSAlerts don't need nor respond to orderOut
232     if ([sheet respondsToSelector:@selector(orderOut:)])
233         [sheet orderOut: self];
234         
235     if (impl)
236         impl->ModalFinishedCallback(sheet, returnCode);
237 }
238 @end
239
240 // here we subclass NSApplication, for the purpose of being able to override sendEvent.
241 @interface wxNSApplication : NSApplication
242 {
243 }
244
245 - (void)sendEvent:(NSEvent *)anEvent;
246
247 @end
248
249 @implementation wxNSApplication
250
251 /* This is needed because otherwise we don't receive any key-up events for command-key
252  combinations (an AppKit bug, apparently)                       */
253 - (void)sendEvent:(NSEvent *)anEvent
254 {
255     if ([anEvent type] == NSKeyUp && ([anEvent modifierFlags] & NSCommandKeyMask))
256         [[self keyWindow] sendEvent:anEvent];
257     else [super sendEvent:anEvent];
258 }
259
260 @end
261
262 WX_NSObject appcontroller = nil;
263
264 NSLayoutManager* gNSLayoutManager = nil;
265
266 WX_NSObject wxApp::OSXCreateAppController()
267 {
268     return [[wxNSAppController alloc] init];
269 }
270
271 bool wxApp::DoInitGui()
272 {
273     wxMacAutoreleasePool pool;
274
275     if (!sm_isEmbedded)
276     {
277         [wxNSApplication sharedApplication];
278
279         appcontroller = OSXCreateAppController();
280         [NSApp setDelegate:appcontroller];
281
282         // calling finishLaunching so early before running the loop seems to trigger some 'MenuManager compatibility' which leads
283         // to the duplication of menus under 10.5 and a warning under 10.6
284 #if 0
285         [NSApp finishLaunching];
286 #endif
287     }
288     gNSLayoutManager = [[NSLayoutManager alloc] init];
289     
290     return true;
291 }
292
293 void wxApp::DoCleanUp()
294 {
295     if ( appcontroller != nil )
296     {
297         [NSApp setDelegate:nil];
298         [appcontroller release];
299         appcontroller = nil;
300     }
301     if ( gNSLayoutManager != nil )
302     {
303         [gNSLayoutManager release];
304         gNSLayoutManager = nil;
305     }
306 }
307
308 void wxClientDisplayRect(int *x, int *y, int *width, int *height)
309 {
310     NSRect displayRect = [wxOSXGetMenuScreen() visibleFrame];
311     wxRect r = wxFromNSRect( NULL, displayRect );
312     if ( x )
313         *x = r.x;
314     if ( y )
315         *y = r.y;
316     if ( width )
317         *width = r.GetWidth();
318     if ( height )
319         *height = r.GetHeight();
320
321 }
322
323 void wxGetMousePosition( int* x, int* y )
324 {
325     wxPoint pt = wxFromNSPoint(NULL, [NSEvent mouseLocation]);
326     if ( x )
327         *x = pt.x;
328     if ( y )
329         *y = pt.y;
330 };
331
332 #if wxOSX_USE_COCOA && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6
333
334 wxMouseState wxGetMouseState()
335 {
336     wxMouseState ms;
337     
338     wxPoint pt = wxGetMousePosition();
339     ms.SetX(pt.x);
340     ms.SetY(pt.y);
341     
342     NSUInteger modifiers = [NSEvent modifierFlags];
343     NSUInteger buttons = [NSEvent pressedMouseButtons];
344     
345     ms.SetLeftDown( (buttons & 0x01) != 0 );
346     ms.SetMiddleDown( (buttons & 0x04) != 0 );
347     ms.SetRightDown( (buttons & 0x02) != 0 );
348     
349     ms.SetRawControlDown(modifiers & NSControlKeyMask);
350     ms.SetShiftDown(modifiers & NSShiftKeyMask);
351     ms.SetAltDown(modifiers & NSAlternateKeyMask);
352     ms.SetControlDown(modifiers & NSCommandKeyMask);
353     
354     return ms;
355 }
356
357
358 #endif
359
360 wxTimerImpl* wxGUIAppTraits::CreateTimerImpl(wxTimer *timer)
361 {
362     return new wxOSXTimerImpl(timer);
363 }
364
365 int gs_wxBusyCursorCount = 0;
366 extern wxCursor    gMacCurrentCursor;
367 wxCursor        gMacStoredActiveCursor;
368
369 // Set the cursor to the busy cursor for all windows
370 void wxBeginBusyCursor(const wxCursor *cursor)
371 {
372     if (gs_wxBusyCursorCount++ == 0)
373     {
374         NSEnumerator *enumerator = [[[NSApplication sharedApplication] windows] objectEnumerator];
375         id object;
376         
377         while ((object = [enumerator nextObject])) {
378             [(NSWindow*) object disableCursorRects];
379         }        
380
381         gMacStoredActiveCursor = gMacCurrentCursor;
382         cursor->MacInstall();
383
384         wxSetCursor(*cursor);
385     }
386     //else: nothing to do, already set
387 }
388
389 // Restore cursor to normal
390 void wxEndBusyCursor()
391 {
392     wxCHECK_RET( gs_wxBusyCursorCount > 0,
393         wxT("no matching wxBeginBusyCursor() for wxEndBusyCursor()") );
394
395     if (--gs_wxBusyCursorCount == 0)
396     {
397         NSEnumerator *enumerator = [[[NSApplication sharedApplication] windows] objectEnumerator];
398         id object;
399         
400         while ((object = [enumerator nextObject])) {
401             [(NSWindow*) object enableCursorRects];
402         }        
403
404         wxSetCursor(wxNullCursor);
405
406         gMacStoredActiveCursor.MacInstall();
407         gMacStoredActiveCursor = wxNullCursor;
408     }
409 }
410
411 // true if we're between the above two calls
412 bool wxIsBusy()
413 {
414     return (gs_wxBusyCursorCount > 0);
415 }
416
417 wxBitmap wxWindowDCImpl::DoGetAsBitmap(const wxRect *subrect) const
418 {
419     // wxScreenDC is derived from wxWindowDC, so a screen dc will
420     // call this method when a Blit is performed with it as a source.
421     if (!m_window)
422         return wxNullBitmap;
423
424     wxSize sz = m_window->GetSize();
425
426     int width = subrect != NULL ? subrect->width : sz.x;
427     int height = subrect !=  NULL ? subrect->height : sz.y ;
428
429     wxBitmap bitmap(width, height);
430
431     NSView* view = (NSView*) m_window->GetHandle();
432     if ( [view isHiddenOrHasHiddenAncestor] == NO )
433     {
434         [view lockFocus];
435         // we use this method as other methods force a repaint, and this method can be
436         // called from OnPaint, even with the window's paint dc as source (see wxHTMLWindow)
437         NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: [view bounds]];
438         [view unlockFocus];
439         if ( [rep respondsToSelector:@selector(CGImage)] )
440         {
441             CGImageRef cgImageRef = (CGImageRef)[rep CGImage];
442
443             CGRect r = CGRectMake( 0 , 0 , CGImageGetWidth(cgImageRef)  , CGImageGetHeight(cgImageRef) );
444             // since our context is upside down we dont use CGContextDrawImage
445             wxMacDrawCGImage( (CGContextRef) bitmap.GetHBITMAP() , &r, cgImageRef ) ;
446         }
447         else
448         {
449             // TODO for 10.4 in case we can support this for osx_cocoa
450         }
451         [rep release];
452     }
453
454     return bitmap;
455 }
456
457 #endif // wxUSE_GUI
458
459 #endif // wxOSX_USE_COCOA