]> git.saurik.com Git - wxWidgets.git/blame - src/osx/cocoa/evtloop.mm
Fix harmless unused parameter warning in wxOSX.
[wxWidgets.git] / src / osx / cocoa / evtloop.mm
CommitLineData
b503b036
SC
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
a9a4f229 7// RCS-ID: $Id$
b503b036
SC
8// Copyright: (c) 2006 Vadim Zeitlin <vadim@wxwindows.org>
9// Licence: wxWindows licence
10///////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20// for compilers that support precompilation, includes "wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
24 #pragma hdrstop
25#endif
26
1e04d2bf 27#include "wx/evtloop.h"
b503b036
SC
28
29#ifndef WX_PRECOMP
30 #include "wx/app.h"
bf06fbce 31 #include "wx/nonownedwnd.h"
b503b036
SC
32#endif // WX_PRECOMP
33
f965a844 34#include "wx/log.h"
d3ad22bd 35#include "wx/scopeguard.h"
f965a844 36
b503b036
SC
37#include "wx/osx/private.h"
38
39// ============================================================================
40// wxEventLoop implementation
41// ============================================================================
42
e9e8b381
SC
43#if 0
44
45// in case we want to integrate this
11fed901 46
902ddbfd
SC
47static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat)
48{
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
51
52 if ( (cat | wxEVT_CATEGORY_USER_INPUT) && (cat | (~wxEVT_CATEGORY_USER_INPUT) ) )
53 return NSAnyEventMask;
54
55 NSUInteger mask = 0;
11fed901 56
902ddbfd
SC
57 if ( cat | wxEVT_CATEGORY_USER_INPUT )
58 {
59 mask |=
60 NSLeftMouseDownMask |
61 NSLeftMouseUpMask |
62 NSRightMouseDownMask |
63 NSRightMouseUpMask |
64 NSMouseMovedMask |
65 NSLeftMouseDraggedMask |
66 NSRightMouseDraggedMask |
67 NSMouseEnteredMask |
68 NSMouseExitedMask |
69 NSScrollWheelMask |
70#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
71 NSTabletPointMask |
72 NSTabletProximityMask |
73#endif
74 NSOtherMouseDownMask |
75 NSOtherMouseUpMask |
76 NSOtherMouseDraggedMask |
77
78 NSKeyDownMask |
79 NSKeyUpMask |
80 NSFlagsChangedMask |
81#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
82 NSEventMaskGesture |
83 NSEventMaskMagnify |
84 NSEventMaskSwipe |
85 NSEventMaskRotate |
86 NSEventMaskBeginGesture |
87 NSEventMaskEndGesture |
88#endif
89 0;
90 }
91
92 if ( cat | (~wxEVT_CATEGORY_USER_INPUT) )
93 {
94 mask |=
95 NSAppKitDefinedMask |
96 NSSystemDefinedMask |
97 NSApplicationDefinedMask |
98 NSPeriodicMask |
99 NSCursorUpdateMask;
100 }
101
102 return mask;
103}
11fed901 104
e9e8b381 105#endif
11fed901 106
902ddbfd
SC
107wxGUIEventLoop::wxGUIEventLoop()
108{
109 m_modalSession = nil;
110 m_dummyWindow = nil;
0aff141c
SC
111 m_modalNestedLevel = 0;
112 m_modalWindow = NULL;
4b0a48db 113 m_osxLowLevelWakeUp = false;
11fed901 114}
11fed901 115
902ddbfd 116wxGUIEventLoop::~wxGUIEventLoop()
b503b036 117{
902ddbfd
SC
118 wxASSERT( m_modalSession == nil );
119 wxASSERT( m_dummyWindow == nil );
0aff141c 120 wxASSERT( m_modalNestedLevel == 0 );
6b8ef0b3
VZ
121}
122
123//-----------------------------------------------------------------------------
124// events dispatch and loop handling
125//-----------------------------------------------------------------------------
126
0056673c
SC
127#if 0
128
b503b036
SC
129bool wxGUIEventLoop::Pending() const
130{
a765eef3
SC
131#if 0
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)
dbeddfb9 136 wxMacAutoreleasePool autoreleasepool;
a765eef3 137
b503b036
SC
138 return [[NSApplication sharedApplication]
139 nextEventMatchingMask: NSAnyEventMask
140 untilDate: nil
141 inMode: NSDefaultRunLoopMode
a765eef3
SC
142 dequeue: NO] != nil;
143#else
144 return true;
145#endif
b503b036
SC
146}
147
50d4763f 148
b503b036
SC
149bool wxGUIEventLoop::Dispatch()
150{
151 if ( !wxTheApp )
152 return false;
153
154 wxMacAutoreleasePool autoreleasepool;
155
156 if(NSEvent *event = [NSApp
157 nextEventMatchingMask:NSAnyEventMask
158 untilDate:[NSDate dateWithTimeIntervalSinceNow: m_sleepTime]
159 inMode:NSDefaultRunLoopMode
160 dequeue: YES])
161 {
0b6f851f
SC
162 WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent();
163 WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef();
164
7dab9892
KO
165 if (wxTheApp)
166 wxTheApp->MacSetCurrentEvent(event, NULL);
b503b036
SC
167 m_sleepTime = 0.0;
168 [NSApp sendEvent: event];
0b6f851f
SC
169
170 if (wxTheApp)
171 wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler);
b503b036
SC
172 }
173 else
174 {
b0a9bfc8
SC
175 if (wxTheApp)
176 wxTheApp->ProcessPendingEvents();
177
b503b036
SC
178 if ( wxTheApp->ProcessIdle() )
179 m_sleepTime = 0.0 ;
180 else
181 {
182 m_sleepTime = 1.0;
183#if wxUSE_THREADS
184 wxMutexGuiLeave();
185 wxMilliSleep(20);
186 wxMutexGuiEnter();
187#endif
188 }
189 }
190
191 return true;
192}
91407318 193
0056673c 194#endif
f965a844 195
0056673c 196int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
91407318
VZ
197{
198 wxMacAutoreleasePool autoreleasepool;
199
902ddbfd
SC
200 if ( m_modalSession )
201 {
202 NSInteger response = [NSApp runModalSession:(NSModalSession)m_modalSession];
203
204 switch (response)
205 {
206 case NSRunContinuesResponse:
207 {
208 if ( [[NSApplication sharedApplication]
209 nextEventMatchingMask: NSAnyEventMask
210 untilDate: nil
211 inMode: NSDefaultRunLoopMode
212 dequeue: NO] != nil )
213 return 1;
214
215 return -1;
216 }
217
218 case NSRunStoppedResponse:
219 case NSRunAbortedResponse:
220 return -1;
221 default:
222 wxFAIL_MSG("unknown response code");
902ddbfd
SC
223 break;
224 }
a624c97f 225 return -1;
902ddbfd
SC
226 }
227 else
228 {
229 NSEvent *event = [NSApp
230 nextEventMatchingMask:NSAnyEventMask
231 untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
232 inMode:NSDefaultRunLoopMode
233 dequeue: YES];
234
235 if ( event == nil )
236 return -1;
91407318 237
902ddbfd 238 [NSApp sendEvent: event];
91407318 239
902ddbfd
SC
240 return 1;
241 }
91407318 242}
80eee837 243
d3ad22bd
VZ
244static int gs_loopNestingLevel = 0;
245
8d40c05f 246void wxGUIEventLoop::OSXDoRun()
80eee837 247{
d3ad22bd
VZ
248 /*
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
257 exit as well.
258
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
262
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
265
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).
270
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.
277 */
278 gs_loopNestingLevel++;
279 wxON_BLOCK_EXIT_SET(gs_loopNestingLevel, gs_loopNestingLevel - 1);
280
281 while ( !m_shouldExit )
282 {
283 // By putting this inside the loop, we can drain it in each
284 // loop iteration.
285 wxMacAutoreleasePool autoreleasepool;
286
287 if ( gs_loopNestingLevel == 1 )
288 {
289 // Use -[NSApplication run] for the main run loop.
290 [NSApp run];
291 }
292 else
293 {
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).
298 //
299 // Here are the other run loops which were tried, but always left
300 // user input disabled:
301 //
302 // [runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
303 // CFRunLoopRun();
304 // CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , true);
305 //
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
312 dequeue: YES];
313
314 [NSApp sendEvent: event];
315
316 /**
317 The NSApplication documentation states that:
318
319 "
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.
324 "
325
326 So to be safe, we also invoke it here in this event loop.
327
328 See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html
329 */
330 [NSApp updateWindows];
331 }
332 }
333
334 // Wake up the enclosing loop so that it can check if it also needs
335 // to exit.
336 WakeUp();
80eee837
SC
337}
338
8d40c05f 339void wxGUIEventLoop::OSXDoStop()
80eee837 340{
d3ad22bd
VZ
341 // We should only stop the top level event loop.
342 if ( gs_loopNestingLevel <= 1 )
343 {
344 [NSApp stop:0];
345 }
346
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.
85a74f93
SC
351 WakeUp();
352}
353
354void wxGUIEventLoop::WakeUp()
355{
843ac6c8 356 // NSEvent* cevent = [NSApp currentEvent];
4b0a48db 357 // NSString* mode = [[NSRunLoop mainRunLoop] currentMode];
50d4763f
SC
358
359 // when already in a mouse event handler, don't add higher level event
843ac6c8 360 // if ( cevent != nil && [cevent type] <= NSMouseMoved && )
4b0a48db 361 if ( m_osxLowLevelWakeUp /* [NSEventTrackingRunLoopMode isEqualToString:mode] */ )
50d4763f 362 {
843ac6c8 363 // NSLog(@"event for wakeup %@ in mode %@",cevent,mode);
50d4763f
SC
364 wxCFEventLoop::WakeUp();
365 }
366 else
367 {
368 wxMacAutoreleasePool autoreleasepool;
369 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
2e5f9929
SC
370 location:NSMakePoint(0.0, 0.0)
371 modifierFlags:0
372 timestamp:0
373 windowNumber:0
374 context:nil
375 subtype:0 data1:0 data2:0];
50d4763f
SC
376 [NSApp postEvent:event atStart:FALSE];
377 }
80eee837
SC
378}
379
7934e447
SC
380CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
381{
382 NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
383 return [nsloop getCFRunLoop];
384}
385
386
cfb0ef70
SC
387// TODO move into a evtloop_osx.cpp
388
389wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
2439f1d9 390{
cfb0ef70 391 m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
2439f1d9 392 wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
cfb0ef70 393 m_modalNativeWindow = m_modalWindow->GetWXWindow();
2439f1d9
SC
394}
395
cfb0ef70
SC
396wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
397{
398 m_modalWindow = NULL;
399 wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
400 m_modalNativeWindow = modalNativeWindow;
401}
402
403// END move into a evtloop_osx.cpp
404
8d40c05f 405void wxModalEventLoop::OSXDoRun()
80eee837
SC
406{
407 wxMacAutoreleasePool pool;
408
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])
414 {
415 while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
416 {
417 [NSApp sendEvent:event];
418 }
419 }
420
cfb0ef70 421 [NSApp runModalForWindow:m_modalNativeWindow];
80eee837
SC
422}
423
8d40c05f 424void wxModalEventLoop::OSXDoStop()
80eee837 425{
f834239f 426 [NSApp abortModal];
80eee837
SC
427}
428
902ddbfd
SC
429void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
430{
431 WXWindow nsnow = nil;
0aff141c
SC
432
433 if ( m_modalNestedLevel > 0 )
434 {
435 wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window");
436 m_modalNestedLevel++;
437 return;
438 }
439
440 m_modalWindow = modalWindow;
441 m_modalNestedLevel = 1;
902ddbfd
SC
442
443 if ( modalWindow )
444 {
b64af07b
SC
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() )
448 modalWindow->Show();
449
902ddbfd
SC
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;
453 }
454 else
455 {
456 NSRect r = NSMakeRect(10, 10, 0, 0);
457 nsnow = [NSPanel alloc];
458 [nsnow initWithContentRect:r
459 styleMask:NSBorderlessWindowMask
460 backing:NSBackingStoreBuffered
461 defer:YES
462 ];
463 [nsnow orderOut:nil];
464 m_dummyWindow = nsnow;
465 }
466 m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
203ec424 467 wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
902ddbfd
SC
468}
469
470void wxGUIEventLoop::EndModalSession()
471{
472 wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
0aff141c
SC
473
474 wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
475
476 if ( --m_modalNestedLevel == 0 )
902ddbfd 477 {
0aff141c
SC
478 [NSApp endModalSession:(NSModalSession)m_modalSession];
479 m_modalSession = nil;
480 if ( m_dummyWindow )
481 {
482 [m_dummyWindow release];
483 m_dummyWindow = nil;
484 }
902ddbfd
SC
485 }
486}
487
488//
489//
490//
491
492wxWindowDisabler::wxWindowDisabler(bool disable)
493{
494 m_modalEventLoop = NULL;
495 m_disabled = disable;
496 if ( disable )
497 DoDisable();
498}
499
500wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
501{
502 m_disabled = true;
503 DoDisable(winToSkip);
504}
505
506void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
507{
508 // remember the top level windows which were already disabled, so that we
509 // don't reenable them later
510 m_winDisabled = NULL;
511
512 wxWindowList::compatibility_iterator node;
513 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
514 {
515 wxWindow *winTop = node->GetData();
516 if ( winTop == winToSkip )
517 continue;
518
519 // we don't need to disable the hidden or already disabled windows
520 if ( winTop->IsEnabled() && winTop->IsShown() )
521 {
522 winTop->Disable();
523 }
524 else
525 {
526 if ( !m_winDisabled )
527 {
528 m_winDisabled = new wxWindowList;
529 }
530
531 m_winDisabled->Append(winTop);
532 }
533 }
534
535 m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
85fb0a0a
RD
536 if (m_modalEventLoop)
537 m_modalEventLoop->BeginModalSession(winToSkip);
902ddbfd
SC
538}
539
540wxWindowDisabler::~wxWindowDisabler()
541{
542 if ( !m_disabled )
543 return;
544
85fb0a0a
RD
545 if (m_modalEventLoop)
546 m_modalEventLoop->EndModalSession();
902ddbfd
SC
547
548 wxWindowList::compatibility_iterator node;
549 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
550 {
551 wxWindow *winTop = node->GetData();
552 if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
553 {
554 winTop->Enable();
555 }
556 //else: had been already disabled, don't reenable
557 }
558
559 delete m_winDisabled;
8d40c05f 560}