1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/cocoa/evtloop.mm
3 // Purpose: implementation of wxEventLoop for OS X
4 // Author: Vadim Zeitlin, Stefan Csomor
7 // Copyright: (c) 2006 Vadim Zeitlin <vadim@wxwindows.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
26 #include "wx/evtloop.h"
30 #include "wx/nonownedwnd.h"
34 #include "wx/scopeguard.h"
36 #include "wx/osx/private.h"
38 // ============================================================================
39 // wxEventLoop implementation
40 // ============================================================================
44 // in case we want to integrate this
46 static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat)
48 // the masking system doesn't really help, as only the lowlevel UI events
49 // are split in a useful way, all others are way to broad
51 if ( (cat | wxEVT_CATEGORY_USER_INPUT) && (cat | (~wxEVT_CATEGORY_USER_INPUT) ) )
52 return NSAnyEventMask;
56 if ( cat | wxEVT_CATEGORY_USER_INPUT )
61 NSRightMouseDownMask |
64 NSLeftMouseDraggedMask |
65 NSRightMouseDraggedMask |
69 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
71 NSTabletProximityMask |
73 NSOtherMouseDownMask |
75 NSOtherMouseDraggedMask |
80 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
85 NSEventMaskBeginGesture |
86 NSEventMaskEndGesture |
91 if ( cat | (~wxEVT_CATEGORY_USER_INPUT) )
96 NSApplicationDefinedMask |
106 wxGUIEventLoop::wxGUIEventLoop()
108 m_modalSession = nil;
110 m_modalNestedLevel = 0;
111 m_modalWindow = NULL;
112 m_osxLowLevelWakeUp = false;
115 wxGUIEventLoop::~wxGUIEventLoop()
117 wxASSERT( m_modalSession == nil );
118 wxASSERT( m_dummyWindow == nil );
119 wxASSERT( m_modalNestedLevel == 0 );
122 //-----------------------------------------------------------------------------
123 // events dispatch and loop handling
124 //-----------------------------------------------------------------------------
128 bool wxGUIEventLoop::Pending() const
131 // this code doesn't reliably detect pending events
132 // so better return true and have the dispatch deal with it
133 // as otherwise we end up in a tight loop when idle events are responded
134 // to by RequestMore(true)
135 wxMacAutoreleasePool autoreleasepool;
137 return [[NSApplication sharedApplication]
138 nextEventMatchingMask: NSAnyEventMask
140 inMode: NSDefaultRunLoopMode
148 bool wxGUIEventLoop::Dispatch()
153 wxMacAutoreleasePool autoreleasepool;
155 if(NSEvent *event = [NSApp
156 nextEventMatchingMask:NSAnyEventMask
157 untilDate:[NSDate dateWithTimeIntervalSinceNow: m_sleepTime]
158 inMode:NSDefaultRunLoopMode
161 WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent();
162 WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef();
165 wxTheApp->MacSetCurrentEvent(event, NULL);
167 [NSApp sendEvent: event];
170 wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler);
175 wxTheApp->ProcessPendingEvents();
177 if ( wxTheApp->ProcessIdle() )
195 int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
197 wxMacAutoreleasePool autoreleasepool;
199 if ( m_modalSession )
201 NSInteger response = [NSApp runModalSession:(NSModalSession)m_modalSession];
205 case NSRunContinuesResponse:
207 if ( [[NSApplication sharedApplication]
208 nextEventMatchingMask: NSAnyEventMask
210 inMode: NSDefaultRunLoopMode
211 dequeue: NO] != nil )
217 case NSRunStoppedResponse:
218 case NSRunAbortedResponse:
221 wxFAIL_MSG("unknown response code");
228 NSEvent *event = [NSApp
229 nextEventMatchingMask:NSAnyEventMask
230 untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
231 inMode:NSDefaultRunLoopMode
237 [NSApp sendEvent: event];
243 static int gs_loopNestingLevel = 0;
245 void wxGUIEventLoop::OSXDoRun()
248 In order to properly nest GUI event loops in Cocoa, it is important to
249 have [NSApp run] only as the main/outermost event loop. There are many
250 problems if [NSApp run] is used as an inner event loop. The main issue
251 is that a call to [NSApp stop] is needed to exit an [NSApp run] event
252 loop. But the [NSApp stop] has some side effects that we do not want -
253 such as if there was a modal dialog box with a modal event loop running,
254 that event loop would also get exited, and the dialog would be closed.
255 The call to [NSApp stop] would also cause the enclosing event loop to
258 webkit's webcore library uses CFRunLoopRun() for nested event loops. See
259 the log of the commit log about the change in webkit's webcore module:
260 http://www.mail-archive.com/webkit-changes@lists.webkit.org/msg07397.html
262 See here for the latest run loop that is used in webcore:
263 https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/mac/RunLoopMac.mm
265 CFRunLoopRun() was tried for the nested event loop here but it causes a
266 problem in that all user input is disabled - and there is no way to
267 re-enable it. The caller of this event loop may not want user input
268 disabled (such as synchronous wxExecute with wxEXEC_NODISABLE flag).
270 In order to have an inner event loop where user input can be enabled,
271 the old wxCocoa code that used the [NSApp nextEventMatchingMask] was
272 borrowed but changed to use blocking instead of polling. By specifying
273 'distantFuture' in 'untildate', we can have it block until the next
274 event. Then we can keep looping on each new event until m_shouldExit is
275 raised to exit the event loop.
277 gs_loopNestingLevel++;
278 wxON_BLOCK_EXIT_SET(gs_loopNestingLevel, gs_loopNestingLevel - 1);
280 while ( !m_shouldExit )
282 // By putting this inside the loop, we can drain it in each
284 wxMacAutoreleasePool autoreleasepool;
286 if ( gs_loopNestingLevel == 1 )
288 // Use -[NSApplication run] for the main run loop.
293 // We use this blocking call to [NSApp nextEventMatchingMask:...]
294 // because the other methods (such as CFRunLoopRun() and [runLoop
295 // runMode:beforeDate] were always disabling input to the windows
296 // (even if we wanted it enabled).
298 // Here are the other run loops which were tried, but always left
299 // user input disabled:
301 // [runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
303 // CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , true);
305 // Using [NSApp nextEventMatchingMask:...] would leave windows
306 // enabled if we wanted them to be, so that is why it is used.
307 NSEvent *event = [NSApp
308 nextEventMatchingMask:NSAnyEventMask
309 untilDate:[NSDate distantFuture]
310 inMode:NSDefaultRunLoopMode
313 [NSApp sendEvent: event];
316 The NSApplication documentation states that:
319 This method is invoked automatically in the main event loop
320 after each event when running in NSDefaultRunLoopMode or
321 NSModalRunLoopMode. This method is not invoked automatically
322 when running in NSEventTrackingRunLoopMode.
325 So to be safe, we also invoke it here in this event loop.
327 See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html
329 [NSApp updateWindows];
333 // Wake up the enclosing loop so that it can check if it also needs
338 void wxGUIEventLoop::OSXDoStop()
340 // We should only stop the top level event loop.
341 if ( gs_loopNestingLevel <= 1 )
346 // For the top level loop only calling stop: is not enough when called from
347 // a runloop-observer, therefore add a dummy event, to make sure the
348 // runloop gets another round. And for the nested loops we need to wake it
349 // up to notice that it should exit, so do this unconditionally.
353 void wxGUIEventLoop::WakeUp()
355 // NSEvent* cevent = [NSApp currentEvent];
356 // NSString* mode = [[NSRunLoop mainRunLoop] currentMode];
358 // when already in a mouse event handler, don't add higher level event
359 // if ( cevent != nil && [cevent type] <= NSMouseMoved && )
360 if ( m_osxLowLevelWakeUp /* [NSEventTrackingRunLoopMode isEqualToString:mode] */ )
362 // NSLog(@"event for wakeup %@ in mode %@",cevent,mode);
363 wxCFEventLoop::WakeUp();
367 wxMacAutoreleasePool autoreleasepool;
368 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
369 location:NSMakePoint(0.0, 0.0)
374 subtype:0 data1:0 data2:0];
375 [NSApp postEvent:event atStart:FALSE];
379 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
381 NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
382 return [nsloop getCFRunLoop];
386 // TODO move into a evtloop_osx.cpp
388 wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
390 m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
391 wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
392 m_modalNativeWindow = m_modalWindow->GetWXWindow();
395 wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
397 m_modalWindow = NULL;
398 wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
399 m_modalNativeWindow = modalNativeWindow;
402 // END move into a evtloop_osx.cpp
404 void wxModalEventLoop::OSXDoRun()
406 wxMacAutoreleasePool pool;
408 // If the app hasn't started, flush the event queue
409 // If we don't do this, the Dock doesn't get the message that
410 // the app has started so will refuse to activate it.
411 [NSApplication sharedApplication];
412 if (![NSApp isRunning])
414 while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
416 [NSApp sendEvent:event];
420 [NSApp runModalForWindow:m_modalNativeWindow];
423 void wxModalEventLoop::OSXDoStop()
428 void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
430 WXWindow nsnow = nil;
432 if ( m_modalNestedLevel > 0 )
434 wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window");
435 m_modalNestedLevel++;
439 m_modalWindow = modalWindow;
440 m_modalNestedLevel = 1;
444 // we must show now, otherwise beginModalSessionForWindow does it but it
445 // also would do a centering of the window before overriding all our position
446 if ( !modalWindow->IsShownOnScreen() )
449 wxNonOwnedWindow* now = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
450 wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" );
451 nsnow = now ? now->GetWXWindow() : nil;
455 NSRect r = NSMakeRect(10, 10, 0, 0);
456 nsnow = [NSPanel alloc];
457 [nsnow initWithContentRect:r
458 styleMask:NSBorderlessWindowMask
459 backing:NSBackingStoreBuffered
462 [nsnow orderOut:nil];
463 m_dummyWindow = nsnow;
465 m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
466 wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
469 void wxGUIEventLoop::EndModalSession()
471 wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
473 wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
475 if ( --m_modalNestedLevel == 0 )
477 [NSApp endModalSession:(NSModalSession)m_modalSession];
478 m_modalSession = nil;
481 [m_dummyWindow release];
491 wxWindowDisabler::wxWindowDisabler(bool disable)
493 m_modalEventLoop = NULL;
494 m_disabled = disable;
499 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
502 DoDisable(winToSkip);
505 void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
507 // remember the top level windows which were already disabled, so that we
508 // don't reenable them later
509 m_winDisabled = NULL;
511 wxWindowList::compatibility_iterator node;
512 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
514 wxWindow *winTop = node->GetData();
515 if ( winTop == winToSkip )
518 // we don't need to disable the hidden or already disabled windows
519 if ( winTop->IsEnabled() && winTop->IsShown() )
525 if ( !m_winDisabled )
527 m_winDisabled = new wxWindowList;
530 m_winDisabled->Append(winTop);
534 m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
535 if (m_modalEventLoop)
536 m_modalEventLoop->BeginModalSession(winToSkip);
539 wxWindowDisabler::~wxWindowDisabler()
544 if (m_modalEventLoop)
545 m_modalEventLoop->EndModalSession();
547 wxWindowList::compatibility_iterator node;
548 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
550 wxWindow *winTop = node->GetData();
551 if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
555 //else: had been already disabled, don't reenable
558 delete m_winDisabled;