Rework comment a bit to accurately reflect situation on 10.2 - it doesn't crash,...
[wxWidgets.git] / src / cocoa / app.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        cocoa/app.mm
3 // Purpose:     wxApp
4 // Author:      David Elliott
5 // Modified by:
6 // Created:     2002/11/27
7 // RCS-ID:      $Id$
8 // Copyright:   (c) David Elliott
9 // Licence:     wxWidgets licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13 #ifndef WX_PRECOMP
14     #include "wx/defs.h"
15     #include "wx/app.h"
16     #include "wx/dc.h"
17     #include "wx/intl.h"
18     #include "wx/log.h"
19 #endif
20
21 #include "wx/module.h"
22
23 #include "wx/cocoa/ObjcPose.h"
24 #include "wx/cocoa/autorelease.h"
25 #include "wx/cocoa/mbarman.h"
26 #include "wx/cocoa/NSApplication.h"
27
28 #if wxUSE_WX_RESOURCES
29 #  include "wx/resource.h"
30 #endif
31
32 #import <AppKit/NSApplication.h>
33 #import <Foundation/NSRunLoop.h>
34 #import <Foundation/NSThread.h>
35 #import <AppKit/NSEvent.h>
36 #import <Foundation/NSString.h>
37 #import <Foundation/NSNotification.h>
38 #import <AppKit/NSCell.h>
39
40 // ========================================================================
41 // wxPoseAsInitializer
42 // ========================================================================
43 wxPoseAsInitializer *wxPoseAsInitializer::sm_first = NULL;
44
45 static bool sg_needIdle = true;
46
47 // ========================================================================
48 // wxPoserNSApplication
49 // ========================================================================
50 @interface wxPoserNSApplication : NSApplication
51 {
52 }
53
54 - (NSEvent *)nextEventMatchingMask:(unsigned int)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)flag;
55 - (void)sendEvent: (NSEvent*)anEvent;
56 @end // wxPoserNSApplication
57
58 WX_IMPLEMENT_POSER(wxPoserNSApplication);
59
60 @implementation wxPoserNSApplication : NSApplication
61
62 /* NOTE: The old method of idle event handling added the handler using the
63     [NSRunLoop -performSelector:target:argument:order:modes] which caused
64     the invocation to occur at the begining of [NSApplication
65     -nextEventMatchingMask:untilDate:expiration:inMode:dequeue:].  However,
66     the code would be scheduled for invocation with every iteration of
67     the event loop.  This new method simply overrides the method.  The
68     same caveats apply.  In particular, by the time the event loop has
69     called this method, it usually expects to receive an event.  If you
70     plan on stopping the event loop, it is wise to send an event through
71     the queue to ensure this method will return.
72     See wxEventLoop::Exit() for more information.
73
74     This overridden method calls the superclass method with an untilDate
75     parameter that indicates nil should be returned if there are no pending
76     events.  That is, nextEventMatchingMask: should not wait for an event.
77     If nil is returned then idle event processing occurs until the user
78     does not request anymore idle events or until a real event comes through.
79
80     RN: Even though Apple documentation states that nil can be passed in place 
81     of [NSDate distantPast] in the untilDate parameter, this causes Jaguar (10.2)
82     to get stuck in some kind of loop deep within nextEventMatchingMask:, thus we 
83     need to explicitly pass [NSDate distantPast] instead.
84 */
85    
86 - (NSEvent *)nextEventMatchingMask:(unsigned int)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)flag
87 {
88     // Get the same events except don't block
89     NSEvent *event = [super nextEventMatchingMask:mask untilDate:[NSDate distantPast] inMode:mode dequeue:flag];
90     // If we got one, simply return it
91     if(event)
92         return event;
93     // No events, try doing some idle stuff
94     if(sg_needIdle
95 #ifdef __WXDEBUG__
96         && !wxTheApp->IsInAssert()
97 #endif
98         && ([NSDefaultRunLoopMode isEqualToString:mode] || [NSModalPanelRunLoopMode isEqualToString:mode]))
99     {
100         sg_needIdle = false;
101         wxLogTrace(wxTRACE_COCOA,wxT("Processing idle events"));
102         while(wxTheApp->ProcessIdle())
103         {
104             // Get the same events except don't block
105             NSEvent *event = [super nextEventMatchingMask:mask untilDate:[NSDate distantPast] inMode:mode dequeue:flag];
106             // If we got one, simply return it
107             if(event)
108                 return event;
109             // we didn't get one, do some idle work
110             wxLogTrace(wxTRACE_COCOA,wxT("Looping idle events"));
111         }
112         // No more idle work requested, block
113         wxLogTrace(wxTRACE_COCOA,wxT("Finished idle processing"));
114     }
115     else
116         wxLogTrace(wxTRACE_COCOA,wxT("Avoiding idle processing sg_needIdle=%d"),sg_needIdle);
117     return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:flag];
118 }
119
120 - (void)sendEvent: (NSEvent*)anEvent
121 {
122     wxLogTrace(wxTRACE_COCOA,wxT("SendEvent"));
123     sg_needIdle = true;
124     [super sendEvent: anEvent];
125 }
126
127 @end // wxPoserNSApplication
128
129 // ========================================================================
130 // wxNSApplicationDelegate
131 // ========================================================================
132 @implementation wxNSApplicationDelegate : NSObject
133
134 // NOTE: Terminate means that the event loop does NOT return and thus
135 // cleanup code doesn't properly execute.  Furthermore, wxWidgets has its
136 // own exit on frame delete mechanism.
137 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
138 {
139     return NO;
140 }
141
142 - (void)applicationWillBecomeActive:(NSNotification *)notification
143 {
144     wxTheApp->CocoaDelegate_applicationWillBecomeActive();
145 }
146
147 - (void)applicationDidBecomeActive:(NSNotification *)notification
148 {
149     wxTheApp->CocoaDelegate_applicationDidBecomeActive();
150 }
151
152 - (void)applicationWillResignActive:(NSNotification *)notification
153 {
154     wxTheApp->CocoaDelegate_applicationWillResignActive();
155 }
156
157 - (void)applicationDidResignActive:(NSNotification *)notification
158 {
159     wxTheApp->CocoaDelegate_applicationDidResignActive();
160 }
161
162 - (void)controlTintChanged:(NSNotification *)notification
163 {
164     wxLogDebug(wxT("TODO: send EVT_SYS_COLOUR_CHANGED as appropriate"));
165 }
166
167 @end // implementation wxNSApplicationDelegate : NSObject
168
169 // ========================================================================
170 // wxApp
171 // ========================================================================
172
173 // ----------------------------------------------------------------------------
174 // wxApp Static member initialization
175 // ----------------------------------------------------------------------------
176 IMPLEMENT_DYNAMIC_CLASS(wxApp, wxEvtHandler)
177 BEGIN_EVENT_TABLE(wxApp, wxEvtHandler)
178     EVT_IDLE(wxAppBase::OnIdle)
179 //    EVT_END_SESSION(wxApp::OnEndSession)
180 //    EVT_QUERY_END_SESSION(wxApp::OnQueryEndSession)
181 END_EVENT_TABLE()
182
183 // ----------------------------------------------------------------------------
184 // wxApp initialization/cleanup
185 // ----------------------------------------------------------------------------
186 bool wxApp::Initialize(int& argc, wxChar **argv)
187 {
188     wxAutoNSAutoreleasePool pool;
189     m_cocoaMainThread = [NSThread currentThread];
190     // Mac OS X passes a process serial number command line argument when
191     // the application is launched from the Finder. This argument must be
192     // removed from the command line arguments before being handled by the
193     // application (otherwise applications would need to handle it)
194     if ( argc > 1 )
195     {
196         static const wxChar *ARG_PSN = _T("-psn_");
197         if ( wxStrncmp(argv[1], ARG_PSN, wxStrlen(ARG_PSN)) == 0 )
198         {
199             // remove this argument
200             --argc;
201             memmove(argv + 1, argv + 2, argc * sizeof(wxChar *));
202         }
203     }
204
205     // Posing must be completed before any instances of the Objective-C
206     // classes being posed as are created.
207     wxPoseAsInitializer::InitializePosers();
208
209     return wxAppBase::Initialize(argc, argv);
210 }
211
212 void wxApp::CleanUp()
213 {
214     wxAutoNSAutoreleasePool pool;
215
216     wxDC::CocoaShutdownTextSystem();
217     wxMenuBarManager::DestroyInstance();
218
219     [m_cocoaApp setDelegate:nil];
220     [[NSNotificationCenter defaultCenter] removeObserver:m_cocoaAppDelegate
221         name:NSControlTintDidChangeNotification object:nil];
222     [m_cocoaAppDelegate release];
223     m_cocoaAppDelegate = NULL;
224
225     wxAppBase::CleanUp();
226 }
227
228 // ----------------------------------------------------------------------------
229 // wxApp creation
230 // ----------------------------------------------------------------------------
231 wxApp::wxApp()
232 {
233     m_topWindow = NULL;
234
235 #if WXWIN_COMPATIBILITY_2_2
236     m_wantDebugOutput = TRUE;
237 #endif
238 #ifdef __WXDEBUG__
239     m_isInAssert = FALSE;
240 #endif // __WXDEBUG__
241
242     argc = 0;
243     argv = NULL;
244     m_cocoaApp = NULL;
245     m_cocoaAppDelegate = NULL;
246 }
247
248 void wxApp::CocoaDelegate_applicationWillBecomeActive()
249 {
250 }
251
252 void wxApp::CocoaDelegate_applicationDidBecomeActive()
253 {
254 }
255
256 void wxApp::CocoaDelegate_applicationWillResignActive()
257 {
258     wxTopLevelWindowCocoa::DeactivatePendingWindow();
259 }
260
261 void wxApp::CocoaDelegate_applicationDidResignActive()
262 {
263 }
264
265 bool wxApp::OnInitGui()
266 {
267     wxAutoNSAutoreleasePool pool;
268     if(!wxAppBase::OnInitGui())
269         return FALSE;
270
271     // Create the app using the sharedApplication method
272     m_cocoaApp = [NSApplication sharedApplication];
273     m_cocoaAppDelegate = [[wxNSApplicationDelegate alloc] init];
274     [m_cocoaApp setDelegate:m_cocoaAppDelegate];
275     [[NSNotificationCenter defaultCenter] addObserver:m_cocoaAppDelegate
276         selector:@selector(controlTintChanged:)
277         name:NSControlTintDidChangeNotification object:nil];
278
279     wxMenuBarManager::CreateInstance();
280
281     wxDC::CocoaInitializeTextSystem();
282     return TRUE;
283 }
284
285 bool wxApp::CallOnInit()
286 {
287 //    wxAutoNSAutoreleasePool pool;
288     return OnInit();
289 }
290
291 bool wxApp::OnInit()
292 {
293     if(!wxAppBase::OnInit())
294         return FALSE;
295
296     return TRUE;
297 }
298
299 void wxApp::Exit()
300 {
301     wxApp::CleanUp();
302
303     wxAppConsole::Exit();
304 }
305
306 // Yield to other processes
307 bool wxApp::Yield(bool onlyIfNeeded)
308 {
309     // MT-FIXME
310     static bool s_inYield = false;
311
312 #if wxUSE_LOG
313     // disable log flushing from here because a call to wxYield() shouldn't
314     // normally result in message boxes popping up &c
315     wxLog::Suspend();
316 #endif // wxUSE_LOG
317
318     if (s_inYield)
319     {
320         if ( !onlyIfNeeded )
321         {
322             wxFAIL_MSG( wxT("wxYield called recursively" ) );
323         }
324
325         return false;
326     }
327
328     s_inYield = true;
329
330     // Run the event loop until it is out of events
331     while(1)
332     {
333         wxAutoNSAutoreleasePool pool;
334         NSEvent *event = [GetNSApplication()
335                 nextEventMatchingMask:NSAnyEventMask
336                 untilDate:[NSDate distantPast]
337                 inMode:NSDefaultRunLoopMode
338                 dequeue: YES];
339         if(!event)
340             break;
341         [GetNSApplication() sendEvent: event];
342     }
343
344 #if wxUSE_LOG
345     // let the logs be flashed again
346     wxLog::Resume();
347 #endif // wxUSE_LOG
348
349     s_inYield = false;
350
351     return true;
352 }
353
354 void wxApp::WakeUpIdle()
355 {
356     [m_cocoaApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
357             location:NSZeroPoint modifierFlags:NSAnyEventMask
358             timestamp:0 windowNumber:0 context:nil
359             subtype:0 data1:0 data2:0] atStart:NO];
360 }
361
362 #ifdef __WXDEBUG__
363 void wxApp::OnAssert(const wxChar *file, int line, const wxChar* cond, const wxChar *msg)
364 {
365     m_isInAssert = TRUE;
366     wxAppBase::OnAssert(file, line, cond, msg);
367     m_isInAssert = FALSE;
368 }
369 #endif // __WXDEBUG__
370