Prevent Cocoa from waiting indefinitely when stopping the event loop
[wxWidgets.git] / src / cocoa / evtloop.mm
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        cocoa/evtloop.mm
3 // Purpose:     implements wxEventLoop for Cocoa
4 // Author:      David Elliott
5 // Modified by:
6 // Created:     2003/10/02
7 // RCS-ID:      $Id$
8 // Copyright:   (c) 2003 David Elliott <dfe@cox.net>
9 // License:     wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13 #ifndef WX_PRECOMP
14     #include "wx/log.h"
15 #endif //WX_PRECOMP
16
17 #include "wx/evtloop.h"
18
19 #import <AppKit/NSApplication.h>
20 #import <AppKit/NSEvent.h>
21 #import <Foundation/NSRunLoop.h>
22
23 // ========================================================================
24 // wxEventLoopImpl
25 // ========================================================================
26
27 class WXDLLEXPORT wxEventLoopImpl
28 {
29 public:
30     // ctor
31     wxEventLoopImpl() { SetExitCode(0); }
32
33     // set/get the exit code
34     void SetExitCode(int exitcode) { m_exitcode = exitcode; }
35     int GetExitCode() const { return m_exitcode; }
36
37 private:
38     // the exit code of the event loop
39     int m_exitcode;
40 };
41
42 // ========================================================================
43 // wxEventLoop
44 // ========================================================================
45
46 // ----------------------------------------------------------------------------
47 // wxEventLoop running and exiting
48 // ----------------------------------------------------------------------------
49
50 wxEventLoop *wxEventLoop::ms_activeLoop = NULL;
51
52 wxEventLoop::~wxEventLoop()
53 {
54     wxASSERT_MSG( !m_impl, _T("should have been deleted in Run()") );
55 }
56
57 bool wxEventLoop::IsRunning() const
58 {
59     return m_impl;
60 }
61
62 int wxEventLoop::Run()
63 {
64     // event loops are not recursive, you need to create another loop!
65     wxCHECK_MSG( !IsRunning(), -1, _T("can't reenter a message loop") );
66
67     wxEventLoop *oldLoop = ms_activeLoop;
68     ms_activeLoop = this;
69
70     m_impl = new wxEventLoopImpl;
71
72     [[NSApplication sharedApplication] run];
73
74     int exitcode = m_impl->GetExitCode();
75     delete m_impl;
76     m_impl = NULL;
77
78     ms_activeLoop = oldLoop;
79
80     return exitcode;
81 }
82
83 void wxEventLoop::Exit(int rc)
84 {
85     wxCHECK_RET( IsRunning(), _T("can't call Exit() if not running") );
86
87     m_impl->SetExitCode(rc);
88
89     NSApplication *cocoaApp = [NSApplication sharedApplication];
90     wxLogDebug("wxEventLoop::Exit isRunning=%d", (int)[cocoaApp isRunning]);
91     // This works around a bug in Cocoa.
92     [NSEvent startPeriodicEventsAfterDelay:0.0 withPeriod:5.0];
93     /* Notes:
94     This function is most often called during idle time.  See
95     wxApp::CocoaInstallIdleHandler() for an overview of the implications
96     of idle event time.  In short, Cocoa must have at least one real event
97     in the queue (of which an idle "event" is not) in order for it to
98     realize that the application has been stopped.  The above method
99     generates the first periodic event immediately, and would generate
100     further events every 5 seconds if not for the fact that the next
101     method stops the event loop.
102
103     If the application was active when closed then this is unnecessary
104     because it would receive a deactivate event anyway.  However, if the
105     application was not active when closed, then no events would be
106     added to the queue by Cocoa and thus the application would wait
107     indefinitely for the next event.
108     */
109     [cocoaApp stop: cocoaApp];
110 }
111
112 // ----------------------------------------------------------------------------
113 // wxEventLoop message processing dispatching
114 // ----------------------------------------------------------------------------
115
116 bool wxEventLoop::Pending() const
117 {
118     // a pointer to the event is returned if there is one, or nil if not
119     return [[NSApplication sharedApplication]
120             nextEventMatchingMask: NSAnyEventMask
121             untilDate: nil /* Equivalent to [NSDate distantPast] */
122             inMode: NSDefaultRunLoopMode
123             dequeue: NO];
124 }
125
126 bool wxEventLoop::Dispatch()
127 {
128     // This check is required by wxGTK but probably not really for wxCocoa
129     // Keep it here to encourage developers to write cross-platform code
130     wxCHECK_MSG( IsRunning(), false, _T("can't call Dispatch() if not running") );
131     NSApplication *cocoaApp = [NSApplication sharedApplication];
132     // Block to retrieve an event then send it
133     if(NSEvent *event = [cocoaApp
134                 nextEventMatchingMask:NSAnyEventMask
135                 untilDate:[NSDate distantFuture]
136                 inMode:NSDefaultRunLoopMode
137                 dequeue: YES])
138     {
139         [cocoaApp sendEvent: event];
140         return true;
141     }
142     return false;
143 }
144