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