1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/cocoa/evtloop.mm
3 // Purpose: implementation of wxEventLoop for OS X
4 // Author: Vadim Zeitlin, Stefan Csomor
8 // Copyright: (c) 2006 Vadim Zeitlin <vadim@wxwindows.org>
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // for compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
27 #include "wx/evtloop.h"
31 #include "wx/nonownedwnd.h"
35 #include "wx/scopeguard.h"
37 #include "wx/osx/private.h"
39 // ============================================================================
40 // wxEventLoop implementation
41 // ============================================================================
45 // in case we want to integrate this
47 static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat)
49 // the masking system doesn't really help, as only the lowlevel UI events
50 // are split in a useful way, all others are way to broad
52 if ( (cat | wxEVT_CATEGORY_USER_INPUT) && (cat | (~wxEVT_CATEGORY_USER_INPUT) ) )
53 return NSAnyEventMask;
57 if ( cat | wxEVT_CATEGORY_USER_INPUT )
62 NSRightMouseDownMask |
65 NSLeftMouseDraggedMask |
66 NSRightMouseDraggedMask |
70 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
72 NSTabletProximityMask |
74 NSOtherMouseDownMask |
76 NSOtherMouseDraggedMask |
81 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
86 NSEventMaskBeginGesture |
87 NSEventMaskEndGesture |
92 if ( cat | (~wxEVT_CATEGORY_USER_INPUT) )
97 NSApplicationDefinedMask |
107 wxGUIEventLoop::wxGUIEventLoop()
109 m_modalSession = nil;
111 m_modalNestedLevel = 0;
112 m_modalWindow = NULL;
113 m_osxLowLevelWakeUp = false;
116 wxGUIEventLoop::~wxGUIEventLoop()
118 wxASSERT( m_modalSession == nil );
119 wxASSERT( m_dummyWindow == nil );
120 wxASSERT( m_modalNestedLevel == 0 );
123 //-----------------------------------------------------------------------------
124 // events dispatch and loop handling
125 //-----------------------------------------------------------------------------
129 bool wxGUIEventLoop::Pending() const
132 // this code doesn't reliably detect pending events
133 // so better return true and have the dispatch deal with it
134 // as otherwise we end up in a tight loop when idle events are responded
135 // to by RequestMore(true)
136 wxMacAutoreleasePool autoreleasepool;
138 return [[NSApplication sharedApplication]
139 nextEventMatchingMask: NSAnyEventMask
141 inMode: NSDefaultRunLoopMode
149 bool wxGUIEventLoop::Dispatch()
154 wxMacAutoreleasePool autoreleasepool;
156 if(NSEvent *event = [NSApp
157 nextEventMatchingMask:NSAnyEventMask
158 untilDate:[NSDate dateWithTimeIntervalSinceNow: m_sleepTime]
159 inMode:NSDefaultRunLoopMode
162 WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent();
163 WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef();
166 wxTheApp->MacSetCurrentEvent(event, NULL);
168 [NSApp sendEvent: event];
171 wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler);
176 wxTheApp->ProcessPendingEvents();
178 if ( wxTheApp->ProcessIdle() )
196 int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
198 wxMacAutoreleasePool autoreleasepool;
200 if ( m_modalSession )
202 NSInteger response = [NSApp runModalSession:(NSModalSession)m_modalSession];
206 case NSRunContinuesResponse:
208 if ( [[NSApplication sharedApplication]
209 nextEventMatchingMask: NSAnyEventMask
211 inMode: NSDefaultRunLoopMode
212 dequeue: NO] != nil )
218 case NSRunStoppedResponse:
219 case NSRunAbortedResponse:
222 wxFAIL_MSG("unknown response code");
229 NSEvent *event = [NSApp
230 nextEventMatchingMask:NSAnyEventMask
231 untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
232 inMode:NSDefaultRunLoopMode
238 [NSApp sendEvent: event];
244 static int gs_loopNestingLevel = 0;
246 void wxGUIEventLoop::OSXDoRun()
249 In order to properly nest GUI event loops in Cocoa, it is important to
250 have [NSApp run] only as the main/outermost event loop. There are many
251 problems if [NSApp run] is used as an inner event loop. The main issue
252 is that a call to [NSApp stop] is needed to exit an [NSApp run] event
253 loop. But the [NSApp stop] has some side effects that we do not want -
254 such as if there was a modal dialog box with a modal event loop running,
255 that event loop would also get exited, and the dialog would be closed.
256 The call to [NSApp stop] would also cause the enclosing event loop to
259 webkit's webcore library uses CFRunLoopRun() for nested event loops. See
260 the log of the commit log about the change in webkit's webcore module:
261 http://www.mail-archive.com/webkit-changes@lists.webkit.org/msg07397.html
263 See here for the latest run loop that is used in webcore:
264 https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/mac/RunLoopMac.mm
266 CFRunLoopRun() was tried for the nested event loop here but it causes a
267 problem in that all user input is disabled - and there is no way to
268 re-enable it. The caller of this event loop may not want user input
269 disabled (such as synchronous wxExecute with wxEXEC_NODISABLE flag).
271 In order to have an inner event loop where user input can be enabled,
272 the old wxCocoa code that used the [NSApp nextEventMatchingMask] was
273 borrowed but changed to use blocking instead of polling. By specifying
274 'distantFuture' in 'untildate', we can have it block until the next
275 event. Then we can keep looping on each new event until m_shouldExit is
276 raised to exit the event loop.
278 gs_loopNestingLevel++;
279 wxON_BLOCK_EXIT_SET(gs_loopNestingLevel, gs_loopNestingLevel - 1);
281 while ( !m_shouldExit )
283 // By putting this inside the loop, we can drain it in each
285 wxMacAutoreleasePool autoreleasepool;
287 if ( gs_loopNestingLevel == 1 )
289 // Use -[NSApplication run] for the main run loop.
294 // We use this blocking call to [NSApp nextEventMatchingMask:...]
295 // because the other methods (such as CFRunLoopRun() and [runLoop
296 // runMode:beforeDate] were always disabling input to the windows
297 // (even if we wanted it enabled).
299 // Here are the other run loops which were tried, but always left
300 // user input disabled:
302 // [runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
304 // CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , true);
306 // Using [NSApp nextEventMatchingMask:...] would leave windows
307 // enabled if we wanted them to be, so that is why it is used.
308 NSEvent *event = [NSApp
309 nextEventMatchingMask:NSAnyEventMask
310 untilDate:[NSDate distantFuture]
311 inMode:NSDefaultRunLoopMode
314 [NSApp sendEvent: event];
317 The NSApplication documentation states that:
320 This method is invoked automatically in the main event loop
321 after each event when running in NSDefaultRunLoopMode or
322 NSModalRunLoopMode. This method is not invoked automatically
323 when running in NSEventTrackingRunLoopMode.
326 So to be safe, we also invoke it here in this event loop.
328 See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html
330 [NSApp updateWindows];
334 // Wake up the enclosing loop so that it can check if it also needs
339 void wxGUIEventLoop::OSXDoStop()
341 // We should only stop the top level event loop.
342 if ( gs_loopNestingLevel <= 1 )
347 // For the top level loop only calling stop: is not enough when called from
348 // a runloop-observer, therefore add a dummy event, to make sure the
349 // runloop gets another round. And for the nested loops we need to wake it
350 // up to notice that it should exit, so do this unconditionally.
354 void wxGUIEventLoop::WakeUp()
356 // NSEvent* cevent = [NSApp currentEvent];
357 // NSString* mode = [[NSRunLoop mainRunLoop] currentMode];
359 // when already in a mouse event handler, don't add higher level event
360 // if ( cevent != nil && [cevent type] <= NSMouseMoved && )
361 if ( m_osxLowLevelWakeUp /* [NSEventTrackingRunLoopMode isEqualToString:mode] */ )
363 // NSLog(@"event for wakeup %@ in mode %@",cevent,mode);
364 wxCFEventLoop::WakeUp();
368 wxMacAutoreleasePool autoreleasepool;
369 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
370 location:NSMakePoint(0.0, 0.0)
375 subtype:0 data1:0 data2:0];
376 [NSApp postEvent:event atStart:FALSE];
380 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
382 NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
383 return [nsloop getCFRunLoop];
387 // TODO move into a evtloop_osx.cpp
389 wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
391 m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
392 wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
393 m_modalNativeWindow = m_modalWindow->GetWXWindow();
396 wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
398 m_modalWindow = NULL;
399 wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
400 m_modalNativeWindow = modalNativeWindow;
403 // END move into a evtloop_osx.cpp
405 void wxModalEventLoop::OSXDoRun()
407 wxMacAutoreleasePool pool;
409 // If the app hasn't started, flush the event queue
410 // If we don't do this, the Dock doesn't get the message that
411 // the app has started so will refuse to activate it.
412 [NSApplication sharedApplication];
413 if (![NSApp isRunning])
415 while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
417 [NSApp sendEvent:event];
421 [NSApp runModalForWindow:m_modalNativeWindow];
424 void wxModalEventLoop::OSXDoStop()
429 void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
431 WXWindow nsnow = nil;
433 if ( m_modalNestedLevel > 0 )
435 wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window");
436 m_modalNestedLevel++;
440 m_modalWindow = modalWindow;
441 m_modalNestedLevel = 1;
445 // we must show now, otherwise beginModalSessionForWindow does it but it
446 // also would do a centering of the window before overriding all our position
447 if ( !modalWindow->IsShownOnScreen() )
450 wxNonOwnedWindow* now = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
451 wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" );
452 nsnow = now ? now->GetWXWindow() : nil;
456 NSRect r = NSMakeRect(10, 10, 0, 0);
457 nsnow = [NSPanel alloc];
458 [nsnow initWithContentRect:r
459 styleMask:NSBorderlessWindowMask
460 backing:NSBackingStoreBuffered
463 [nsnow orderOut:nil];
464 m_dummyWindow = nsnow;
466 m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
467 wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
470 void wxGUIEventLoop::EndModalSession()
472 wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
474 wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
476 if ( --m_modalNestedLevel == 0 )
478 [NSApp endModalSession:(NSModalSession)m_modalSession];
479 m_modalSession = nil;
482 [m_dummyWindow release];
492 wxWindowDisabler::wxWindowDisabler(bool disable)
494 m_modalEventLoop = NULL;
495 m_disabled = disable;
500 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
503 DoDisable(winToSkip);
506 void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
508 // remember the top level windows which were already disabled, so that we
509 // don't reenable them later
510 m_winDisabled = NULL;
512 wxWindowList::compatibility_iterator node;
513 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
515 wxWindow *winTop = node->GetData();
516 if ( winTop == winToSkip )
519 // we don't need to disable the hidden or already disabled windows
520 if ( winTop->IsEnabled() && winTop->IsShown() )
526 if ( !m_winDisabled )
528 m_winDisabled = new wxWindowList;
531 m_winDisabled->Append(winTop);
535 m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
536 if (m_modalEventLoop)
537 m_modalEventLoop->BeginModalSession(winToSkip);
540 wxWindowDisabler::~wxWindowDisabler()
545 if (m_modalEventLoop)
546 m_modalEventLoop->EndModalSession();
548 wxWindowList::compatibility_iterator node;
549 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
551 wxWindow *winTop = node->GetData();
552 if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
556 //else: had been already disabled, don't reenable
559 delete m_winDisabled;