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;