]> git.saurik.com Git - wxWidgets.git/blobdiff - src/osx/cocoa/evtloop.mm
Fix install_name_tool calls in OS X "make install".
[wxWidgets.git] / src / osx / cocoa / evtloop.mm
index ed35319658eb8af7a1682a4b722ba92c49bd641a..2a27139d35c1052661a0376bdcf8bdfc0dc08ef1 100644 (file)
@@ -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 <vadim@wxwindows.org>
 // Licence:     wxWindows licence
 ///////////////////////////////////////////////////////////////////////////////
@@ -32,6 +31,7 @@
 #endif // WX_PRECOMP
 
 #include "wx/log.h"
+#include "wx/scopeguard.h"
 
 #include "wx/osx/private.h"
 
 // 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
 
+// in case we want to integrate this
 
+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;
 
-       NSKeyDownMask                   = 1 << NSKeyDown,
-       NSKeyUpMask                     = 1 << NSKeyUp,
-       NSFlagsChangedMask              = 1 << NSFlagsChanged,
+    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;
+}
 
-       NSAppKitDefinedMask             = 1 << NSAppKitDefined,
-       NSSystemDefinedMask             = 1 << NSSystemDefined,
-       NSApplicationDefinedMask        = 1 << NSApplicationDefined,
-       NSPeriodicMask                  = 1 << NSPeriodic,
-       NSCursorUpdateMask              = 1 << NSCursorUpdate,
+#endif
 
-       NSAnyEventMask                  = 0xffffffffU
+wxGUIEventLoop::wxGUIEventLoop()
+{
+    m_modalSession = nil;
+    m_dummyWindow = nil;
+    m_modalNestedLevel = 0;
+    m_modalWindow = NULL;
+    m_osxLowLevelWakeUp = false;
 }
-*/
 
-wxGUIEventLoop::wxGUIEventLoop()
+wxGUIEventLoop::~wxGUIEventLoop()
 {
+    wxASSERT( m_modalSession == nil );
+    wxASSERT( m_dummyWindow == nil );
+    wxASSERT( m_modalNestedLevel == 0 );
 }
 
 //-----------------------------------------------------------------------------
@@ -105,6 +144,7 @@ bool wxGUIEventLoop::Pending() const
 #endif
 }
 
+
 bool wxGUIEventLoop::Dispatch()
 {
     if ( !wxTheApp )
@@ -118,10 +158,16 @@ 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
     {
@@ -150,29 +196,184 @@ int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
 {
     wxMacAutoreleasePool autoreleasepool;
 
-    NSEvent *event = [NSApp
-                nextEventMatchingMask:NSAnyEventMask
-                untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
-                inMode:NSDefaultRunLoopMode
-                dequeue: YES];
-    
-    if ( event == nil )
+    if ( m_modalSession )
+    {
+        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;
+    }
+    else 
+    {        
+        NSEvent *event = [NSApp
+                    nextEventMatchingMask:NSAnyEventMask
+                    untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
+                    inMode:NSDefaultRunLoopMode
+                    dequeue: YES];
+        
+        if ( event == nil )
+            return -1;
 
-    [NSApp sendEvent: event];
+        [NSApp sendEvent: event];
 
-    return 1;
+        return 1;
+    }
 }
 
-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];
+    // 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];
+    }
 }
 
 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
@@ -200,7 +401,7 @@ wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
 
 // END move into a evtloop_osx.cpp
 
-void wxModalEventLoop::DoRun()
+void wxModalEventLoop::OSXDoRun()
 {
     wxMacAutoreleasePool pool;
 
@@ -219,8 +420,140 @@ void wxModalEventLoop::DoRun()
     [NSApp runModalForWindow:m_modalNativeWindow];
 }
 
-void wxModalEventLoop::DoStop()
+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<wxNonOwnedWindow*> (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()
 {
-    [NSApp stopModal];
+    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;
+}