Allow many Objective-C classes to be uniquified at runtime.
[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 BEGIN_EVENT_TABLE(wxApp, wxEvtHandler)
105     EVT_IDLE(wxAppBase::OnIdle)
106 //    EVT_END_SESSION(wxApp::OnEndSession)
107 //    EVT_QUERY_END_SESSION(wxApp::OnQueryEndSession)
108 END_EVENT_TABLE()
109
110 // ----------------------------------------------------------------------------
111 // wxApp initialization/cleanup
112 // ----------------------------------------------------------------------------
113 bool wxApp::Initialize(int& argc, wxChar **argv)
114 {
115     wxAutoNSAutoreleasePool pool;
116     m_cocoaMainThread = [NSThread currentThread];
117     // Mac OS X passes a process serial number command line argument when
118     // the application is launched from the Finder. This argument must be
119     // removed from the command line arguments before being handled by the
120     // application (otherwise applications would need to handle it)
121     if ( argc > 1 )
122     {
123         static const wxChar *ARG_PSN = _T("-psn_");
124         if ( wxStrncmp(argv[1], ARG_PSN, wxStrlen(ARG_PSN)) == 0 )
125         {
126             // remove this argument
127             --argc;
128             memmove(argv + 1, argv + 2, argc * sizeof(wxChar *));
129         }
130     }
131
132     return wxAppBase::Initialize(argc, argv);
133 }
134
135 void wxApp::CleanUp()
136 {
137     wxAutoNSAutoreleasePool pool;
138
139     wxDC::CocoaShutdownTextSystem();
140     wxMenuBarManager::DestroyInstance();
141
142     [[NSNotificationCenter defaultCenter] removeObserver:sg_cocoaAppObserver];
143     if(!sm_isEmbedded)
144     {
145         [m_cocoaApp setDelegate:nil];
146         [m_cocoaAppDelegate release];
147         m_cocoaAppDelegate = NULL;
148     }
149
150     wxAppBase::CleanUp();
151 }
152
153 // ----------------------------------------------------------------------------
154 // wxApp creation
155 // ----------------------------------------------------------------------------
156 wxApp::wxApp()
157 {
158     m_topWindow = NULL;
159
160 #ifdef __WXDEBUG__
161     m_isInAssert = false;
162 #endif // __WXDEBUG__
163
164     argc = 0;
165     argv = NULL;
166     m_cocoaApp = NULL;
167     m_cocoaAppDelegate = NULL;
168 }
169
170 void wxApp::CocoaDelegate_applicationWillBecomeActive()
171 {
172 }
173
174 void wxApp::CocoaDelegate_applicationDidBecomeActive()
175 {
176 }
177
178 void wxApp::CocoaDelegate_applicationWillResignActive()
179 {
180     wxTopLevelWindowCocoa::DeactivatePendingWindow();
181 }
182
183 void wxApp::CocoaDelegate_applicationDidResignActive()
184 {
185 }
186
187 bool wxApp::OnInitGui()
188 {
189     wxAutoNSAutoreleasePool pool;
190     if(!wxAppBase::OnInitGui())
191         return false;
192
193     // Create the app using the sharedApplication method
194     m_cocoaApp = [NSApplication sharedApplication];
195
196     if(!sm_isEmbedded)
197     {
198         // Enable response to application delegate messages
199         m_cocoaAppDelegate = [[WX_GET_OBJC_CLASS(wxNSApplicationDelegate) alloc] init];
200         [m_cocoaApp setDelegate:m_cocoaAppDelegate];
201     }
202
203     // Enable response to "delegate" messages on the notification observer
204     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
205         selector:@selector(applicationWillBecomeActive:)
206         name:NSApplicationWillBecomeActiveNotification object:nil];
207
208     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
209         selector:@selector(applicationDidBecomeActive:)
210         name:NSApplicationDidBecomeActiveNotification object:nil];
211
212     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
213         selector:@selector(applicationWillResignActive:)
214         name:NSApplicationWillResignActiveNotification object:nil];
215
216     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
217         selector:@selector(applicationDidResignActive:)
218         name:NSApplicationDidResignActiveNotification object:nil];
219
220     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
221         selector:@selector(applicationWillUpdate:)
222         name:NSApplicationWillUpdateNotification object:nil];
223
224     // Enable response to system notifications
225     [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
226         selector:@selector(controlTintChanged:)
227         name:NSControlTintDidChangeNotification object:nil];
228
229     if(!sm_isEmbedded)
230         wxMenuBarManager::CreateInstance();
231
232     wxDC::CocoaInitializeTextSystem();
233     return true;
234 }
235
236 wxApp::~wxApp()
237 {
238     if(m_cfRunLoopIdleObserver != NULL)
239     {
240         // Invalidate the observer which also removes it from the run loop.
241         CFRunLoopObserverInvalidate(m_cfRunLoopIdleObserver);
242         // Release the ref as we don't need it anymore.
243         m_cfRunLoopIdleObserver.reset();
244     }
245 }
246
247 bool wxApp::CallOnInit()
248 {
249 //    wxAutoNSAutoreleasePool pool;
250     return OnInit();
251 }
252
253 bool wxApp::OnInit()
254 {
255     if(!wxAppBase::OnInit())
256         return false;
257
258     return true;
259 }
260
261 void wxApp::Exit()
262 {
263     wxApp::CleanUp();
264
265     wxAppConsole::Exit();
266 }
267
268 // Yield to other processes
269 bool wxApp::Yield(bool onlyIfNeeded)
270 {
271     // MT-FIXME
272     static bool s_inYield = false;
273
274 #if wxUSE_LOG
275     // disable log flushing from here because a call to wxYield() shouldn't
276     // normally result in message boxes popping up &c
277     wxLog::Suspend();
278 #endif // wxUSE_LOG
279
280     if (s_inYield)
281     {
282         if ( !onlyIfNeeded )
283         {
284             wxFAIL_MSG( wxT("wxYield called recursively" ) );
285         }
286
287         return false;
288     }
289
290     s_inYield = true;
291
292     // Run the event loop until it is out of events
293     while(1)
294     {
295         wxAutoNSAutoreleasePool pool;
296         NSEvent *event = [GetNSApplication()
297                 nextEventMatchingMask:NSAnyEventMask
298                 untilDate:[NSDate distantPast]
299                 inMode:NSDefaultRunLoopMode
300                 dequeue: YES];
301         if(!event)
302             break;
303         [GetNSApplication() sendEvent: event];
304     }
305
306 #if wxUSE_LOG
307     // let the logs be flashed again
308     wxLog::Resume();
309 #endif // wxUSE_LOG
310
311     s_inYield = false;
312
313     return true;
314 }
315
316 void wxApp::WakeUpIdle()
317 {
318     [m_cocoaApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
319             location:NSZeroPoint modifierFlags:NSAnyEventMask
320             timestamp:0 windowNumber:0 context:nil
321             subtype:0 data1:0 data2:0] atStart:NO];
322 }
323
324 extern "C" static void ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
325 extern "C" static void ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
326 {
327     static_cast<wxApp*>(info)->CF_ObserveMainRunLoopBeforeWaiting(observer, activity);
328 }
329
330 #if 0
331 static int sg_cApplicationWillUpdate = 0;
332 #endif
333
334 void wxApp::CocoaDelegate_applicationWillUpdate()
335 {
336     wxLogTrace(wxTRACE_COCOA,wxT("applicationWillUpdate"));
337
338 //    CFRunLoopRef cfRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
339     CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
340     wxCFRef<CFStringRef> cfRunLoopMode(CFRunLoopCopyCurrentMode(cfRunLoop));
341
342     if(m_cfRunLoopIdleObserver != NULL && m_cfObservedRunLoopMode != cfRunLoopMode)
343     {
344         CFRunLoopObserverInvalidate(m_cfRunLoopIdleObserver);
345         m_cfRunLoopIdleObserver.reset();
346     }
347 #if 0
348     ++sg_cApplicationWillUpdate;
349 #endif
350     if(m_cfRunLoopIdleObserver == NULL)
351     {
352         // Enable idle event handling
353         CFRunLoopObserverContext observerContext =
354         {   0
355         ,   this
356         ,   NULL
357         ,   NULL
358         ,   NULL
359         };
360         m_cfRunLoopIdleObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, /*repeats*/FALSE, /*priority*/0, ObserveMainRunLoopBeforeWaiting, &observerContext));
361         m_cfObservedRunLoopMode = cfRunLoopMode;
362         CFRunLoopAddObserver(cfRunLoop, m_cfRunLoopIdleObserver, m_cfObservedRunLoopMode);
363     }
364 }
365
366 static inline bool FakeNeedMoreIdle()
367 {
368 #if 0
369 // Return true on every 10th call.
370     static int idleCount = 0;
371     return ++idleCount % 10;
372 #else
373     return false;
374 #endif
375 }
376
377 void wxApp::CF_ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, int activity)
378 {
379     // Ensure that the app knows we've been invalidated
380     m_cfRunLoopIdleObserver.reset();
381 #if 0
382     wxLogTrace(wxTRACE_COCOA,wxT("Idle BEGIN (%d)"), sg_cApplicationWillUpdate);
383     sg_cApplicationWillUpdate = 0;
384 #else
385     wxLogTrace(wxTRACE_COCOA,wxT("Idle BEGIN"));
386 #endif
387     if( ProcessIdle() || FakeNeedMoreIdle() )
388     {
389         wxLogTrace(wxTRACE_COCOA, wxT("Idle REQUEST MORE"));
390         [NSApp setWindowsNeedUpdate:YES];
391     }
392     else
393     {
394         wxLogTrace(wxTRACE_COCOA, wxT("Idle END"));
395     }
396 }
397
398 #ifdef __WXDEBUG__
399 void wxApp::OnAssert(const wxChar *file, int line, const wxChar* cond, const wxChar *msg)
400 {
401     m_isInAssert = true;
402     wxAppBase::OnAssert(file, line, cond, msg);
403     m_isInAssert = false;
404 }
405 #endif // __WXDEBUG__