]> git.saurik.com Git - wxWidgets.git/blame - src/osx/cocoa/evtloop.mm
No real changes, just make wxWindow::CanScroll() virtual.
[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
b503b036
SC
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
1e04d2bf 26#include "wx/evtloop.h"
b503b036
SC
27
28#ifndef WX_PRECOMP
29 #include "wx/app.h"
bf06fbce 30 #include "wx/nonownedwnd.h"
b503b036
SC
31#endif // WX_PRECOMP
32
f965a844 33#include "wx/log.h"
d3ad22bd 34#include "wx/scopeguard.h"
f965a844 35
b503b036
SC
36#include "wx/osx/private.h"
37
38// ============================================================================
39// wxEventLoop implementation
40// ============================================================================
41
e9e8b381
SC
42#if 0
43
44// in case we want to integrate this
11fed901 45
902ddbfd
SC
46static 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;
11fed901 55
902ddbfd
SC
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}
11fed901 103
e9e8b381 104#endif
11fed901 105
902ddbfd
SC
106wxGUIEventLoop::wxGUIEventLoop()
107{
108 m_modalSession = nil;
109 m_dummyWindow = nil;
0aff141c
SC
110 m_modalNestedLevel = 0;
111 m_modalWindow = NULL;
4b0a48db 112 m_osxLowLevelWakeUp = false;
11fed901 113}
11fed901 114
902ddbfd 115wxGUIEventLoop::~wxGUIEventLoop()
b503b036 116{
902ddbfd
SC
117 wxASSERT( m_modalSession == nil );
118 wxASSERT( m_dummyWindow == nil );
0aff141c 119 wxASSERT( m_modalNestedLevel == 0 );
6b8ef0b3
VZ
120}
121
122//-----------------------------------------------------------------------------
123// events dispatch and loop handling
124//-----------------------------------------------------------------------------
125
0056673c
SC
126#if 0
127
b503b036
SC
128bool wxGUIEventLoop::Pending() const
129{
a765eef3
SC
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)
dbeddfb9 135 wxMacAutoreleasePool autoreleasepool;
a765eef3 136
b503b036
SC
137 return [[NSApplication sharedApplication]
138 nextEventMatchingMask: NSAnyEventMask
139 untilDate: nil
140 inMode: NSDefaultRunLoopMode
a765eef3
SC
141 dequeue: NO] != nil;
142#else
143 return true;
144#endif
b503b036
SC
145}
146
50d4763f 147
b503b036
SC
148bool 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 {
0b6f851f
SC
161 WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent();
162 WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef();
163
7dab9892
KO
164 if (wxTheApp)
165 wxTheApp->MacSetCurrentEvent(event, NULL);
b503b036
SC
166 m_sleepTime = 0.0;
167 [NSApp sendEvent: event];
0b6f851f
SC
168
169 if (wxTheApp)
170 wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler);
b503b036
SC
171 }
172 else
173 {
b0a9bfc8
SC
174 if (wxTheApp)
175 wxTheApp->ProcessPendingEvents();
176
b503b036
SC
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}
91407318 192
0056673c 193#endif
f965a844 194
0056673c 195int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
91407318
VZ
196{
197 wxMacAutoreleasePool autoreleasepool;
198
902ddbfd
SC
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");
902ddbfd
SC
222 break;
223 }
a624c97f 224 return -1;
902ddbfd
SC
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;
91407318 236
902ddbfd 237 [NSApp sendEvent: event];
91407318 238
902ddbfd
SC
239 return 1;
240 }
91407318 241}
80eee837 242
d3ad22bd
VZ
243static int gs_loopNestingLevel = 0;
244
8d40c05f 245void wxGUIEventLoop::OSXDoRun()
80eee837 246{
d3ad22bd
VZ
247 /*
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
256 exit as well.
257
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
261
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
264
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).
269
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.
276 */
277 gs_loopNestingLevel++;
278 wxON_BLOCK_EXIT_SET(gs_loopNestingLevel, gs_loopNestingLevel - 1);
279
280 while ( !m_shouldExit )
281 {
282 // By putting this inside the loop, we can drain it in each
283 // loop iteration.
284 wxMacAutoreleasePool autoreleasepool;
285
286 if ( gs_loopNestingLevel == 1 )
287 {
288 // Use -[NSApplication run] for the main run loop.
289 [NSApp run];
290 }
291 else
292 {
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).
297 //
298 // Here are the other run loops which were tried, but always left
299 // user input disabled:
300 //
301 // [runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
302 // CFRunLoopRun();
303 // CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , true);
304 //
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
311 dequeue: YES];
312
313 [NSApp sendEvent: event];
314
315 /**
316 The NSApplication documentation states that:
317
318 "
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.
323 "
324
325 So to be safe, we also invoke it here in this event loop.
326
327 See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html
328 */
329 [NSApp updateWindows];
330 }
331 }
332
333 // Wake up the enclosing loop so that it can check if it also needs
334 // to exit.
335 WakeUp();
80eee837
SC
336}
337
8d40c05f 338void wxGUIEventLoop::OSXDoStop()
80eee837 339{
d3ad22bd
VZ
340 // We should only stop the top level event loop.
341 if ( gs_loopNestingLevel <= 1 )
342 {
28f058d3 343 [NSApp stop:0];
d3ad22bd
VZ
344 }
345
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.
85a74f93
SC
350 WakeUp();
351}
352
353void wxGUIEventLoop::WakeUp()
354{
843ac6c8 355 // NSEvent* cevent = [NSApp currentEvent];
4b0a48db 356 // NSString* mode = [[NSRunLoop mainRunLoop] currentMode];
50d4763f
SC
357
358 // when already in a mouse event handler, don't add higher level event
843ac6c8 359 // if ( cevent != nil && [cevent type] <= NSMouseMoved && )
4b0a48db 360 if ( m_osxLowLevelWakeUp /* [NSEventTrackingRunLoopMode isEqualToString:mode] */ )
50d4763f 361 {
843ac6c8 362 // NSLog(@"event for wakeup %@ in mode %@",cevent,mode);
50d4763f
SC
363 wxCFEventLoop::WakeUp();
364 }
365 else
366 {
367 wxMacAutoreleasePool autoreleasepool;
368 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
2e5f9929
SC
369 location:NSMakePoint(0.0, 0.0)
370 modifierFlags:0
371 timestamp:0
372 windowNumber:0
373 context:nil
374 subtype:0 data1:0 data2:0];
50d4763f
SC
375 [NSApp postEvent:event atStart:FALSE];
376 }
80eee837
SC
377}
378
7934e447
SC
379CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
380{
381 NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
382 return [nsloop getCFRunLoop];
383}
384
385
cfb0ef70
SC
386// TODO move into a evtloop_osx.cpp
387
388wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
2439f1d9 389{
cfb0ef70 390 m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
2439f1d9 391 wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
cfb0ef70 392 m_modalNativeWindow = m_modalWindow->GetWXWindow();
2439f1d9
SC
393}
394
cfb0ef70
SC
395wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
396{
397 m_modalWindow = NULL;
398 wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
399 m_modalNativeWindow = modalNativeWindow;
400}
401
402// END move into a evtloop_osx.cpp
403
8d40c05f 404void wxModalEventLoop::OSXDoRun()
80eee837
SC
405{
406 wxMacAutoreleasePool pool;
407
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])
413 {
414 while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
415 {
416 [NSApp sendEvent:event];
417 }
418 }
419
cfb0ef70 420 [NSApp runModalForWindow:m_modalNativeWindow];
80eee837
SC
421}
422
8d40c05f 423void wxModalEventLoop::OSXDoStop()
80eee837 424{
f834239f 425 [NSApp abortModal];
80eee837
SC
426}
427
902ddbfd
SC
428void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
429{
430 WXWindow nsnow = nil;
0aff141c
SC
431
432 if ( m_modalNestedLevel > 0 )
433 {
434 wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window");
435 m_modalNestedLevel++;
436 return;
437 }
438
439 m_modalWindow = modalWindow;
440 m_modalNestedLevel = 1;
902ddbfd
SC
441
442 if ( modalWindow )
443 {
b64af07b
SC
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() )
447 modalWindow->Show();
448
902ddbfd
SC
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;
452 }
453 else
454 {
455 NSRect r = NSMakeRect(10, 10, 0, 0);
456 nsnow = [NSPanel alloc];
457 [nsnow initWithContentRect:r
458 styleMask:NSBorderlessWindowMask
459 backing:NSBackingStoreBuffered
460 defer:YES
461 ];
462 [nsnow orderOut:nil];
463 m_dummyWindow = nsnow;
464 }
465 m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
203ec424 466 wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
902ddbfd
SC
467}
468
469void wxGUIEventLoop::EndModalSession()
470{
471 wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
0aff141c
SC
472
473 wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
474
475 if ( --m_modalNestedLevel == 0 )
902ddbfd 476 {
0aff141c
SC
477 [NSApp endModalSession:(NSModalSession)m_modalSession];
478 m_modalSession = nil;
479 if ( m_dummyWindow )
480 {
481 [m_dummyWindow release];
482 m_dummyWindow = nil;
483 }
902ddbfd
SC
484 }
485}
486
487//
488//
489//
490
491wxWindowDisabler::wxWindowDisabler(bool disable)
492{
493 m_modalEventLoop = NULL;
494 m_disabled = disable;
495 if ( disable )
496 DoDisable();
497}
498
499wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
500{
501 m_disabled = true;
502 DoDisable(winToSkip);
503}
504
505void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
506{
507 // remember the top level windows which were already disabled, so that we
508 // don't reenable them later
509 m_winDisabled = NULL;
510
511 wxWindowList::compatibility_iterator node;
512 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
513 {
514 wxWindow *winTop = node->GetData();
515 if ( winTop == winToSkip )
516 continue;
517
518 // we don't need to disable the hidden or already disabled windows
519 if ( winTop->IsEnabled() && winTop->IsShown() )
520 {
521 winTop->Disable();
522 }
523 else
524 {
525 if ( !m_winDisabled )
526 {
527 m_winDisabled = new wxWindowList;
528 }
529
530 m_winDisabled->Append(winTop);
531 }
532 }
533
534 m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
85fb0a0a
RD
535 if (m_modalEventLoop)
536 m_modalEventLoop->BeginModalSession(winToSkip);
902ddbfd
SC
537}
538
539wxWindowDisabler::~wxWindowDisabler()
540{
541 if ( !m_disabled )
542 return;
543
85fb0a0a
RD
544 if (m_modalEventLoop)
545 m_modalEventLoop->EndModalSession();
902ddbfd
SC
546
547 wxWindowList::compatibility_iterator node;
548 for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
549 {
550 wxWindow *winTop = node->GetData();
551 if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
552 {
553 winTop->Enable();
554 }
555 //else: had been already disabled, don't reenable
556 }
557
558 delete m_winDisabled;
8d40c05f 559}