make sure we can issue a WakeUp call that really triggers at the NSRunLoop level...
[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 }
113
114 wxGUIEventLoop::~wxGUIEventLoop()
115 {
116     wxASSERT( m_modalSession == nil );
117     wxASSERT( m_dummyWindow == nil );
118     wxASSERT( m_modalNestedLevel == 0 );
119 }
120
121 //-----------------------------------------------------------------------------
122 // events dispatch and loop handling
123 //-----------------------------------------------------------------------------
124
125 #if 0
126
127 bool wxGUIEventLoop::Pending() const
128 {
129 #if 0
130     // this code doesn't reliably detect pending events
131     // so better return true and have the dispatch deal with it
132     // as otherwise we end up in a tight loop when idle events are responded
133     // to by RequestMore(true)
134     wxMacAutoreleasePool autoreleasepool;
135   
136     return [[NSApplication sharedApplication]
137             nextEventMatchingMask: NSAnyEventMask
138             untilDate: nil
139             inMode: NSDefaultRunLoopMode
140             dequeue: NO] != nil;
141 #else
142     return true;
143 #endif
144 }
145
146 bool wxGUIEventLoop::Dispatch()
147 {
148     if ( !wxTheApp )
149         return false;
150
151     wxMacAutoreleasePool autoreleasepool;
152
153     if(NSEvent *event = [NSApp
154                 nextEventMatchingMask:NSAnyEventMask
155                 untilDate:[NSDate dateWithTimeIntervalSinceNow: m_sleepTime]
156                 inMode:NSDefaultRunLoopMode
157                 dequeue: YES])
158     {
159         WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent();
160         WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef();
161
162         if (wxTheApp)
163             wxTheApp->MacSetCurrentEvent(event, NULL);
164         m_sleepTime = 0.0;
165         [NSApp sendEvent: event];
166
167         if (wxTheApp)
168             wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler);
169     }
170     else
171     {
172         if (wxTheApp)
173             wxTheApp->ProcessPendingEvents();
174         
175         if ( wxTheApp->ProcessIdle() )
176             m_sleepTime = 0.0 ;
177         else
178         {
179             m_sleepTime = 1.0;
180 #if wxUSE_THREADS
181             wxMutexGuiLeave();
182             wxMilliSleep(20);
183             wxMutexGuiEnter();
184 #endif
185         }
186     }
187
188     return true;
189 }
190
191 #endif
192
193 int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
194 {
195     wxMacAutoreleasePool autoreleasepool;
196
197     if ( m_modalSession )
198     {
199         NSInteger response = [NSApp runModalSession:(NSModalSession)m_modalSession];
200         
201         switch (response) 
202         {
203             case NSRunContinuesResponse:
204             {
205                 if ( [[NSApplication sharedApplication]
206                         nextEventMatchingMask: NSAnyEventMask
207                         untilDate: nil
208                         inMode: NSDefaultRunLoopMode
209                         dequeue: NO] != nil )
210                     return 1;
211                 
212                 return -1;
213             }
214                 
215             case NSRunStoppedResponse:
216             case NSRunAbortedResponse:
217                 return -1;
218             default:
219                 wxFAIL_MSG("unknown response code");
220                 break;
221         }
222         return -1;
223     }
224     else 
225     {        
226         NSEvent *event = [NSApp
227                     nextEventMatchingMask:NSAnyEventMask
228                     untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
229                     inMode:NSDefaultRunLoopMode
230                     dequeue: YES];
231         
232         if ( event == nil )
233             return -1;
234
235         [NSApp sendEvent: event];
236
237         return 1;
238     }
239 }
240
241 void wxGUIEventLoop::DoRun()
242 {
243     wxMacAutoreleasePool autoreleasepool;
244     [NSApp run];
245 }
246
247 void wxGUIEventLoop::DoStop()
248 {
249     // only calling stop: is not enough when called from a runloop-observer,
250     // therefore add a dummy event, to make sure the runloop gets another round
251     [NSApp stop:0];
252     WakeUp();
253 }
254
255 void wxGUIEventLoop::WakeUp()
256 {
257     NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined 
258                                         location:NSMakePoint(0.0, 0.0) 
259                                    modifierFlags:0 
260                                        timestamp:0 
261                                     windowNumber:0 
262                                          context:nil
263                                          subtype:0 data1:0 data2:0]; 
264     [NSApp postEvent:event atStart:FALSE];
265 }
266
267 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
268 {
269     NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
270     return [nsloop getCFRunLoop];
271 }
272
273
274 // TODO move into a evtloop_osx.cpp
275
276 wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
277 {
278     m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
279     wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
280     m_modalNativeWindow = m_modalWindow->GetWXWindow();
281 }
282
283 wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
284 {
285     m_modalWindow = NULL;
286     wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
287     m_modalNativeWindow = modalNativeWindow;
288 }
289
290 // END move into a evtloop_osx.cpp
291
292 void wxModalEventLoop::DoRun()
293 {
294     wxMacAutoreleasePool pool;
295
296     // If the app hasn't started, flush the event queue
297     // If we don't do this, the Dock doesn't get the message that
298     // the app has started so will refuse to activate it.
299     [NSApplication sharedApplication];
300     if (![NSApp isRunning])
301     {
302         while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
303         {
304             [NSApp sendEvent:event];
305         }
306     }
307     
308     [NSApp runModalForWindow:m_modalNativeWindow];
309 }
310
311 void wxModalEventLoop::DoStop()
312 {
313     [NSApp stopModal];
314 }
315
316 void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
317 {
318     WXWindow nsnow = nil;
319
320     if ( m_modalNestedLevel > 0 )
321     {
322         wxASSERT_MSG( m_modalWindow == modalWindow, "Nested Modal Sessions must be based on same window");
323         m_modalNestedLevel++;
324         return;
325     }
326     
327     m_modalWindow = modalWindow;
328     m_modalNestedLevel = 1;
329     
330     if ( modalWindow )
331     {
332         // we must show now, otherwise beginModalSessionForWindow does it but it
333         // also would do a centering of the window before overriding all our position
334         if ( !modalWindow->IsShownOnScreen() )
335             modalWindow->Show();
336         
337         wxNonOwnedWindow* now = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
338         wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" );
339         nsnow = now ? now->GetWXWindow() : nil;
340     }
341     else
342     {
343         NSRect r = NSMakeRect(10, 10, 0, 0);
344         nsnow = [NSPanel alloc];
345         [nsnow initWithContentRect:r
346                                styleMask:NSBorderlessWindowMask
347                                  backing:NSBackingStoreBuffered
348                                    defer:YES
349          ];
350         [nsnow orderOut:nil];
351         m_dummyWindow = nsnow;
352     }
353     m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
354     wxASSERT_MSG(m_modalSession != NULL, "modal session couldn't be started");
355 }
356
357 void wxGUIEventLoop::EndModalSession()
358 {
359     wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
360     
361     wxASSERT_MSG(m_modalNestedLevel > 0, "incorrect modal nesting level");
362     
363     if ( --m_modalNestedLevel == 0 )
364     {
365         [NSApp endModalSession:(NSModalSession)m_modalSession];
366         m_modalSession = nil;
367         if ( m_dummyWindow )
368         {
369             [m_dummyWindow release];
370             m_dummyWindow = nil;
371         }
372     }
373 }
374
375 //
376 // 
377 //
378
379 wxWindowDisabler::wxWindowDisabler(bool disable)
380 {
381     m_modalEventLoop = NULL;
382     m_disabled = disable;
383     if ( disable )
384         DoDisable();
385 }
386
387 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
388 {
389     m_disabled = true;
390     DoDisable(winToSkip);
391 }
392
393 void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
394 {    
395     // remember the top level windows which were already disabled, so that we
396     // don't reenable them later
397     m_winDisabled = NULL;
398     
399     wxWindowList::compatibility_iterator node;
400     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
401     {
402         wxWindow *winTop = node->GetData();
403         if ( winTop == winToSkip )
404             continue;
405         
406         // we don't need to disable the hidden or already disabled windows
407         if ( winTop->IsEnabled() && winTop->IsShown() )
408         {
409             winTop->Disable();
410         }
411         else
412         {
413             if ( !m_winDisabled )
414             {
415                 m_winDisabled = new wxWindowList;
416             }
417             
418             m_winDisabled->Append(winTop);
419         }
420     }
421     
422     m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
423     m_modalEventLoop->BeginModalSession(winToSkip);
424 }
425
426 wxWindowDisabler::~wxWindowDisabler()
427 {
428     if ( !m_disabled )
429         return;
430     
431     m_modalEventLoop->EndModalSession();
432     
433     wxWindowList::compatibility_iterator node;
434     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
435     {
436         wxWindow *winTop = node->GetData();
437         if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
438         {
439             winTop->Enable();
440         }
441         //else: had been already disabled, don't reenable
442     }
443     
444     delete m_winDisabled;
445 }
446