PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/ExtLibs/wxWidgets/src/cocoa/app.mm

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