support wxWindowDisabler on osx_cocoa
[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: evtloop.cpp 54845 2008-07-30 14:52:41Z 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
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
43 static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat)
44 {
45     // the masking system doesn't really help, as only the lowlevel UI events
46     // are split in a useful way, all others are way to broad
47         
48     if ( (cat | wxEVT_CATEGORY_USER_INPUT) && (cat | (~wxEVT_CATEGORY_USER_INPUT) ) )
49         return NSAnyEventMask;
50     
51     NSUInteger mask = 0;
52
53     if ( cat | wxEVT_CATEGORY_USER_INPUT )
54     {
55         mask |=
56             NSLeftMouseDownMask |
57             NSLeftMouseUpMask |
58             NSRightMouseDownMask |
59             NSRightMouseUpMask |
60             NSMouseMovedMask |
61             NSLeftMouseDraggedMask |
62             NSRightMouseDraggedMask |
63             NSMouseEnteredMask |
64             NSMouseExitedMask |
65             NSScrollWheelMask |
66 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
67             NSTabletPointMask |
68             NSTabletProximityMask |
69 #endif
70             NSOtherMouseDownMask |
71             NSOtherMouseUpMask |
72             NSOtherMouseDraggedMask |
73
74             NSKeyDownMask |
75             NSKeyUpMask |
76             NSFlagsChangedMask |
77 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
78             NSEventMaskGesture |
79             NSEventMaskMagnify |
80             NSEventMaskSwipe |
81             NSEventMaskRotate |
82             NSEventMaskBeginGesture |
83             NSEventMaskEndGesture |
84 #endif
85             0;
86     }
87     
88     if ( cat | (~wxEVT_CATEGORY_USER_INPUT) )
89     {
90         mask |= 
91             NSAppKitDefinedMask |
92             NSSystemDefinedMask |
93             NSApplicationDefinedMask |
94             NSPeriodicMask |
95             NSCursorUpdateMask;
96     }
97     
98     return mask;
99 }
100
101
102 wxGUIEventLoop::wxGUIEventLoop()
103 {
104     m_modalSession = nil;
105     m_dummyWindow = nil;
106 }
107
108 wxGUIEventLoop::~wxGUIEventLoop()
109 {
110     wxASSERT( m_modalSession == nil );
111     wxASSERT( m_dummyWindow == nil );
112 }
113
114 //-----------------------------------------------------------------------------
115 // events dispatch and loop handling
116 //-----------------------------------------------------------------------------
117
118 #if 0
119
120 bool wxGUIEventLoop::Pending() const
121 {
122 #if 0
123     // this code doesn't reliably detect pending events
124     // so better return true and have the dispatch deal with it
125     // as otherwise we end up in a tight loop when idle events are responded
126     // to by RequestMore(true)
127     wxMacAutoreleasePool autoreleasepool;
128   
129     return [[NSApplication sharedApplication]
130             nextEventMatchingMask: NSAnyEventMask
131             untilDate: nil
132             inMode: NSDefaultRunLoopMode
133             dequeue: NO] != nil;
134 #else
135     return true;
136 #endif
137 }
138
139 bool wxGUIEventLoop::Dispatch()
140 {
141     if ( !wxTheApp )
142         return false;
143
144     wxMacAutoreleasePool autoreleasepool;
145
146     if(NSEvent *event = [NSApp
147                 nextEventMatchingMask:NSAnyEventMask
148                 untilDate:[NSDate dateWithTimeIntervalSinceNow: m_sleepTime]
149                 inMode:NSDefaultRunLoopMode
150                 dequeue: YES])
151     {
152         WXEVENTREF formerEvent = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEvent();
153         WXEVENTHANDLERCALLREF formerHandler = wxTheApp == NULL ? NULL : wxTheApp->MacGetCurrentEventHandlerCallRef();
154
155         if (wxTheApp)
156             wxTheApp->MacSetCurrentEvent(event, NULL);
157         m_sleepTime = 0.0;
158         [NSApp sendEvent: event];
159
160         if (wxTheApp)
161             wxTheApp->MacSetCurrentEvent(formerEvent , formerHandler);
162     }
163     else
164     {
165         if (wxTheApp)
166             wxTheApp->ProcessPendingEvents();
167         
168         if ( wxTheApp->ProcessIdle() )
169             m_sleepTime = 0.0 ;
170         else
171         {
172             m_sleepTime = 1.0;
173 #if wxUSE_THREADS
174             wxMutexGuiLeave();
175             wxMilliSleep(20);
176             wxMutexGuiEnter();
177 #endif
178         }
179     }
180
181     return true;
182 }
183
184 #endif
185
186 int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
187 {
188     wxMacAutoreleasePool autoreleasepool;
189
190     if ( m_modalSession )
191     {
192         NSInteger response = [NSApp runModalSession:(NSModalSession)m_modalSession];
193         
194         switch (response) 
195         {
196             case NSRunContinuesResponse:
197             {
198                 if ( [[NSApplication sharedApplication]
199                         nextEventMatchingMask: NSAnyEventMask
200                         untilDate: nil
201                         inMode: NSDefaultRunLoopMode
202                         dequeue: NO] != nil )
203                     return 1;
204                 
205                 return -1;
206             }
207                 
208             case NSRunStoppedResponse:
209             case NSRunAbortedResponse:
210                 return -1;
211             default:
212                 wxFAIL_MSG("unknown response code");
213                 return -1;
214                 break;
215         }
216     }
217     else 
218     {        
219         NSEvent *event = [NSApp
220                     nextEventMatchingMask:NSAnyEventMask
221                     untilDate:[NSDate dateWithTimeIntervalSinceNow: timeout/1000]
222                     inMode:NSDefaultRunLoopMode
223                     dequeue: YES];
224         
225         if ( event == nil )
226             return -1;
227
228         [NSApp sendEvent: event];
229
230         return 1;
231     }
232 }
233
234 void wxGUIEventLoop::DoRun()
235 {
236     wxMacAutoreleasePool autoreleasepool;
237     [NSApp run];
238 }
239
240 void wxGUIEventLoop::DoStop()
241 {
242     [NSApp stop:0];
243     // only calling stop: is not enough when called from a runloop-observer,
244     // therefore add a dummy event, to make sure the runloop gets another round
245     NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined 
246                                         location:NSMakePoint(0.0, 0.0) 
247                                    modifierFlags:0 
248                                        timestamp:0 
249                                     windowNumber:0 
250                                          context:nil
251                                          subtype:0 data1:0 data2:0]; 
252     [NSApp postEvent:event atStart:FALSE];
253 }
254
255 CFRunLoopRef wxGUIEventLoop::CFGetCurrentRunLoop() const
256 {
257     NSRunLoop* nsloop = [NSRunLoop currentRunLoop];
258     return [nsloop getCFRunLoop];
259 }
260
261
262 // TODO move into a evtloop_osx.cpp
263
264 wxModalEventLoop::wxModalEventLoop(wxWindow *modalWindow)
265 {
266     m_modalWindow = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
267     wxASSERT_MSG( m_modalWindow != NULL, "must pass in a toplevel window for modal event loop" );
268     m_modalNativeWindow = m_modalWindow->GetWXWindow();
269 }
270
271 wxModalEventLoop::wxModalEventLoop(WXWindow modalNativeWindow)
272 {
273     m_modalWindow = NULL;
274     wxASSERT_MSG( modalNativeWindow != NULL, "must pass in a toplevel window for modal event loop" );
275     m_modalNativeWindow = modalNativeWindow;
276 }
277
278 // END move into a evtloop_osx.cpp
279
280 void wxModalEventLoop::DoRun()
281 {
282     wxMacAutoreleasePool pool;
283
284     // If the app hasn't started, flush the event queue
285     // If we don't do this, the Dock doesn't get the message that
286     // the app has started so will refuse to activate it.
287     [NSApplication sharedApplication];
288     if (![NSApp isRunning])
289     {
290         while(NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSDefaultRunLoopMode dequeue:YES])
291         {
292             [NSApp sendEvent:event];
293         }
294     }
295     
296     [NSApp runModalForWindow:m_modalNativeWindow];
297 }
298
299 void wxModalEventLoop::DoStop()
300 {
301     [NSApp stopModal];
302 }
303
304 void wxGUIEventLoop::BeginModalSession( wxWindow* modalWindow )
305 {
306     WXWindow nsnow = nil;
307     
308     if ( modalWindow )
309     {
310         wxNonOwnedWindow* now = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
311         wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" );
312         nsnow = now ? now->GetWXWindow() : nil;
313     }
314     else
315     {
316         NSRect r = NSMakeRect(10, 10, 0, 0);
317         nsnow = [NSPanel alloc];
318         [nsnow initWithContentRect:r
319                                styleMask:NSBorderlessWindowMask
320                                  backing:NSBackingStoreBuffered
321                                    defer:YES
322          ];
323         [nsnow orderOut:nil];
324         m_dummyWindow = nsnow;
325     }
326     m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
327 }
328
329 void wxGUIEventLoop::EndModalSession()
330 {
331     wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
332     [NSApp endModalSession:(NSModalSession)m_modalSession];
333     m_modalSession = nil;
334     if ( m_dummyWindow )
335     {
336         [m_dummyWindow release];
337         m_dummyWindow = nil;
338     }
339 }
340
341 //
342 // 
343 //
344
345 wxWindowDisabler::wxWindowDisabler(bool disable)
346 {
347     m_modalEventLoop = NULL;
348     m_disabled = disable;
349     if ( disable )
350         DoDisable();
351 }
352
353 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
354 {
355     m_disabled = true;
356     DoDisable(winToSkip);
357 }
358
359 void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
360 {    
361     // remember the top level windows which were already disabled, so that we
362     // don't reenable them later
363     m_winDisabled = NULL;
364     
365     wxWindowList::compatibility_iterator node;
366     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
367     {
368         wxWindow *winTop = node->GetData();
369         if ( winTop == winToSkip )
370             continue;
371         
372         // we don't need to disable the hidden or already disabled windows
373         if ( winTop->IsEnabled() && winTop->IsShown() )
374         {
375             winTop->Disable();
376         }
377         else
378         {
379             if ( !m_winDisabled )
380             {
381                 m_winDisabled = new wxWindowList;
382             }
383             
384             m_winDisabled->Append(winTop);
385         }
386     }
387     
388     m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
389     m_modalEventLoop->BeginModalSession(winToSkip);
390 }
391
392 wxWindowDisabler::~wxWindowDisabler()
393 {
394     if ( !m_disabled )
395         return;
396     
397     m_modalEventLoop->EndModalSession();
398     
399     wxWindowList::compatibility_iterator node;
400     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
401     {
402         wxWindow *winTop = node->GetData();
403         if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
404         {
405             winTop->Enable();
406         }
407         //else: had been already disabled, don't reenable
408     }
409     
410     delete m_winDisabled;
411 }
412