Fixed incorrect comment about [NSDate distantPast]. We aren't seeking to
[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     Apple documentation states that nil can be passed in place of
81     [NSDate distantPast] to the untilDate parameter.  However, according
82     to Ryan Norton this crashes on Jaguar (10.2).
83 */
84    
85 - (NSEvent *)nextEventMatchingMask:(unsigned int)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)flag
86 {
87     // Get the same events except don't block
88     NSEvent *event = [super nextEventMatchingMask:mask untilDate:[NSDate distantPast] inMode:mode dequeue:flag];
89     // If we got one, simply return it
90     if(event)
91         return event;
92     // No events, try doing some idle stuff
93     if(sg_needIdle
94 #ifdef __WXDEBUG__
95         && !wxTheApp->IsInAssert()
96 #endif
97         && ([NSDefaultRunLoopMode isEqualToString:mode] || [NSModalPanelRunLoopMode isEqualToString:mode]))
98     {
99         sg_needIdle = false;
100         wxLogTrace(wxTRACE_COCOA,wxT("Processing idle events"));
101         while(wxTheApp->ProcessIdle())
102         {
103             // Get the same events except don't block
104             NSEvent *event = [super nextEventMatchingMask:mask untilDate:[NSDate distantPast] inMode:mode dequeue:flag];
105             // If we got one, simply return it
106             if(event)
107                 return event;
108             // we didn't get one, do some idle work
109             wxLogTrace(wxTRACE_COCOA,wxT("Looping idle events"));
110         }
111         // No more idle work requested, block
112         wxLogTrace(wxTRACE_COCOA,wxT("Finished idle processing"));
113     }
114     else
115         wxLogTrace(wxTRACE_COCOA,wxT("Avoiding idle processing sg_needIdle=%d"),sg_needIdle);
116     return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:flag];
117 }
118
119 - (void)sendEvent: (NSEvent*)anEvent
120 {
121     wxLogTrace(wxTRACE_COCOA,wxT("SendEvent"));
122     sg_needIdle = true;
123     [super sendEvent: anEvent];
124 }
125
126 @end // wxPoserNSApplication
127
128 // ========================================================================
129 // wxNSApplicationDelegate
130 // ========================================================================
131 @implementation wxNSApplicationDelegate : NSObject
132
133 // NOTE: Terminate means that the event loop does NOT return and thus
134 // cleanup code doesn't properly execute.  Furthermore, wxWidgets has its
135 // own exit on frame delete mechanism.
136 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
137 {
138     return NO;
139 }
140
141 - (void)applicationWillBecomeActive:(NSNotification *)notification
142 {
143     wxTheApp->CocoaDelegate_applicationWillBecomeActive();
144 }
145
146 - (void)applicationDidBecomeActive:(NSNotification *)notification
147 {
148     wxTheApp->CocoaDelegate_applicationDidBecomeActive();
149 }
150
151 - (void)applicationWillResignActive:(NSNotification *)notification
152 {
153     wxTheApp->CocoaDelegate_applicationWillResignActive();
154 }
155
156 - (void)applicationDidResignActive:(NSNotification *)notification
157 {
158     wxTheApp->CocoaDelegate_applicationDidResignActive();
159 }
160
161 - (void)controlTintChanged:(NSNotification *)notification
162 {
163     wxLogDebug(wxT("TODO: send EVT_SYS_COLOUR_CHANGED as appropriate"));
164 }
165
166 @end // implementation wxNSApplicationDelegate : NSObject
167
168 // ========================================================================
169 // wxApp
170 // ========================================================================
171
172 // ----------------------------------------------------------------------------
173 // wxApp Static member initialization
174 // ----------------------------------------------------------------------------
175 IMPLEMENT_DYNAMIC_CLASS(wxApp, wxEvtHandler)
176 BEGIN_EVENT_TABLE(wxApp, wxEvtHandler)
177     EVT_IDLE(wxAppBase::OnIdle)
178 //    EVT_END_SESSION(wxApp::OnEndSession)
179 //    EVT_QUERY_END_SESSION(wxApp::OnQueryEndSession)
180 END_EVENT_TABLE()
181
182 // ----------------------------------------------------------------------------
183 // wxApp initialization/cleanup
184 // ----------------------------------------------------------------------------
185 bool wxApp::Initialize(int& argc, wxChar **argv)
186 {
187     wxAutoNSAutoreleasePool pool;
188     m_cocoaMainThread = [NSThread currentThread];
189     // Mac OS X passes a process serial number command line argument when
190     // the application is launched from the Finder. This argument must be
191     // removed from the command line arguments before being handled by the
192     // application (otherwise applications would need to handle it)
193     if ( argc > 1 )
194     {
195         static const wxChar *ARG_PSN = _T("-psn_");
196         if ( wxStrncmp(argv[1], ARG_PSN, wxStrlen(ARG_PSN)) == 0 )
197         {
198             // remove this argument
199             --argc;
200             memmove(argv + 1, argv + 2, argc * sizeof(wxChar *));
201         }
202     }
203
204     // Posing must be completed before any instances of the Objective-C
205     // classes being posed as are created.
206     wxPoseAsInitializer::InitializePosers();
207
208     return wxAppBase::Initialize(argc, argv);
209 }
210
211 void wxApp::CleanUp()
212 {
213     wxAutoNSAutoreleasePool pool;
214
215     wxDC::CocoaShutdownTextSystem();
216     wxMenuBarManager::DestroyInstance();
217
218     [m_cocoaApp setDelegate:nil];
219     [[NSNotificationCenter defaultCenter] removeObserver:m_cocoaAppDelegate
220         name:NSControlTintDidChangeNotification object:nil];
221     [m_cocoaAppDelegate release];
222     m_cocoaAppDelegate = NULL;
223
224     wxAppBase::CleanUp();
225 }
226
227 // ----------------------------------------------------------------------------
228 // wxApp creation
229 // ----------------------------------------------------------------------------
230 wxApp::wxApp()
231 {
232     m_topWindow = NULL;
233
234 #if WXWIN_COMPATIBILITY_2_2
235     m_wantDebugOutput = TRUE;
236 #endif
237 #ifdef __WXDEBUG__
238     m_isInAssert = FALSE;
239 #endif // __WXDEBUG__
240
241     argc = 0;
242     argv = NULL;
243     m_cocoaApp = NULL;
244     m_cocoaAppDelegate = NULL;
245 }
246
247 void wxApp::CocoaDelegate_applicationWillBecomeActive()
248 {
249 }
250
251 void wxApp::CocoaDelegate_applicationDidBecomeActive()
252 {
253 }
254
255 void wxApp::CocoaDelegate_applicationWillResignActive()
256 {
257     wxTopLevelWindowCocoa::DeactivatePendingWindow();
258 }
259
260 void wxApp::CocoaDelegate_applicationDidResignActive()
261 {
262 }
263
264 bool wxApp::OnInitGui()
265 {
266     wxAutoNSAutoreleasePool pool;
267     if(!wxAppBase::OnInitGui())
268         return FALSE;
269
270     // Create the app using the sharedApplication method
271     m_cocoaApp = [NSApplication sharedApplication];
272     m_cocoaAppDelegate = [[wxNSApplicationDelegate alloc] init];
273     [m_cocoaApp setDelegate:m_cocoaAppDelegate];
274     [[NSNotificationCenter defaultCenter] addObserver:m_cocoaAppDelegate
275         selector:@selector(controlTintChanged:)
276         name:NSControlTintDidChangeNotification object:nil];
277
278     wxMenuBarManager::CreateInstance();
279
280     wxDC::CocoaInitializeTextSystem();
281     return TRUE;
282 }
283
284 bool wxApp::CallOnInit()
285 {
286 //    wxAutoNSAutoreleasePool pool;
287     return OnInit();
288 }
289
290 bool wxApp::OnInit()
291 {
292     if(!wxAppBase::OnInit())
293         return FALSE;
294
295     return TRUE;
296 }
297
298 void wxApp::Exit()
299 {
300     wxApp::CleanUp();
301
302     wxAppConsole::Exit();
303 }
304
305 // Yield to other processes
306 bool wxApp::Yield(bool onlyIfNeeded)
307 {
308     // MT-FIXME
309     static bool s_inYield = false;
310
311 #if wxUSE_LOG
312     // disable log flushing from here because a call to wxYield() shouldn't
313     // normally result in message boxes popping up &c
314     wxLog::Suspend();
315 #endif // wxUSE_LOG
316
317     if (s_inYield)
318     {
319         if ( !onlyIfNeeded )
320         {
321             wxFAIL_MSG( wxT("wxYield called recursively" ) );
322         }
323
324         return false;
325     }
326
327     s_inYield = true;
328
329     // Run the event loop until it is out of events
330     while(1)
331     {
332         wxAutoNSAutoreleasePool pool;
333         NSEvent *event = [GetNSApplication()
334                 nextEventMatchingMask:NSAnyEventMask
335                 untilDate:[NSDate distantPast]
336                 inMode:NSDefaultRunLoopMode
337                 dequeue: YES];
338         if(!event)
339             break;
340         [GetNSApplication() sendEvent: event];
341     }
342
343 #if wxUSE_LOG
344     // let the logs be flashed again
345     wxLog::Resume();
346 #endif // wxUSE_LOG
347
348     s_inYield = false;
349
350     return true;
351 }
352
353 void wxApp::WakeUpIdle()
354 {
355     [m_cocoaApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
356             location:NSZeroPoint modifierFlags:NSAnyEventMask
357             timestamp:0 windowNumber:0 context:nil
358             subtype:0 data1:0 data2:0] atStart:NO];
359 }
360
361 #ifdef __WXDEBUG__
362 void wxApp::OnAssert(const wxChar *file, int line, const wxChar* cond, const wxChar *msg)
363 {
364     m_isInAssert = TRUE;
365     wxAppBase::OnAssert(file, line, cond, msg);
366     m_isInAssert = FALSE;
367 }
368 #endif // __WXDEBUG__
369