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