X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/11fed901d1744019e2a4279714d172829a3c0699..e733c4ce1e24cf7e4b0b0d8362fc59aaa7a7641c:/src/osx/cocoa/evtloop.mm diff --git a/src/osx/cocoa/evtloop.mm b/src/osx/cocoa/evtloop.mm index 795a05f9b7..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 /////////////////////////////////////////////////////////////////////////////// @@ -28,9 +27,11 @@ #ifndef WX_PRECOMP #include "wx/app.h" + #include "wx/nonownedwnd.h" #endif // WX_PRECOMP #include "wx/log.h" +#include "wx/scopeguard.h" #include "wx/osx/private.h" @@ -38,66 +39,112 @@ // wxEventLoop implementation // ============================================================================ -/* -static int CalculateNSEventMaskFromEventCategory(wxEventCategory cat) -{ - NSLeftMouseDownMask | - NSLeftMouseUpMask | - NSRightMouseDownMask | - NSRightMouseUpMask = 1 << NSRightMouseUp, - NSMouseMovedMask = 1 << NSMouseMoved, - NSLeftMouseDraggedMask = 1 << NSLeftMouseDragged, - NSRightMouseDraggedMask = 1 << NSRightMouseDragged, - NSMouseEnteredMask = 1 << NSMouseEntered, - NSMouseExitedMask = 1 << NSMouseExited, - NSScrollWheelMask = 1 << NSScrollWheel, -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 - NSTabletPointMask = 1 << NSTabletPoint, - NSTabletProximityMask = 1 << NSTabletProximity, -#endif - NSOtherMouseDownMask = 1 << NSOtherMouseDown, - NSOtherMouseUpMask = 1 << NSOtherMouseUp, - NSOtherMouseDraggedMask = 1 << NSOtherMouseDragged, - - +#if 0 - NSKeyDownMask = 1 << NSKeyDown, - NSKeyUpMask = 1 << NSKeyUp, - NSFlagsChangedMask = 1 << NSFlagsChanged, +// in case we want to integrate this - NSAppKitDefinedMask = 1 << NSAppKitDefined, - NSSystemDefinedMask = 1 << NSSystemDefined, - NSApplicationDefinedMask = 1 << NSApplicationDefined, - NSPeriodicMask = 1 << NSPeriodic, - NSCursorUpdateMask = 1 << NSCursorUpdate, - - NSAnyEventMask = 0xffffffffU +static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat) +{ + // the masking system doesn't really help, as only the lowlevel UI events + // are split in a useful way, all others are way to broad + + if ( (cat | wxEVT_CATEGORY_USER_INPUT) && (cat | (~wxEVT_CATEGORY_USER_INPUT) ) ) + return NSAnyEventMask; + + NSUInteger mask = 0; + + if ( cat | wxEVT_CATEGORY_USER_INPUT ) + { + mask |= + NSLeftMouseDownMask | + NSLeftMouseUpMask | + NSRightMouseDownMask | + NSRightMouseUpMask | + NSMouseMovedMask | + NSLeftMouseDraggedMask | + NSRightMouseDraggedMask | + NSMouseEnteredMask | + NSMouseExitedMask | + NSScrollWheelMask | +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + NSTabletPointMask | + NSTabletProximityMask | +#endif + NSOtherMouseDownMask | + NSOtherMouseUpMask | + NSOtherMouseDraggedMask | + + NSKeyDownMask | + NSKeyUpMask | + NSFlagsChangedMask | +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + NSEventMaskGesture | + NSEventMaskMagnify | + NSEventMaskSwipe | + NSEventMaskRotate | + NSEventMaskBeginGesture | + NSEventMaskEndGesture | +#endif + 0; + } + + if ( cat | (~wxEVT_CATEGORY_USER_INPUT) ) + { + mask |= + NSAppKitDefinedMask | + NSSystemDefinedMask | + NSApplicationDefinedMask | + NSPeriodicMask | + NSCursorUpdateMask; + } + + return mask; } -*/ + +#endif wxGUIEventLoop::wxGUIEventLoop() { - m_sleepTime = 0.0; + m_modalSession = nil; + m_dummyWindow = nil; + m_modalNestedLevel = 0; + m_modalWindow = NULL; + m_osxLowLevelWakeUp = false; } -void wxGUIEventLoop::WakeUp() +wxGUIEventLoop::~wxGUIEventLoop() { - extern void wxMacWakeUp(); - - wxMacWakeUp(); + wxASSERT( m_modalSession == nil ); + wxASSERT( m_dummyWindow == nil ); + wxASSERT( m_modalNestedLevel == 0 ); } +//----------------------------------------------------------------------------- +// events dispatch and loop handling +//----------------------------------------------------------------------------- + +#if 0 + bool wxGUIEventLoop::Pending() const { +#if 0 + // this code doesn't reliably detect pending events + // so better return true and have the dispatch deal with it + // as otherwise we end up in a tight loop when idle events are responded + // to by RequestMore(true) wxMacAutoreleasePool autoreleasepool; - // a pointer to the event is returned if there is one, or nil if not + return [[NSApplication sharedApplication] nextEventMatchingMask: NSAnyEventMask untilDate: nil inMode: NSDefaultRunLoopMode - dequeue: NO]; + dequeue: NO] != nil; +#else + return true; +#endif } + bool wxGUIEventLoop::Dispatch() { if ( !wxTheApp ) @@ -111,11 +158,22 @@ bool wxGUIEventLoop::Dispatch() inMode:NSDefaultRunLoopMode dequeue: YES]) { + WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent(); + WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef(); + + if (wxTheApp) + wxTheApp->MacSetCurrentEvent(event, NULL); m_sleepTime = 0.0; [NSApp sendEvent: event]; + + if (wxTheApp) + wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler); } else { + if (wxTheApp) + wxTheApp->ProcessPendingEvents(); + if ( wxTheApp->ProcessIdle() ) m_sleepTime = 0.0 ; else @@ -132,56 +190,370 @@ bool wxGUIEventLoop::Dispatch() return true; } -bool wxGUIEventLoop::YieldFor(long eventsToProcess) +#endif + +int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout) { -#if wxUSE_THREADS - // Yielding from a non-gui thread needs to bail out, otherwise we end up - // possibly sending events in the thread too. - if ( !wxThread::IsMain() ) + wxMacAutoreleasePool autoreleasepool; + + if ( m_modalSession ) { - return true; + NSInteger response = [NSApp runModalSession:(NSModalSession)m_modalSession]; + + switch (response) + { + case NSRunContinuesResponse: + { + if ( [[NSApplication sharedApplication] + nextEventMatchingMask: NSAnyEventMask + untilDate: nil + inMode: NSDefaultRunLoopMode + dequeue: NO] != nil ) + return 1; + + return -1; + } + + case NSRunStoppedResponse: + case NSRunAbortedResponse: + return -1; + default: + wxFAIL_MSG("unknown response code"); + break; + } + return -1; } -#endif // wxUSE_THREADS + else + { + NSEvent *event = [NSApp + nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000] + inMode:NSDefaultRunLoopMode + dequeue: YES]; + + if ( event == nil ) + return -1; - m_isInsideYield = true; - m_eventsToProcessInsideYield = eventsToProcess; + [NSApp sendEvent: event]; -#if wxUSE_LOG - // disable log flushing from here because a call to wxYield() shouldn't - // normally result in message boxes popping up &c - wxLog::Suspend(); -#endif // wxUSE_LOG + return 1; + } +} - // process all pending events: - while ( Pending() ) - Dispatch(); +static int gs_loopNestingLevel = 0; - // it's necessary to call ProcessIdle() to update the frames sizes which - // might have been changed (it also will update other things set from - // OnUpdateUI() which is a nice (and desired) side effect) - while ( ProcessIdle() ) {} +void wxGUIEventLoop::OSXDoRun() +{ + /* + 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 wxUSE_LOG - wxLog::Resume(); -#endif // wxUSE_LOG - m_isInsideYield = false; + 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]; + } + } - return true; + // Wake up the enclosing loop so that it can check if it also needs + // to exit. + WakeUp(); } -int wxGUIEventLoop::DispatchTimeout(unsigned long timeout) +void wxGUIEventLoop::OSXDoStop() { - wxMacAutoreleasePool autoreleasepool; + // We should only stop the top level event loop. + if ( gs_loopNestingLevel <= 1 ) + { + [NSApp stop:0]; + } - NSEvent *event = [NSApp - nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000] - inMode:NSDefaultRunLoopMode - dequeue: YES]; - if ( !event ) - return -1; + // 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(); +} - [NSApp sendEvent: event]; +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]; + } +} - return true; +CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const +{ + NSRunLoop* nsloop = [NSRunLoop currentRunLoop]; + return [nsloop getCFRunLoop]; +} + + +// TODO move into a evtloop_osx.cpp + +wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow) +{ + m_modalWindow = dynamic_cast (modalWindow); + wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" ); + m_modalNativeWindow = m_modalWindow->GetWXWindow(); +} + +wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow) +{ + m_modalWindow = NULL; + wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" ); + m_modalNativeWindow = modalNativeWindow; +} + +// END move into a evtloop_osx.cpp + +void wxModalEventLoop::OSXDoRun() +{ + wxMacAutoreleasePool pool; + + // If the app hasn't started, flush the event queue + // If we don't do this, the Dock doesn't get the message that + // the app has started so will refuse to activate it. + [NSApplication sharedApplication]; + if (![NSApp isRunning]) + { + while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES]) + { + [NSApp sendEvent:event]; + } + } + + [NSApp runModalForWindow:m_modalNativeWindow]; +} + +void wxModalEventLoop::OSXDoStop() +{ + [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 ) + { + // we must show now, otherwise beginModalSessionForWindow does it but it + // also would do a centering of the window before overriding all our position + if ( !modalWindow->IsShownOnScreen() ) + modalWindow->Show(); + + wxNonOwnedWindow* now = dynamic_cast (modalWindow); + wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" ); + nsnow = now ? now->GetWXWindow() : nil; + } + else + { + NSRect r = NSMakeRect(10, 10, 0, 0); + nsnow = [NSPanel alloc]; + [nsnow initWithContentRect:r + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:YES + ]; + [nsnow orderOut:nil]; + 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"); + + wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level"); + + if ( --m_modalNestedLevel == 0 ) + { + [NSApp endModalSession:(NSModalSession)m_modalSession]; + m_modalSession = nil; + if ( m_dummyWindow ) + { + [m_dummyWindow release]; + m_dummyWindow = nil; + } + } +} + +// +// +// + +wxWindowDisabler::wxWindowDisabler(bool disable) +{ + m_modalEventLoop = NULL; + m_disabled = disable; + if ( disable ) + DoDisable(); +} + +wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip) +{ + m_disabled = true; + DoDisable(winToSkip); +} + +void wxWindowDisabler::DoDisable(wxWindow *winToSkip) +{ + // remember the top level windows which were already disabled, so that we + // don't reenable them later + m_winDisabled = NULL; + + wxWindowList::compatibility_iterator node; + for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() ) + { + wxWindow *winTop = node->GetData(); + if ( winTop == winToSkip ) + continue; + + // we don't need to disable the hidden or already disabled windows + if ( winTop->IsEnabled() && winTop->IsShown() ) + { + winTop->Disable(); + } + else + { + if ( !m_winDisabled ) + { + m_winDisabled = new wxWindowList; + } + + m_winDisabled->Append(winTop); + } + } + + m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive(); + if (m_modalEventLoop) + m_modalEventLoop->BeginModalSession(winToSkip); +} + +wxWindowDisabler::~wxWindowDisabler() +{ + if ( !m_disabled ) + return; + + if (m_modalEventLoop) + m_modalEventLoop->EndModalSession(); + + wxWindowList::compatibility_iterator node; + for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() ) + { + wxWindow *winTop = node->GetData(); + if ( !m_winDisabled || !m_winDisabled->Find(winTop) ) + { + winTop->Enable(); + } + //else: had been already disabled, don't reenable + } + + delete m_winDisabled; }