process pending wx events before sending idle events
[wxWidgets.git] / src / cocoa / app.mm
1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/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 //              Software 2000 Ltd.
10 // Licence:     wxWidgets licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 #include "wx/wxprec.h"
14
15 #include "wx/app.h"
16
17 #ifndef WX_PRECOMP
18     #include "wx/dc.h"
19     #include "wx/intl.h"
20     #include "wx/log.h"
21     #include "wx/module.h"
22 #endif
23
24 #include "wx/cocoa/ObjcRef.h"
25 #include "wx/cocoa/autorelease.h"
26 #include "wx/cocoa/mbarman.h"
27 #include "wx/cocoa/NSApplication.h"
28
29 #import <AppKit/NSApplication.h>
30 #import <Foundation/NSRunLoop.h>
31 #import <Foundation/NSThread.h>
32 #import <AppKit/NSEvent.h>
33 #import <Foundation/NSString.h>
34 #import <Foundation/NSNotification.h>
35 #import <AppKit/NSCell.h>
36
37 bool      wxApp::sm_isEmbedded = false; // Normally we're not a plugin
38
39 // wxNSApplicationObserver singleton.
40 static wxObjcAutoRefFromAlloc<wxNSApplicationObserver*> sg_cocoaAppObserver = [[WX_GET_OBJC_CLASS(wxNSApplicationObserver) alloc] init];
41
42 // ========================================================================
43 // wxNSApplicationDelegate
44 // ========================================================================
45 @implementation wxNSApplicationDelegate : NSObject
46
47 // NOTE: Terminate means that the event loop does NOT return and thus
48 // cleanup code doesn't properly execute.  Furthermore, wxWidgets has its
49 // own exit on frame delete mechanism.
50 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
51 {
52     return NO;
53 }
54
55 @end // implementation wxNSApplicationDelegate : NSObject
56 WX_IMPLEMENT_GET_OBJC_CLASS(wxNSApplicationDelegate,NSObject)
57
58 // ========================================================================
59 // wxNSApplicationObserver
60 // ========================================================================
61 @implementation wxNSApplicationObserver : NSObject
62
63 - (void)applicationWillBecomeActive:(NSNotification *)notification
64 {
65     wxTheApp->CocoaDelegate_applicationWillBecomeActive();
66 }
67
68 - (void)applicationDidBecomeActive:(NSNotification *)notification
69 {
70     wxTheApp->CocoaDelegate_applicationDidBecomeActive();
71 }
72
73 - (void)applicationWillResignActive:(NSNotification *)notification
74 {
75     wxTheApp->CocoaDelegate_applicationWillResignActive();
76 }
77
78 - (void)applicationDidResignActive:(NSNotification *)notification
79 {
80     wxTheApp->CocoaDelegate_applicationDidResignActive();
81 }
82
83 - (void)applicationWillUpdate:(NSNotification *)notification;
84 {
85     wxTheApp->CocoaDelegate_applicationWillUpdate();
86 }
87
88 - (void)controlTintChanged:(NSNotification *)notification
89 {
90     wxLogDebug(wxT("TODO: send EVT_SYS_COLOUR_CHANGED as appropriate"));
91 }
92
93 @end // implementation wxNSApplicationObserver : NSObject
94 WX_IMPLEMENT_GET_OBJC_CLASS(wxNSApplicationObserver,NSObject)
95
96 // ========================================================================
97 // wxApp
98 // ========================================================================
99
100 // ----------------------------------------------------------------------------
101 // wxApp Static member initialization
102 // ----------------------------------------------------------------------------
103 IMPLEMENT_DYNAMIC_CLASS(wxApp, wxEvtHandler)
104
105 // ----------------------------------------------------------------------------
106 // wxApp initialization/cleanup
107 // ----------------------------------------------------------------------------
108 bool wxApp::Initialize(int& argc, wxChar **argv)
109 {
110     wxAutoNSAutoreleasePool pool;
111     m_cocoaMainThread = [NSThread currentThread];
112     // Mac OS X passes a process serial number command line argument when
113     // the application is launched from the Finder. This argument must be
114     // removed from the command line arguments before being handled by the
115     // application (otherwise applications would need to handle it)
116     if ( argc > 1 )
117     {
118         static const wxChar *ARG_PSN = _T("-psn_");
119         if ( wxStrncmp(argv[1], ARG_PSN, wxStrlen(ARG_PSN)) == 0 )
120         {
121             // remove this argument
122             --argc;
123             memmove(argv + 1, argv + 2, argc * sizeof(wxChar *));
124         }
125     }
126
127     return wxAppBase::Initialize(argc, argv);
128 }
129
130 void wxApp::CleanUp()
131 {
132     wxAutoNSAutoreleasePool pool;
133
134     wxDC::CocoaShutdownTextSystem();
135     wxMenuBarManager::DestroyInstance();
136
137     [[NSNotificationCenter defaultCenter] removeObserver:sg_cocoaAppObserver];
138     if(!sm_isEmbedded)
139     {
140         [m_cocoaApp setDelegate:nil];
141         [m_cocoaAppDelegate release];
142         m_cocoaAppDelegate = NULL;
143     }
144
145     wxAppBase::CleanUp();
146 }
147
148 // ----------------------------------------------------------------------------
149 // wxApp creation
150 // ----------------------------------------------------------------------------
151 wxApp::wxApp()
152 {
153     m_topWindow = NULL;
154
155 #ifdef __WXDEBUG__
156     m_isInAssert = false;
157 #endif // __WXDEBUG__
158
159     argc = 0;
160     argv = NULL;
161     m_cocoaApp = NULL;
162     m_cocoaAppDelegate = NULL;
163 }
164
165 void wxApp::CocoaDelegate_applicationWillBecomeActive()
166 {
167 }
168
169 void wxApp::CocoaDelegate_applicationDidBecomeActive()
170 {
171 }
172
173 void wxApp::CocoaDelegate_applicationWillResignActive()
174 {
175     wxTopLevelWindowCocoa::DeactivatePendingWindow();
176 }
177
178 void wxApp::CocoaDelegate_applicationDidResignActive()
179 {
180 }
181
182 bool wxApp::OnInitGui()
183 {
184     wxAutoNSAutoreleasePool pool;
185     if(!wxAppBase::OnInitGui())
186         return false;
187
188     // Create the app using the sharedApplication method
189     m_cocoaApp = [NSApplication sharedApplication];
190
191     if(!sm_isEmbedded)
192     {
193         // Enable response to application delegate messages
194         m_cocoaAppDelegate = [[WX_GET_OBJC_CLASS(wxNSApplicationDelegate) alloc] init];
195         [m_cocoaApp setDelegate:m_cocoaAppDelegate];
196     }
197
198     // Enable response to "delegate" messages on the notification observer
199     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
200         selector:@selector(applicationWillBecomeActive:)
201         name:NSApplicationWillBecomeActiveNotification object:nil];
202
203     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
204         selector:@selector(applicationDidBecomeActive:)
205         name:NSApplicationDidBecomeActiveNotification object:nil];
206
207     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
208         selector:@selector(applicationWillResignActive:)
209         name:NSApplicationWillResignActiveNotification object:nil];
210
211     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
212         selector:@selector(applicationDidResignActive:)
213         name:NSApplicationDidResignActiveNotification object:nil];
214
215     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
216         selector:@selector(applicationWillUpdate:)
217         name:NSApplicationWillUpdateNotification object:nil];
218
219     // Enable response to system notifications
220     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
221         selector:@selector(controlTintChanged:)
222         name:NSControlTintDidChangeNotification object:nil];
223
224     if(!sm_isEmbedded)
225         wxMenuBarManager::CreateInstance();
226
227     wxDC::CocoaInitializeTextSystem();
228     return true;
229 }
230
231 wxApp::~wxApp()
232 {
233     if(m_cfRunLoopIdleObserver != NULL)
234     {
235         // Invalidate the observer which also removes it from the run loop.
236         CFRunLoopObserverInvalidate(m_cfRunLoopIdleObserver);
237         // Release the ref as we don't need it anymore.
238         m_cfRunLoopIdleObserver.reset();
239     }
240 }
241
242 bool wxApp::CallOnInit()
243 {
244 //    wxAutoNSAutoreleasePool pool;
245     return OnInit();
246 }
247
248 bool wxApp::OnInit()
249 {
250     if(!wxAppBase::OnInit())
251         return false;
252
253     return true;
254 }
255
256 void wxApp::Exit()
257 {
258     wxApp::CleanUp();
259
260     wxAppConsole::Exit();
261 }
262
263 // Yield to other processes
264 bool wxApp::Yield(bool onlyIfNeeded)
265 {
266     // MT-FIXME
267     static bool s_inYield = false;
268
269 #if wxUSE_LOG
270     // disable log flushing from here because a call to wxYield() shouldn't
271     // normally result in message boxes popping up &c
272     wxLog::Suspend();
273 #endif // wxUSE_LOG
274
275     if (s_inYield)
276     {
277         if ( !onlyIfNeeded )
278         {
279             wxFAIL_MSG( wxT("wxYield called recursively" ) );
280         }
281
282         return false;
283     }
284
285     s_inYield = true;
286
287     // Run the event loop until it is out of events
288     while(1)
289     {
290         wxAutoNSAutoreleasePool pool;
291         NSEvent *event = [GetNSApplication()
292                 nextEventMatchingMask:NSAnyEventMask
293                 untilDate:[NSDate distantPast]
294                 inMode:NSDefaultRunLoopMode
295                 dequeue: YES];
296         if(!event)
297             break;
298         [GetNSApplication() sendEvent: event];
299     }
300
301 #if wxUSE_LOG
302     // let the logs be flashed again
303     wxLog::Resume();
304 #endif // wxUSE_LOG
305
306     s_inYield = false;
307
308     return true;
309 }
310
311 void wxApp::WakeUpIdle()
312 {
313     [m_cocoaApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
314             location:NSZeroPoint modifierFlags:NSAnyEventMask
315             timestamp:0 windowNumber:0 context:nil
316             subtype:0 data1:0 data2:0] atStart:NO];
317 }
318
319 extern "C" static void ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
320 extern "C" static void ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
321 {
322     static_cast<wxApp*>(info)->CF_ObserveMainRunLoopBeforeWaiting(observer, activity);
323 }
324
325 #if 0
326 static int sg_cApplicationWillUpdate = 0;
327 #endif
328
329 void wxApp::CocoaDelegate_applicationWillUpdate()
330 {
331     wxLogTrace(wxTRACE_COCOA,wxT("applicationWillUpdate"));
332
333 //    CFRunLoopRef cfRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
334     CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
335     wxCFRef<CFStringRef> cfRunLoopMode(CFRunLoopCopyCurrentMode(cfRunLoop));
336
337     if(m_cfRunLoopIdleObserver != NULL && m_cfObservedRunLoopMode != cfRunLoopMode)
338     {
339         CFRunLoopObserverInvalidate(m_cfRunLoopIdleObserver);
340         m_cfRunLoopIdleObserver.reset();
341     }
342 #if 0
343     ++sg_cApplicationWillUpdate;
344 #endif
345     if(m_cfRunLoopIdleObserver == NULL)
346     {
347         // Enable idle event handling
348         CFRunLoopObserverContext observerContext =
349         {   0
350         ,   this
351         ,   NULL
352         ,   NULL
353         ,   NULL
354         };
355         m_cfRunLoopIdleObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, /*repeats*/FALSE, /*priority*/0, ObserveMainRunLoopBeforeWaiting, &observerContext));
356         m_cfObservedRunLoopMode = cfRunLoopMode;
357         CFRunLoopAddObserver(cfRunLoop, m_cfRunLoopIdleObserver, m_cfObservedRunLoopMode);
358     }
359 }
360
361 static inline bool FakeNeedMoreIdle()
362 {
363 #if 0
364 // Return true on every 10th call.
365     static int idleCount = 0;
366     return ++idleCount % 10;
367 #else
368     return false;
369 #endif
370 }
371
372 void wxApp::CF_ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, int activity)
373 {
374     // Ensure that the app knows we've been invalidated
375     m_cfRunLoopIdleObserver.reset();
376 #if 0
377     wxLogTrace(wxTRACE_COCOA,wxT("Idle BEGIN (%d)"), sg_cApplicationWillUpdate);
378     sg_cApplicationWillUpdate = 0;
379 #else
380     wxLogTrace(wxTRACE_COCOA,wxT("Idle BEGIN"));
381 #endif
382     if( ProcessIdle() || FakeNeedMoreIdle() )
383     {
384         wxLogTrace(wxTRACE_COCOA, wxT("Idle REQUEST MORE"));
385         [NSApp setWindowsNeedUpdate:YES];
386     }
387     else
388     {
389         wxLogTrace(wxTRACE_COCOA, wxT("Idle END"));
390     }
391 }
392
393 #ifdef __WXDEBUG__
394 void wxApp::OnAssert(const wxChar *file, int line, const wxChar* cond, const wxChar *msg)
395 {
396     m_isInAssert = true;
397     wxAppBase::OnAssert(file, line, cond, msg);
398     m_isInAssert = false;
399 }
400 #endif // __WXDEBUG__