X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e9e8b38179b147dd04f5c19af7e56a94342e93a7..3d777efedc1e05bd6c2a7c34a00a65895b62bb13:/src/osx/cocoa/evtloop.mm diff --git a/src/osx/cocoa/evtloop.mm b/src/osx/cocoa/evtloop.mm index dea4cc832e..2a27139d35 100644 --- a/src/osx/cocoa/evtloop.mm +++ b/src/osx/cocoa/evtloop.mm @@ -4,7 +4,6 @@ // Author: Vadim Zeitlin, Stefan Csomor // Modified by: // Created: 2006-01-12 -// RCS-ID: $Id: evtloop.cpp 54845 2008-07-30 14:52:41Z SC $ // Copyright: (c) 2006 Vadim Zeitlin // Licence: wxWindows licence /////////////////////////////////////////////////////////////////////////////// @@ -32,6 +31,7 @@ #endif // WX_PRECOMP #include "wx/log.h" +#include "wx/scopeguard.h" #include "wx/osx/private.h" @@ -107,12 +107,16 @@ wxGUIEventLoop::wxGUIEventLoop() { m_modalSession = nil; m_dummyWindow = nil; + m_modalNestedLevel = 0; + m_modalWindow = NULL; + m_osxLowLevelWakeUp = false; } wxGUIEventLoop::~wxGUIEventLoop() { wxASSERT( m_modalSession == nil ); wxASSERT( m_dummyWindow == nil ); + wxASSERT( m_modalNestedLevel == 0 ); } //----------------------------------------------------------------------------- @@ -140,6 +144,7 @@ bool wxGUIEventLoop::Pending() const #endif } + bool wxGUIEventLoop::Dispatch() { if ( !wxTheApp ) @@ -214,9 +219,9 @@ int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout) return -1; default: wxFAIL_MSG("unknown response code"); - return -1; break; } + return -1; } else { @@ -235,25 +240,140 @@ int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout) } } -void wxGUIEventLoop::DoRun() +static int gs_loopNestingLevel = 0; + +void wxGUIEventLoop::OSXDoRun() { - wxMacAutoreleasePool autoreleasepool; - [NSApp run]; + /* + In order to properly nest GUI event loops in Cocoa, it is important to + have [NSApp run] only as the main/outermost event loop. There are many + problems if [NSApp run] is used as an inner event loop. The main issue + is that a call to [NSApp stop] is needed to exit an [NSApp run] event + loop. But the [NSApp stop] has some side effects that we do not want - + such as if there was a modal dialog box with a modal event loop running, + that event loop would also get exited, and the dialog would be closed. + The call to [NSApp stop] would also cause the enclosing event loop to + exit as well. + + webkit's webcore library uses CFRunLoopRun() for nested event loops. See + the log of the commit log about the change in webkit's webcore module: + http://www.mail-archive.com/webkit-changes@lists.webkit.org/msg07397.html + + See here for the latest run loop that is used in webcore: + https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/mac/RunLoopMac.mm + + CFRunLoopRun() was tried for the nested event loop here but it causes a + problem in that all user input is disabled - and there is no way to + re-enable it. The caller of this event loop may not want user input + disabled (such as synchronous wxExecute with wxEXEC_NODISABLE flag). + + In order to have an inner event loop where user input can be enabled, + the old wxCocoa code that used the [NSApp nextEventMatchingMask] was + borrowed but changed to use blocking instead of polling. By specifying + 'distantFuture' in 'untildate', we can have it block until the next + event. Then we can keep looping on each new event until m_shouldExit is + raised to exit the event loop. + */ + gs_loopNestingLevel++; + wxON_BLOCK_EXIT_SET(gs_loopNestingLevel, gs_loopNestingLevel - 1); + + while ( !m_shouldExit ) + { + // By putting this inside the loop, we can drain it in each + // loop iteration. + wxMacAutoreleasePool autoreleasepool; + + if ( gs_loopNestingLevel == 1 ) + { + // Use -[NSApplication run] for the main run loop. + [NSApp run]; + } + else + { + // We use this blocking call to [NSApp nextEventMatchingMask:...] + // because the other methods (such as CFRunLoopRun() and [runLoop + // runMode:beforeDate] were always disabling input to the windows + // (even if we wanted it enabled). + // + // Here are the other run loops which were tried, but always left + // user input disabled: + // + // [runLoop runMode:NSDefaultRunLoopMode beforeDate:date]; + // CFRunLoopRun(); + // CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , true); + // + // Using [NSApp nextEventMatchingMask:...] would leave windows + // enabled if we wanted them to be, so that is why it is used. + NSEvent *event = [NSApp + nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue: YES]; + + [NSApp sendEvent: event]; + + /** + The NSApplication documentation states that: + + " + This method is invoked automatically in the main event loop + after each event when running in NSDefaultRunLoopMode or + NSModalRunLoopMode. This method is not invoked automatically + when running in NSEventTrackingRunLoopMode. + " + + So to be safe, we also invoke it here in this event loop. + + See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html + */ + [NSApp updateWindows]; + } + } + + // Wake up the enclosing loop so that it can check if it also needs + // to exit. + WakeUp(); } -void wxGUIEventLoop::DoStop() +void wxGUIEventLoop::OSXDoStop() { - [NSApp stop:0]; - // only calling stop: is not enough when called from a runloop-observer, - // therefore add a dummy event, to make sure the runloop gets another round - NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined + // We should only stop the top level event loop. + if ( gs_loopNestingLevel <= 1 ) + { + [NSApp stop:0]; + } + + // For the top level loop only calling stop: is not enough when called from + // a runloop-observer, therefore add a dummy event, to make sure the + // runloop gets another round. And for the nested loops we need to wake it + // up to notice that it should exit, so do this unconditionally. + WakeUp(); +} + +void wxGUIEventLoop::WakeUp() +{ + // NSEvent* cevent = [NSApp currentEvent]; + // NSString* mode = [[NSRunLoop mainRunLoop] currentMode]; + + // when already in a mouse event handler, don't add higher level event + // if ( cevent != nil && [cevent type] <= NSMouseMoved && ) + if ( m_osxLowLevelWakeUp /* [NSEventTrackingRunLoopMode isEqualToString:mode] */ ) + { + // NSLog(@"event for wakeup %@ in mode %@",cevent,mode); + wxCFEventLoop::WakeUp(); + } + else + { + wxMacAutoreleasePool autoreleasepool; + NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0.0, 0.0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil subtype:0 data1:0 data2:0]; - [NSApp postEvent:event atStart:FALSE]; + [NSApp postEvent:event atStart:FALSE]; + } } CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const @@ -281,7 +401,7 @@ wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow) // END move into a evtloop_osx.cpp -void wxModalEventLoop::DoRun() +void wxModalEventLoop::OSXDoRun() { wxMacAutoreleasePool pool; @@ -300,14 +420,24 @@ void wxModalEventLoop::DoRun() [NSApp runModalForWindow:m_modalNativeWindow]; } -void wxModalEventLoop::DoStop() +void wxModalEventLoop::OSXDoStop() { - [NSApp stopModal]; + [NSApp abortModal]; } void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow ) { WXWindow nsnow = nil; + + if ( m_modalNestedLevel > 0 ) + { + wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window"); + m_modalNestedLevel++; + return; + } + + m_modalWindow = modalWindow; + m_modalNestedLevel = 1; if ( modalWindow ) { @@ -333,17 +463,24 @@ void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow ) m_dummyWindow = nsnow; } m_modalSession = [NSApp beginModalSessionForWindow:nsnow]; + wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started"); } void wxGUIEventLoop::EndModalSession() { wxASSERT_MSG(m_modalSession != NULL, "no modal session active"); - [NSApp endModalSession:(NSModalSession)m_modalSession]; - m_modalSession = nil; - if ( m_dummyWindow ) + + wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level"); + + if ( --m_modalNestedLevel == 0 ) { - [m_dummyWindow release]; - m_dummyWindow = nil; + [NSApp endModalSession:(NSModalSession)m_modalSession]; + m_modalSession = nil; + if ( m_dummyWindow ) + { + [m_dummyWindow release]; + m_dummyWindow = nil; + } } } @@ -395,7 +532,8 @@ void wxWindowDisabler::DoDisable(wxWindow *winToSkip) } m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive(); - m_modalEventLoop->BeginModalSession(winToSkip); + if (m_modalEventLoop) + m_modalEventLoop->BeginModalSession(winToSkip); } wxWindowDisabler::~wxWindowDisabler() @@ -403,7 +541,8 @@ wxWindowDisabler::~wxWindowDisabler() if ( !m_disabled ) return; - m_modalEventLoop->EndModalSession(); + if (m_modalEventLoop) + m_modalEventLoop->EndModalSession(); wxWindowList::compatibility_iterator node; for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() ) @@ -418,4 +557,3 @@ wxWindowDisabler::~wxWindowDisabler() delete m_winDisabled; } -