]> git.saurik.com Git - wxWidgets.git/blob - src/osx/cocoa/evtloop.mm
OSX adaptions
[wxWidgets.git] / src / osx / cocoa / evtloop.mm
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/cocoa/evtloop.mm
3 // Purpose: implementation of wxEventLoop for OS X
4 // Author: Vadim Zeitlin, Stefan Csomor
5 // Modified by:
6 // Created: 2006-01-12
7 // Copyright: (c) 2006 Vadim Zeitlin <vadim@wxwindows.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #include "wx/evtloop.h"
27
28 #ifndef WX_PRECOMP
29 #include "wx/app.h"
30 #include "wx/nonownedwnd.h"
31 #endif // WX_PRECOMP
32
33 #include "wx/log.h"
34 #include "wx/scopeguard.h"
35
36 #include "wx/osx/private.h"
37
38 // ============================================================================
39 // wxEventLoop implementation
40 // ============================================================================
41
42 #if 0
43
44 // in case we want to integrate this
45
46 static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat)
47 {
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
50
51 if ( (cat | wxEVT_CATEGORY_USER_INPUT) && (cat | (~wxEVT_CATEGORY_USER_INPUT) ) )
52 return NSAnyEventMask;
53
54 NSUInteger mask = 0;
55
56 if ( cat | wxEVT_CATEGORY_USER_INPUT )
57 {
58 mask |=
59 NSLeftMouseDownMask |
60 NSLeftMouseUpMask |
61 NSRightMouseDownMask |
62 NSRightMouseUpMask |
63 NSMouseMovedMask |
64 NSLeftMouseDraggedMask |
65 NSRightMouseDraggedMask |
66 NSMouseEnteredMask |
67 NSMouseExitedMask |
68 NSScrollWheelMask |
69 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
70 NSTabletPointMask |
71 NSTabletProximityMask |
72 #endif
73 NSOtherMouseDownMask |
74 NSOtherMouseUpMask |
75 NSOtherMouseDraggedMask |
76
77 NSKeyDownMask |
78 NSKeyUpMask |
79 NSFlagsChangedMask |
80 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
81 NSEventMaskGesture |
82 NSEventMaskMagnify |
83 NSEventMaskSwipe |
84 NSEventMaskRotate |
85 NSEventMaskBeginGesture |
86 NSEventMaskEndGesture |
87 #endif
88 0;
89 }
90
91 if ( cat | (~wxEVT_CATEGORY_USER_INPUT) )
92 {
93 mask |=
94 NSAppKitDefinedMask |
95 NSSystemDefinedMask |
96 NSApplicationDefinedMask |
97 NSPeriodicMask |
98 NSCursorUpdateMask;
99 }
100
101 return mask;
102 }
103
104 #endif
105
106 wxGUIEventLoop::wxGUIEventLoop()
107 {
108 m_modalSession = nil;
109 m_dummyWindow = nil;
110 m_modalNestedLevel = 0;
111 m_modalWindow = NULL;
112 m_osxLowLevelWakeUp = false;
113 }
114
115 wxGUIEventLoop::~wxGUIEventLoop()
116 {
117 wxASSERT( m_modalSession == nil );
118 wxASSERT( m_dummyWindow == nil );
119 wxASSERT( m_modalNestedLevel == 0 );
120 }
121
122 //-----------------------------------------------------------------------------
123 // events dispatch and loop handling
124 //-----------------------------------------------------------------------------
125
126 #if 0
127
128 bool wxGUIEventLoop::Pending() const
129 {
130 #if 0
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;
136
137 return [[NSApplication sharedApplication]
138 nextEventMatchingMask: NSAnyEventMask
139 untilDate: nil
140 inMode: NSDefaultRunLoopMode
141 dequeue: NO] != nil;
142 #else
143 return true;
144 #endif
145 }
146
147
148 bool wxGUIEventLoop::Dispatch()
149 {
150 if ( !wxTheApp )
151 return false;
152
153 wxMacAutoreleasePool autoreleasepool;
154
155 if(NSEvent *event = [NSApp
156 nextEventMatchingMask:NSAnyEventMask
157 untilDate:[NSDate dateWithTimeIntervalSinceNow: m_sleepTime]
158 inMode:NSDefaultRunLoopMode
159 dequeue: YES])
160 {
161 WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent();
162 WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef();
163
164 if (wxTheApp)
165 wxTheApp->MacSetCurrentEvent(event, NULL);
166 m_sleepTime = 0.0;
167 [NSApp sendEvent: event];
168
169 if (wxTheApp)
170 wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler);
171 }
172 else
173 {
174 if (wxTheApp)
175 wxTheApp->ProcessPendingEvents();
176
177 if ( wxTheApp->ProcessIdle() )
178 m_sleepTime = 0.0 ;
179 else
180 {
181 m_sleepTime = 1.0;
182 #if wxUSE_THREADS
183 wxMutexGuiLeave();
184 wxMilliSleep(20);
185 wxMutexGuiEnter();
186 #endif
187 }
188 }
189
190 return true;
191 }
192
193 #endif
194
195 int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
196 {
197 wxMacAutoreleasePool autoreleasepool;
198
199 if ( m_modalSession )
200 {
201 NSInteger response = [NSApp runModalSession:(NSModalSession)m_modalSession];
202
203 switch (response)
204 {
205 case NSRunContinuesResponse:
206 {
207 if ( [[NSApplication sharedApplication]
208 nextEventMatchingMask: NSAnyEventMask
209 untilDate: nil
210 inMode: NSDefaultRunLoopMode
211 dequeue: NO] != nil )
212 return 1;
213
214 return -1;
215 }
216
217 case NSRunStoppedResponse:
218 case NSRunAbortedResponse:
219 return -1;
220 default:
221 wxFAIL_MSG("unknown response code");
222 break;
223 }
224 return -1;
225 }
226 else
227 {
228 NSEvent *event = [NSApp
229 nextEventMatchingMask:NSAnyEventMask
230 untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
231 inMode:NSDefaultRunLoopMode
232 dequeue: YES];
233
234 if ( event == nil )
235 return -1;
236
237 [NSApp sendEvent: event];
238
239 return 1;
240 }
241 }
242
243 static int gs_loopNestingLevel = 0;
244
245 int wxGUIEventLoop::Run()
246 {
247 // because we are using native callbacks for notifying about entering and exiting
248 // the main event loop, we must this leave out here
249
250 // event loops are not recursive, you need to create another loop!
251 wxCHECK_MSG( !IsInsideRun(), -1, wxT("can't reenter a message loop") );
252
253 // We might be called again, after a previous call to ScheduleExit(), so
254 // reset this flag.
255 m_shouldExit = false;
256
257 // Set this variable to true for the duration of this method.
258 m_isInsideRun = true;
259 wxON_BLOCK_EXIT_SET(m_isInsideRun, false);
260
261 // Finally really run the loop.
262 return DoRun();
263 }
264
265 void wxGUIEventLoop::OSXDoRun()
266 {
267 /*
268 In order to properly nest GUI event loops in Cocoa, it is important to
269 have [NSApp run] only as the main/outermost event loop. There are many
270 problems if [NSApp run] is used as an inner event loop. The main issue
271 is that a call to [NSApp stop] is needed to exit an [NSApp run] event
272 loop. But the [NSApp stop] has some side effects that we do not want -
273 such as if there was a modal dialog box with a modal event loop running,
274 that event loop would also get exited, and the dialog would be closed.
275 The call to [NSApp stop] would also cause the enclosing event loop to
276 exit as well.
277
278 webkit's webcore library uses CFRunLoopRun() for nested event loops. See
279 the log of the commit log about the change in webkit's webcore module:
280 http://www.mail-archive.com/webkit-changes@lists.webkit.org/msg07397.html
281
282 See here for the latest run loop that is used in webcore:
283 https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/mac/RunLoopMac.mm
284
285 CFRunLoopRun() was tried for the nested event loop here but it causes a
286 problem in that all user input is disabled - and there is no way to
287 re-enable it. The caller of this event loop may not want user input
288 disabled (such as synchronous wxExecute with wxEXEC_NODISABLE flag).
289
290 In order to have an inner event loop where user input can be enabled,
291 the old wxCocoa code that used the [NSApp nextEventMatchingMask] was
292 borrowed but changed to use blocking instead of polling. By specifying
293 'distantFuture' in 'untildate', we can have it block until the next
294 event. Then we can keep looping on each new event until m_shouldExit is
295 raised to exit the event loop.
296 */
297 gs_loopNestingLevel++;
298 wxON_BLOCK_EXIT_SET(gs_loopNestingLevel, gs_loopNestingLevel - 1);
299
300 while ( !m_shouldExit )
301 {
302 // By putting this inside the loop, we can drain it in each
303 // loop iteration.
304 wxMacAutoreleasePool autoreleasepool;
305
306 if ( gs_loopNestingLevel == 1 )
307 {
308 // Use -[NSApplication run] for the main run loop.
309 [NSApp run];
310 }
311 else
312 {
313 // We use this blocking call to [NSApp nextEventMatchingMask:...]
314 // because the other methods (such as CFRunLoopRun() and [runLoop
315 // runMode:beforeDate] were always disabling input to the windows
316 // (even if we wanted it enabled).
317 //
318 // Here are the other run loops which were tried, but always left
319 // user input disabled:
320 //
321 // [runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
322 // CFRunLoopRun();
323 // CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , true);
324 //
325 // Using [NSApp nextEventMatchingMask:...] would leave windows
326 // enabled if we wanted them to be, so that is why it is used.
327 NSEvent *event = [NSApp
328 nextEventMatchingMask:NSAnyEventMask
329 untilDate:[NSDate distantFuture]
330 inMode:NSDefaultRunLoopMode
331 dequeue: YES];
332
333 [NSApp sendEvent: event];
334
335 /**
336 The NSApplication documentation states that:
337
338 "
339 This method is invoked automatically in the main event loop
340 after each event when running in NSDefaultRunLoopMode or
341 NSModalRunLoopMode. This method is not invoked automatically
342 when running in NSEventTrackingRunLoopMode.
343 "
344
345 So to be safe, we also invoke it here in this event loop.
346
347 See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html
348 */
349 [NSApp updateWindows];
350 }
351 }
352
353 // Wake up the enclosing loop so that it can check if it also needs
354 // to exit.
355 WakeUp();
356 }
357
358 void wxGUIEventLoop::OSXDoStop()
359 {
360 // We should only stop the top level event loop.
361 if ( gs_loopNestingLevel <= 1 )
362 {
363 // using terminate support all native notifications
364 [NSApp terminate:0];
365 }
366
367 // For the top level loop only calling stop: is not enough when called from
368 // a runloop-observer, therefore add a dummy event, to make sure the
369 // runloop gets another round. And for the nested loops we need to wake it
370 // up to notice that it should exit, so do this unconditionally.
371 WakeUp();
372 }
373
374 void wxGUIEventLoop::OSXOnWillTerminate()
375 {
376 OnExit();
377 }
378
379 void wxGUIEventLoop::WakeUp()
380 {
381 // NSEvent* cevent = [NSApp currentEvent];
382 // NSString* mode = [[NSRunLoop mainRunLoop] currentMode];
383
384 // when already in a mouse event handler, don't add higher level event
385 // if ( cevent != nil && [cevent type] <= NSMouseMoved && )
386 if ( m_osxLowLevelWakeUp /* [NSEventTrackingRunLoopMode isEqualToString:mode] */ )
387 {
388 // NSLog(@"event for wakeup %@ in mode %@",cevent,mode);
389 wxCFEventLoop::WakeUp();
390 }
391 else
392 {
393 wxMacAutoreleasePool autoreleasepool;
394 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
395 location:NSMakePoint(0.0, 0.0)
396 modifierFlags:0
397 timestamp:0
398 windowNumber:0
399 context:nil
400 subtype:0 data1:0 data2:0];
401 [NSApp postEvent:event atStart:FALSE];
402 }
403 }
404
405 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
406 {
407 NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
408 return [nsloop getCFRunLoop];
409 }
410
411
412 // TODO move into a evtloop_osx.cpp
413
414 wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
415 {
416 m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
417 wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
418 m_modalNativeWindow = m_modalWindow->GetWXWindow();
419 }
420
421 wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
422 {
423 m_modalWindow = NULL;
424 wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
425 m_modalNativeWindow = modalNativeWindow;
426 }
427
428 // END move into a evtloop_osx.cpp
429
430 void wxModalEventLoop::OSXDoRun()
431 {
432 wxMacAutoreleasePool pool;
433
434 // If the app hasn't started, flush the event queue
435 // If we don't do this, the Dock doesn't get the message that
436 // the app has started so will refuse to activate it.
437 [NSApplication sharedApplication];
438 if (![NSApp isRunning])
439 {
440 while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
441 {
442 [NSApp sendEvent:event];
443 }
444 }
445
446 [NSApp runModalForWindow:m_modalNativeWindow];
447 }
448
449 void wxModalEventLoop::OSXDoStop()
450 {
451 [NSApp abortModal];
452 }
453
454 void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
455 {
456 WXWindow nsnow = nil;
457
458 if ( m_modalNestedLevel > 0 )
459 {
460 wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window");
461 m_modalNestedLevel++;
462 return;
463 }
464
465 m_modalWindow = modalWindow;
466 m_modalNestedLevel = 1;
467
468 if ( modalWindow )
469 {
470 // we must show now, otherwise beginModalSessionForWindow does it but it
471 // also would do a centering of the window before overriding all our position
472 if ( !modalWindow->IsShownOnScreen() )
473 modalWindow->Show();
474
475 wxNonOwnedWindow* now = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
476 wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" );
477 nsnow = now ? now->GetWXWindow() : nil;
478 }
479 else
480 {
481 NSRect r = NSMakeRect(10, 10, 0, 0);
482 nsnow = [NSPanel alloc];
483 [nsnow initWithContentRect:r
484 styleMask:NSBorderlessWindowMask
485 backing:NSBackingStoreBuffered
486 defer:YES
487 ];
488 [nsnow orderOut:nil];
489 m_dummyWindow = nsnow;
490 }
491 m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
492 wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
493 }
494
495 void wxGUIEventLoop::EndModalSession()
496 {
497 wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
498
499 wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
500
501 if ( --m_modalNestedLevel == 0 )
502 {
503 [NSApp endModalSession:(NSModalSession)m_modalSession];
504 m_modalSession = nil;
505 if ( m_dummyWindow )
506 {
507 [m_dummyWindow release];
508 m_dummyWindow = nil;
509 }
510 }
511 }
512
513 //
514 //
515 //
516
517 wxWindowDisabler::wxWindowDisabler(bool disable)
518 {
519 m_modalEventLoop = NULL;
520 m_disabled = disable;
521 if ( disable )
522 DoDisable();
523 }
524
525 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
526 {
527 m_disabled = true;
528 DoDisable(winToSkip);
529 }
530
531 void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
532 {
533 // remember the top level windows which were already disabled, so that we
534 // don't reenable them later
535 m_winDisabled = NULL;
536
537 wxWindowList::compatibility_iterator node;
538 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
539 {
540 wxWindow *winTop = node->GetData();
541 if ( winTop == winToSkip )
542 continue;
543
544 // we don't need to disable the hidden or already disabled windows
545 if ( winTop->IsEnabled() && winTop->IsShown() )
546 {
547 winTop->Disable();
548 }
549 else
550 {
551 if ( !m_winDisabled )
552 {
553 m_winDisabled = new wxWindowList;
554 }
555
556 m_winDisabled->Append(winTop);
557 }
558 }
559
560 m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
561 if (m_modalEventLoop)
562 m_modalEventLoop->BeginModalSession(winToSkip);
563 }
564
565 wxWindowDisabler::~wxWindowDisabler()
566 {
567 if ( !m_disabled )
568 return;
569
570 if (m_modalEventLoop)
571 m_modalEventLoop->EndModalSession();
572
573 wxWindowList::compatibility_iterator node;
574 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
575 {
576 wxWindow *winTop = node->GetData();
577 if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
578 {
579 winTop->Enable();
580 }
581 //else: had been already disabled, don't reenable
582 }
583
584 delete m_winDisabled;
585 }