// 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 <vadim@wxwindows.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#endif // WX_PRECOMP
#include "wx/log.h"
+#include "wx/scopeguard.h"
#include "wx/osx/private.h"
{
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 );
}
//-----------------------------------------------------------------------------
#endif
}
+
bool wxGUIEventLoop::Dispatch()
{
if ( !wxTheApp )
}
}
-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::OSXDoStop()
+{
+ // 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::DoStop()
+void wxGUIEventLoop::WakeUp()
{
- [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
+ // 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
// END move into a evtloop_osx.cpp
-void wxModalEventLoop::DoRun()
+void wxModalEventLoop::OSXDoRun()
{
wxMacAutoreleasePool pool;
[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 )
{
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;
+ }
}
}
}
m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
- m_modalEventLoop->BeginModalSession(winToSkip);
+ if (m_modalEventLoop)
+ m_modalEventLoop->BeginModalSession(winToSkip);
}
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() )
delete m_winDisabled;
}
-