]> git.saurik.com Git - wxWidgets.git/blob - src/osx/cocoa/evtloop.mm
Don't hide the window in its dtor in wxGTK.
[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 void wxGUIEventLoop::OSXDoRun()
246 {
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();
336 }
337
338 void wxGUIEventLoop::OSXDoStop()
339 {
340 // We should only stop the top level event loop.
341 if ( gs_loopNestingLevel <= 1 )
342 {
343 // using terminate support all native notifications
344 [NSApp terminate: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.
351 WakeUp();
352 }
353
354 void wxGUIEventLoop::WakeUp()
355 {
356 // NSEvent* cevent = [NSApp currentEvent];
357 // NSString* mode = [[NSRunLoop mainRunLoop] currentMode];
358
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] */ )
362 {
363 // NSLog(@"event for wakeup %@ in mode %@",cevent,mode);
364 wxCFEventLoop::WakeUp();
365 }
366 else
367 {
368 wxMacAutoreleasePool autoreleasepool;
369 NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
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];
376 [NSApp postEvent:event atStart:FALSE];
377 }
378 }
379
380 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
381 {
382 NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
383 return [nsloop getCFRunLoop];
384 }
385
386
387 // TODO move into a evtloop_osx.cpp
388
389 wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
390 {
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();
394 }
395
396 wxModalEventLoop::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
405 void wxModalEventLoop::OSXDoRun()
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
421 [NSApp runModalForWindow:m_modalNativeWindow];
422 }
423
424 void wxModalEventLoop::OSXDoStop()
425 {
426 [NSApp abortModal];
427 }
428
429 void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
430 {
431 WXWindow nsnow = nil;
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;
442
443 if ( modalWindow )
444 {
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
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];
467 wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
468 }
469
470 void wxGUIEventLoop::EndModalSession()
471 {
472 wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
473
474 wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
475
476 if ( --m_modalNestedLevel == 0 )
477 {
478 [NSApp endModalSession:(NSModalSession)m_modalSession];
479 m_modalSession = nil;
480 if ( m_dummyWindow )
481 {
482 [m_dummyWindow release];
483 m_dummyWindow = nil;
484 }
485 }
486 }
487
488 //
489 //
490 //
491
492 wxWindowDisabler::wxWindowDisabler(bool disable)
493 {
494 m_modalEventLoop = NULL;
495 m_disabled = disable;
496 if ( disable )
497 DoDisable();
498 }
499
500 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
501 {
502 m_disabled = true;
503 DoDisable(winToSkip);
504 }
505
506 void 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();
536 if (m_modalEventLoop)
537 m_modalEventLoop->BeginModalSession(winToSkip);
538 }
539
540 wxWindowDisabler::~wxWindowDisabler()
541 {
542 if ( !m_disabled )
543 return;
544
545 if (m_modalEventLoop)
546 m_modalEventLoop->EndModalSession();
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;
560 }