]> git.saurik.com Git - wxWidgets.git/blame - src/cocoa/app.mm
Fix wxHtmlHelpData::SetTempDir() to behave correctly without trailing slash.
[wxWidgets.git] / src / cocoa / app.mm
CommitLineData
fb896a32 1/////////////////////////////////////////////////////////////////////////////
8898456d 2// Name: src/cocoa/app.mm
fb896a32
DE
3// Purpose: wxApp
4// Author: David Elliott
5// Modified by:
6// Created: 2002/11/27
fb896a32 7// Copyright: (c) David Elliott
047c1182 8// Software 2000 Ltd.
526954c5 9// Licence: wxWindows licence
fb896a32
DE
10/////////////////////////////////////////////////////////////////////////////
11
fb896a32 12#include "wx/wxprec.h"
8898456d 13
da80ae71
WS
14#include "wx/app.h"
15
fb896a32 16#ifndef WX_PRECOMP
fb896a32
DE
17 #include "wx/intl.h"
18 #include "wx/log.h"
02761f6c 19 #include "wx/module.h"
fb896a32
DE
20#endif
21
047c1182 22#include "wx/cocoa/ObjcRef.h"
493902ac 23#include "wx/cocoa/autorelease.h"
af367f46 24#include "wx/cocoa/mbarman.h"
bb27b372 25#include "wx/cocoa/NSApplication.h"
fb45bb1f 26
938156b2
DE
27#include "wx/cocoa/dc.h"
28
fb896a32
DE
29#import <AppKit/NSApplication.h>
30#import <Foundation/NSRunLoop.h>
14fc7eb4 31#import <Foundation/NSThread.h>
aaa5ab05 32#import <AppKit/NSEvent.h>
eb537cfb 33#import <Foundation/NSString.h>
bb27b372
RN
34#import <Foundation/NSNotification.h>
35#import <AppKit/NSCell.h>
fb896a32 36
879180b6
DE
37bool wxApp::sm_isEmbedded = false; // Normally we're not a plugin
38
047c1182 39// wxNSApplicationObserver singleton.
a24aa427 40static wxObjcAutoRefFromAlloc<wxNSApplicationObserver*> sg_cocoaAppObserver = [[WX_GET_OBJC_CLASS(wxNSApplicationObserver) alloc] init];
047c1182 41
ba808e24
DE
42// ========================================================================
43// wxNSApplicationDelegate
44// ========================================================================
ba808e24
DE
45@implementation wxNSApplicationDelegate : NSObject
46
2990ec97 47// NOTE: Terminate means that the event loop does NOT return and thus
bb27b372 48// cleanup code doesn't properly execute. Furthermore, wxWidgets has its
2990ec97 49// own exit on frame delete mechanism.
fb896a32
DE
50- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
51{
2990ec97 52 return NO;
fb896a32
DE
53}
54
047c1182 55@end // implementation wxNSApplicationDelegate : NSObject
a24aa427 56WX_IMPLEMENT_GET_OBJC_CLASS(wxNSApplicationDelegate,NSObject)
047c1182
DE
57
58// ========================================================================
59// wxNSApplicationObserver
60// ========================================================================
61@implementation wxNSApplicationObserver : NSObject
62
0187ddb4
DE
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
047c1182
DE
83- (void)applicationWillUpdate:(NSNotification *)notification;
84{
85 wxTheApp->CocoaDelegate_applicationWillUpdate();
86}
87
bb27b372
RN
88- (void)controlTintChanged:(NSNotification *)notification
89{
422d306c 90 wxLogDebug(wxT("TODO: send EVT_SYS_COLOUR_CHANGED as appropriate"));
bb27b372
RN
91}
92
047c1182 93@end // implementation wxNSApplicationObserver : NSObject
a24aa427 94WX_IMPLEMENT_GET_OBJC_CLASS(wxNSApplicationObserver,NSObject)
e2478fde 95
70fb935a
DE
96// ========================================================================
97// wxApp
98// ========================================================================
fb896a32
DE
99
100// ----------------------------------------------------------------------------
101// wxApp Static member initialization
102// ----------------------------------------------------------------------------
fb896a32 103IMPLEMENT_DYNAMIC_CLASS(wxApp, wxEvtHandler)
fb896a32
DE
104
105// ----------------------------------------------------------------------------
05e2b077 106// wxApp initialization/cleanup
fb896a32 107// ----------------------------------------------------------------------------
05e2b077 108bool wxApp::Initialize(int& argc, wxChar **argv)
fb896a32 109{
47f1ad6a 110 wxAutoNSAutoreleasePool pool;
14fc7eb4 111 m_cocoaMainThread = [NSThread currentThread];
05e2b077
VZ
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 {
9a83f860 118 static const wxChar *ARG_PSN = wxT("-psn_");
b0c0a393 119 if ( wxStrncmp(argv[1], ARG_PSN, wxStrlen(ARG_PSN)) == 0 )
05e2b077
VZ
120 {
121 // remove this argument
33f39af3 122 --argc;
b0c0a393 123 memmove(argv + 1, argv + 2, argc * sizeof(wxChar *));
05e2b077
VZ
124 }
125 }
126
04453a9e
DE
127 /*
128 Cocoa supports -Key value options which set the user defaults key "Key"
129 to the value "value" Some of them are very handy for debugging like
130 -NSShowAllViews YES. Cocoa picks these up from the real argv so
131 our removal of them from the wx copy of it does not affect Cocoa's
132 ability to see them.
133
134 We basically just assume that any "-NS" option and its following
135 argument needs to be removed from argv. We hope that user code does
136 not expect to see -NS options and indeed it's probably a safe bet
137 since most user code accepting options is probably using the
138 double-dash GNU-style syntax.
139 */
140 for(int i=1; i < argc; ++i)
141 {
142 static const wxChar *ARG_NS = wxT("-NS");
143 static const int ARG_NS_LEN = wxStrlen(ARG_NS);
144 if( wxStrncmp(argv[i], ARG_NS, ARG_NS_LEN) == 0 )
145 {
146 // Only eat this option if it has an argument
147 if( (i + 1) < argc )
148 {
149 argc -= 2;
150 memmove(argv + i, argv + i + 2, argc * sizeof(wxChar*));
151 // drop back one position so the next run through the loop
152 // reprocesses the argument at our current index.
153 --i;
154 }
155 }
156 }
157
94826170 158 return wxAppBase::Initialize(argc, argv);
fb896a32
DE
159}
160
94826170 161void wxApp::CleanUp()
fb896a32 162{
0187ddb4
DE
163 wxAutoNSAutoreleasePool pool;
164
938156b2 165 wxCocoaDCImpl::CocoaShutdownTextSystem();
af367f46 166 wxMenuBarManager::DestroyInstance();
94826170 167
879180b6
DE
168 [[NSNotificationCenter defaultCenter] removeObserver:sg_cocoaAppObserver];
169 if(!sm_isEmbedded)
170 {
171 [m_cocoaApp setDelegate:nil];
172 [m_cocoaAppDelegate release];
173 m_cocoaAppDelegate = NULL;
174 }
0187ddb4 175
94826170 176 wxAppBase::CleanUp();
fb896a32
DE
177}
178
179// ----------------------------------------------------------------------------
180// wxApp creation
181// ----------------------------------------------------------------------------
fb896a32
DE
182wxApp::wxApp()
183{
184 m_topWindow = NULL;
fb896a32 185
fb896a32 186 argc = 0;
b95d7ac9 187#if !wxUSE_UNICODE
fb896a32 188 argv = NULL;
b95d7ac9 189#endif
fb896a32 190 m_cocoaApp = NULL;
0187ddb4 191 m_cocoaAppDelegate = NULL;
fb896a32
DE
192}
193
0187ddb4
DE
194void wxApp::CocoaDelegate_applicationWillBecomeActive()
195{
196}
197
198void wxApp::CocoaDelegate_applicationDidBecomeActive()
199{
200}
201
202void wxApp::CocoaDelegate_applicationWillResignActive()
203{
39050120 204 wxTopLevelWindowCocoa::DeactivatePendingWindow();
0187ddb4
DE
205}
206
207void wxApp::CocoaDelegate_applicationDidResignActive()
208{
209}
210
fb896a32
DE
211bool wxApp::OnInitGui()
212{
47f1ad6a 213 wxAutoNSAutoreleasePool pool;
fb896a32 214 if(!wxAppBase::OnInitGui())
8898456d 215 return false;
fb896a32
DE
216
217 // Create the app using the sharedApplication method
218 m_cocoaApp = [NSApplication sharedApplication];
047c1182 219
879180b6
DE
220 if(!sm_isEmbedded)
221 {
222 // Enable response to application delegate messages
a24aa427 223 m_cocoaAppDelegate = [[WX_GET_OBJC_CLASS(wxNSApplicationDelegate) alloc] init];
879180b6
DE
224 [m_cocoaApp setDelegate:m_cocoaAppDelegate];
225 }
047c1182
DE
226
227 // Enable response to "delegate" messages on the notification observer
228 [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
229 selector:@selector(applicationWillBecomeActive:)
230 name:NSApplicationWillBecomeActiveNotification object:nil];
231
232 [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
233 selector:@selector(applicationDidBecomeActive:)
234 name:NSApplicationDidBecomeActiveNotification object:nil];
235
236 [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
237 selector:@selector(applicationWillResignActive:)
238 name:NSApplicationWillResignActiveNotification object:nil];
239
240 [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
241 selector:@selector(applicationDidResignActive:)
242 name:NSApplicationDidResignActiveNotification object:nil];
243
244 [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
245 selector:@selector(applicationWillUpdate:)
246 name:NSApplicationWillUpdateNotification object:nil];
247
248 // Enable response to system notifications
249 [[NSNotificationCenter defaultCenter] addObserver:sg_cocoaAppObserver
bb27b372
RN
250 selector:@selector(controlTintChanged:)
251 name:NSControlTintDidChangeNotification object:nil];
af367f46 252
879180b6
DE
253 if(!sm_isEmbedded)
254 wxMenuBarManager::CreateInstance();
af367f46 255
938156b2 256 wxCocoaDCImpl::CocoaInitializeTextSystem();
8898456d 257 return true;
fb896a32
DE
258}
259
047c1182
DE
260wxApp::~wxApp()
261{
262 if(m_cfRunLoopIdleObserver != NULL)
263 {
264 // Invalidate the observer which also removes it from the run loop.
265 CFRunLoopObserverInvalidate(m_cfRunLoopIdleObserver);
266 // Release the ref as we don't need it anymore.
267 m_cfRunLoopIdleObserver.reset();
268 }
269}
270
47f1ad6a
DE
271bool wxApp::CallOnInit()
272{
bd3e8827 273// wxAutoNSAutoreleasePool pool;
47f1ad6a
DE
274 return OnInit();
275}
276
fb896a32
DE
277bool wxApp::OnInit()
278{
279 if(!wxAppBase::OnInit())
8898456d 280 return false;
fb896a32 281
8898456d 282 return true;
fb896a32
DE
283}
284
70fb935a
DE
285void wxApp::Exit()
286{
287 wxApp::CleanUp();
288
289 wxAppConsole::Exit();
290}
291
eb537cfb
DE
292void wxApp::WakeUpIdle()
293{
30cfcda5
DE
294 /* When called from the main thread the NSAutoreleasePool managed by
295 the [NSApplication run] method would ordinarily be in place and so
296 one would think a pool here would be unnecessary.
297
298 However, when called from a different thread there is usually no
299 NSAutoreleasePool in place because wxThread has no knowledge of
300 wxCocoa. The pool here is generally only ever going to contain
301 the NSEvent we create with the factory method. As soon as we add
302 it to the main event queue with postEvent:atStart: it is retained
303 and so safe for our pool to release.
304 */
305 wxAutoNSAutoreleasePool pool;
306 /* NOTE: This is a little heavy handed. What this does is cause an
307 AppKit NSEvent to be added to NSApplication's queue (which is always
308 on the main thread). This will cause the main thread runloop to
309 exit which returns control to nextEventMatchingMask which returns
310 the event which is then sent with sendEvent: and essentially dropped
311 since it's not for a window (windowNumber 0) and NSApplication
312 certainly doesn't understand it.
313
314 With the exception of wxEventLoop::Exit which uses us to cause the
315 runloop to exit and return to the NSApplication event loop, most
316 callers only need wx idle to happen, or more specifically only really
317 need to ensure that ProcessPendingEvents is called which is currently
318 done without exiting the runloop.
319
320 Be careful if you decide to change the implementation of this method
4c51a665 321 as wxEventLoop::Exit depends on the current behaviour.
30cfcda5 322 */
eb537cfb
DE
323 [m_cocoaApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
324 location:NSZeroPoint modifierFlags:NSAnyEventMask
325 timestamp:0 windowNumber:0 context:nil
326 subtype:0 data1:0 data2:0] atStart:NO];
327}
328
047c1182
DE
329extern "C" static void ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
330extern "C" static void ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
331{
332 static_cast<wxApp*>(info)->CF_ObserveMainRunLoopBeforeWaiting(observer, activity);
333}
334
335#if 0
336static int sg_cApplicationWillUpdate = 0;
337#endif
338
30cfcda5
DE
339/*!
340 Invoked from the applicationWillUpdate notification observer. See the
341 NSApplication documentation for the official statement on when this
342 will be called. Since it can be hard to understand for a Cocoa newbie
343 I'll try to explain it here as it relates to wxCocoa.
344
345 Basically, we get called from within nextEventMatchingMask if and only
346 if any user code told the application to send the update notification
347 (sort of like a request for idle events). However, unlike wx idle events,
348 this notification is sent quite often, nearly every time through the loop
349 because nearly every control tells the application to send it.
350
351 Because wx idle events are only supposed to be sent when the event loop
352 is about to block we instead schedule a function to be called just
353 before the run loop waits and send the idle events from there.
354
355 It also has the desirable effect of only sending the wx idle events when
d13b34d3 356 the event loop is actually going to block. If the event loop is being
30cfcda5
DE
357 pumped manualy (e.g. like a PeekMessage) then the kCFRunLoopBeforeWaiting
358 observer never fires. Our Yield() method depends on this because sending
359 idle events from within Yield would be bad.
360
361 Normally you might think that we could just set the observer up once and
362 leave it attached. However, this is problematic because our run loop
363 observer calls user code (the idle handlers) which can actually display
364 modal dialogs. Displaying a modal dialog causes reentry of the event
365 loop, usually in a different run loop mode than the main loop (e.g. in
366 modal-dialog mode instead of default mode). Because we only register the
367 observer with the run loop mode at the time of this call, it won't be
368 called from a modal loop.
369
370 We want it to be called and thus we need a new observer.
371 */
047c1182
DE
372void wxApp::CocoaDelegate_applicationWillUpdate()
373{
374 wxLogTrace(wxTRACE_COCOA,wxT("applicationWillUpdate"));
375
376// CFRunLoopRef cfRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
377 CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
378 wxCFRef<CFStringRef> cfRunLoopMode(CFRunLoopCopyCurrentMode(cfRunLoop));
379
30cfcda5
DE
380 /* If we have an observer and that observer is for the wrong run loop
381 mode then invalidate it and release it.
382 */
047c1182
DE
383 if(m_cfRunLoopIdleObserver != NULL && m_cfObservedRunLoopMode != cfRunLoopMode)
384 {
385 CFRunLoopObserverInvalidate(m_cfRunLoopIdleObserver);
386 m_cfRunLoopIdleObserver.reset();
387 }
388#if 0
389 ++sg_cApplicationWillUpdate;
390#endif
30cfcda5
DE
391 /* This will be true either on the first call or when the above code has
392 invalidated and released the exisiting observer.
393 */
047c1182
DE
394 if(m_cfRunLoopIdleObserver == NULL)
395 {
396 // Enable idle event handling
397 CFRunLoopObserverContext observerContext =
398 { 0
399 , this
400 , NULL
401 , NULL
402 , NULL
403 };
30cfcda5
DE
404 /* NOTE: I can't recall why we don't just let the observer repeat
405 instead of invalidating itself each time it fires thus requiring
406 it to be recreated for each shot but there was if I remember
407 some good (but very obscure) reason for it.
408
409 On the other hand, I could be wrong so don't take that as gospel.
410 */
047c1182
DE
411 m_cfRunLoopIdleObserver.reset(CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, /*repeats*/FALSE, /*priority*/0, ObserveMainRunLoopBeforeWaiting, &observerContext));
412 m_cfObservedRunLoopMode = cfRunLoopMode;
413 CFRunLoopAddObserver(cfRunLoop, m_cfRunLoopIdleObserver, m_cfObservedRunLoopMode);
414 }
415}
416
417static inline bool FakeNeedMoreIdle()
418{
419#if 0
420// Return true on every 10th call.
421 static int idleCount = 0;
422 return ++idleCount % 10;
423#else
424 return false;
425#endif
426}
427
30cfcda5
DE
428/*!
429 Called by CFRunLoop just before waiting. This is the appropriate time to
430 send idle events. Unlike other ports, we don't peek the queue for events
431 and stop idling if there is one. Instead, if the user requests more idle
432 events we tell Cocoa to send us an applicationWillUpdate notification
433 which will cause our observer of that notification to tell CFRunLoop to
434 call us before waiting which will cause us to be fired again but only
435 after exhausting the event queue.
436
437 The reason we do it this way is that peeking for an event causes CFRunLoop
438 to reenter and fire off its timers, observers, and sources which we're
439 better off avoiding. Doing it this way, we basically let CFRunLoop do the
440 work of peeking for the next event which is much nicer.
441 */
047c1182
DE
442void wxApp::CF_ObserveMainRunLoopBeforeWaiting(CFRunLoopObserverRef observer, int activity)
443{
30cfcda5
DE
444 // Ensure that CocoaDelegate_applicationWillUpdate will recreate us.
445 // We've already been invalidated by CFRunLoop because we are one-shot.
047c1182
DE
446 m_cfRunLoopIdleObserver.reset();
447#if 0
448 wxLogTrace(wxTRACE_COCOA,wxT("Idle BEGIN (%d)"), sg_cApplicationWillUpdate);
449 sg_cApplicationWillUpdate = 0;
450#else
451 wxLogTrace(wxTRACE_COCOA,wxT("Idle BEGIN"));
452#endif
453 if( ProcessIdle() || FakeNeedMoreIdle() )
454 {
455 wxLogTrace(wxTRACE_COCOA, wxT("Idle REQUEST MORE"));
456 [NSApp setWindowsNeedUpdate:YES];
457 }
458 else
459 {
460 wxLogTrace(wxTRACE_COCOA, wxT("Idle END"));
461 }
462}
463
30cfcda5
DE
464/* A note about Cocoa's event loops vs. run loops:
465
466 It's important to understand that Cocoa has a two-level event loop. The
467 outer level is run by NSApplication and can only ever happen on the main
468 thread. The nextEventMatchingMask:untilDate:inMode:dequeue: method returns
469 the next event which is then given to sendEvent: to send it. These
470 methods are defined in NSApplication and are thus part of AppKit.
471
472 Events (NSEvent) are only sent due to actual user actions like clicking
473 the mouse or moving the mouse or pressing a key and so on. There are no
474 paint events; there are no timer events; there are no socket events; there
475 are no idle events.
476
477 All of those types of "events" have nothing to do with the GUI at all.
478 That is why Cocoa's AppKit doesn't implement them. Instead, they are
479 implemented in Foundation's NSRunLoop which on OS X uses CFRunLoop
480 to do the actual work.
481
482 How NSApplication uses NSRunLoop is rather interesting. Basically, it
483 interacts with NSRunLoop only from within the nextEventMatchingMask
484 method. It passes its inMode: argument almost directly to NSRunLoop
485 and thus CFRunLoop. The run loop then runs (e.g. loops) until it
486 is told to exit. The run loop calls the callout functions directly.
487 From within those callout functions the run loop is considered to
488 be running. Presumably, the AppKit installs a run loop source to
489 receive messages from the window server over the mach port (like a
490 socket). For some messages (e.g. need to paint) the AppKit will
491 call application code like drawRect: without exiting the run loop.
492 For other messages (ones that can be encapsulated in an NSEvent)
493 the AppKit tells the run loop to exit which returns control to
494 the nextEventMatchingMask method which then returns the NSEvent
495 object. It's important to note that once the runloop has exited
496 it is no longer considered running and thus if you ask it which
497 mode it is running in it will return nil.
498
499 When manually pumping the event loop care should be taken to
500 tell it to run in the correct mode. For instance, if you are
501 using it to run a modal dialog then you want to run it in
502 the modal panel run loop mode. AppKit presumably has sources
503 or timers or observers that specifically don't listen on this
504 mode. Another interesting mode is the connection reply mode.
505 This allows Cocoa to wait for a response from a distributed
506 objects message without firing off user code that may result
507 in a DO call being made thus recursing. So basically, the
508 mode is a way for Cocoa to attempt to avoid run loop recursion
509 but to allow it under certain circumstances.
510 */
511