fix positioning bug with window disabler and hidden windows
[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         // we must show now, otherwise beginModalSessionForWindow does it but it
311         // also would do a centering of the window before overriding all our position
312         if ( !modalWindow->IsShownOnScreen() )
313             modalWindow->Show();
314         
315         wxNonOwnedWindow* now = dynamic_cast<wxNonOwnedWindow*> (modalWindow);
316         wxASSERT_MSG( now != NULL, "must pass in a toplevel window for modal event loop" );
317         nsnow = now ? now->GetWXWindow() : nil;
318     }
319     else
320     {
321         NSRect r = NSMakeRect(10, 10, 0, 0);
322         nsnow = [NSPanel alloc];
323         [nsnow initWithContentRect:r
324                                styleMask:NSBorderlessWindowMask
325                                  backing:NSBackingStoreBuffered
326                                    defer:YES
327          ];
328         [nsnow orderOut:nil];
329         m_dummyWindow = nsnow;
330     }
331     m_modalSession = [NSApp beginModalSessionForWindow:nsnow];
332 }
333
334 void wxGUIEventLoop::EndModalSession()
335 {
336     wxASSERT_MSG(m_modalSession != NULL, "no modal session active");
337     [NSApp endModalSession:(NSModalSession)m_modalSession];
338     m_modalSession = nil;
339     if ( m_dummyWindow )
340     {
341         [m_dummyWindow release];
342         m_dummyWindow = nil;
343     }
344 }
345
346 //
347 // 
348 //
349
350 wxWindowDisabler::wxWindowDisabler(bool disable)
351 {
352     m_modalEventLoop = NULL;
353     m_disabled = disable;
354     if ( disable )
355         DoDisable();
356 }
357
358 wxWindowDisabler::wxWindowDisabler(wxWindow *winToSkip)
359 {
360     m_disabled = true;
361     DoDisable(winToSkip);
362 }
363
364 void wxWindowDisabler::DoDisable(wxWindow *winToSkip)
365 {    
366     // remember the top level windows which were already disabled, so that we
367     // don't reenable them later
368     m_winDisabled = NULL;
369     
370     wxWindowList::compatibility_iterator node;
371     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
372     {
373         wxWindow *winTop = node->GetData();
374         if ( winTop == winToSkip )
375             continue;
376         
377         // we don't need to disable the hidden or already disabled windows
378         if ( winTop->IsEnabled() && winTop->IsShown() )
379         {
380             winTop->Disable();
381         }
382         else
383         {
384             if ( !m_winDisabled )
385             {
386                 m_winDisabled = new wxWindowList;
387             }
388             
389             m_winDisabled->Append(winTop);
390         }
391     }
392     
393     m_modalEventLoop = (wxEventLoop*)wxEventLoopBase::GetActive();
394     m_modalEventLoop->BeginModalSession(winToSkip);
395 }
396
397 wxWindowDisabler::~wxWindowDisabler()
398 {
399     if ( !m_disabled )
400         return;
401     
402     m_modalEventLoop->EndModalSession();
403     
404     wxWindowList::compatibility_iterator node;
405     for ( node = wxTopLevelWindows.GetFirst(); node; node = node->GetNext() )
406     {
407         wxWindow *winTop = node->GetData();
408         if ( !m_winDisabled || !m_winDisabled->Find(winTop) )
409         {
410             winTop->Enable();
411         }
412         //else: had been already disabled, don't reenable
413     }
414     
415     delete m_winDisabled;
416 }
417