make WakeUp variant explicit, too many errors using heuristics, like #14176
[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 // RCS-ID:      $Id$
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
27 #include "wx/evtloop.h"
28
29 #ifndef WX_PRECOMP
30     #include "wx/app.h"
31     #include "wx/nonownedwnd.h"
32 #endif // WX_PRECOMP
33
34 #include "wx/log.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 void wxGUIEventLoop::DoRun()
244 {
245     wxMacAutoreleasePool autoreleasepool;
246     [NSApp run];
247 }
248
249 void wxGUIEventLoop::DoStop()
250 {
251     // only calling stop: is not enough when called from a runloop-observer,
252     // therefore add a dummy event, to make sure the runloop gets another round
253     [NSApp stop:0];
254     WakeUp();
255 }
256
257 void wxGUIEventLoop::WakeUp()
258 {
259     // NSEvent* cevent = [NSApp currentEvent];
260     // NSString* mode = [[NSRunLoop mainRunLoop] currentMode];
261     
262     // when already in a mouse event handler, don't add higher level event
263     // if ( cevent != nil && [cevent type] <= NSMouseMoved && )
264     if ( m_osxLowLevelWakeUp /* [NSEventTrackingRunLoopMode isEqualToString:mode] */ )
265     {
266         // NSLog(@"event for wakeup %@ in mode %@",cevent,mode);
267         wxCFEventLoop::WakeUp();        
268     }
269     else
270     {
271         wxMacAutoreleasePool autoreleasepool;
272         NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined 
273                                         location:NSMakePoint(0.0, 0.0) 
274                                    modifierFlags:0 
275                                        timestamp:0 
276                                     windowNumber:0 
277                                          context:nil
278                                          subtype:0 data1:0 data2:0]; 
279         [NSApp postEvent:event atStart:FALSE];
280     }
281 }
282
283 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
284 {
285     NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
286     return [nsloop getCFRunLoop];
287 }
288
289
290 // TODO move into a evtloop_osx.cpp
291
292 wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
293 {
294     m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
295     wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
296     m_modalNativeWindow = m_modalWindow->GetWXWindow();
297 }
298
299 wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
300 {
301     m_modalWindow = NULL;
302     wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
303     m_modalNativeWindow = modalNativeWindow;
304 }
305
306 // END move into a evtloop_osx.cpp
307
308 void wxModalEventLoop::DoRun()
309 {
310     wxMacAutoreleasePool pool;
311
312     // If the app hasn't started, flush the event queue
313     // If we don't do this, the Dock doesn't get the message that
314     // the app has started so will refuse to activate it.
315     [NSApplication sharedApplication];
316     if (![NSApp isRunning])
317     {
318         while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
319         {
320             [NSApp sendEvent:event];
321         }
322     }
323     
324     [NSApp runModalForWindow:m_modalNativeWindow];
325 }
326
327 void wxModalEventLoop::DoStop()
328 {
329     [NSApp abortModal];
330 }
331
332 void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
333 {
334     WXWindow nsnow = nil;
335
336     if ( m_modalNestedLevel > 0 )
337     {
338         wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window");
339         m_modalNestedLevel++;
340         return;
341     }
342     
343     m_modalWindow = modalWindow;
344     m_modalNestedLevel = 1;
345     
346     if ( modalWindow )
347     {
348         // we must show now, otherwise beginModalSessionForWindow does it but it
349         // also would do a centering of the window before overriding all our position
350         if ( !modalWindow->IsShownOnScreen() )
351             modalWindow->Show();
352         
353         wxNonOwnedWindow* now = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
354         wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" );
355         nsnow = now ? now->GetWXWindow() : nil;
356     }
357     else
358     {
359         NSRect r = NSMakeRect(10, 10, 0, 0);
360         nsnow = [NSPanel alloc];
361         [nsnow initWithContentRect:r
362                                styleMask:NSBorderlessWindowMask
363                                  backing:NSBackingStoreBuffered
364                                    defer:YES
365          ];
366         [nsnow orderOut:nil];
367         m_dummyWindow = nsnow;
368     }
369     m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
370     wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
371 }
372
373 void wxGUIEventLoop::EndModalSession()
374 {
375     wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
376     
377     wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
378     
379     if ( --m_modalNestedLevel == 0 )
380     {
381         [NSApp endModalSession:(NSModalSession)m_modalSession];
382         m_modalSession = nil;
383         if ( m_dummyWindow )
384         {
385             [m_dummyWindow release];
386             m_dummyWindow = nil;
387         }
388     }
389 }
390
391 //
392 // 
393 //
394
395 wxWindowDisabler::wxWindowDisabler(bool disable)
396 {
397     m_modalEventLoop = NULL;
398     m_disabled = disable;
399     if ( disable )
400         DoDisable();
401 }
402
403 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
404 {
405     m_disabled = true;
406     DoDisable(winToSkip);
407 }
408
409 void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
410 {    
411     // remember the top level windows which were already disabled, so that we
412     // don't reenable them later
413     m_winDisabled = NULL;
414     
415     wxWindowList::compatibility_iterator node;
416     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
417     {
418         wxWindow *winTop = node->GetData();
419         if ( winTop == winToSkip )
420             continue;
421         
422         // we don't need to disable the hidden or already disabled windows
423         if ( winTop->IsEnabled() && winTop->IsShown() )
424         {
425             winTop->Disable();
426         }
427         else
428         {
429             if ( !m_winDisabled )
430             {
431                 m_winDisabled = new wxWindowList;
432             }
433             
434             m_winDisabled->Append(winTop);
435         }
436     }
437     
438     m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
439     if (m_modalEventLoop)
440         m_modalEventLoop->BeginModalSession(winToSkip);
441 }
442
443 wxWindowDisabler::~wxWindowDisabler()
444 {
445     if ( !m_disabled )
446         return;
447     
448     if (m_modalEventLoop)
449         m_modalEventLoop->EndModalSession();
450     
451     wxWindowList::compatibility_iterator node;
452     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
453     {
454         wxWindow *winTop = node->GetData();
455         if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
456         {
457             winTop->Enable();
458         }
459         //else: had been already disabled, don't reenable
460     }
461     
462     delete m_winDisabled;
463 }