1///////////////////////////////////////////////////////////////////////////////
2// Name:        src/cocoa/evtloop.mm
3// Purpose:     implements wxEventLoop for Cocoa
4// Author:      David Elliott
5// Created:     2003/10/02
6// Copyright:   (c) 2003 David Elliott <dfe@cox.net>
7//              (c) 2013 Rob Bresalier
8// Licence:     wxWindows licence
9///////////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12
13#include "wx/evtloop.h"
14
15#ifndef WX_PRECOMP
16    #include "wx/log.h"
17    #include "wx/app.h"
18#endif //WX_PRECOMP
19
20#import <AppKit/NSApplication.h>
21#import <AppKit/NSEvent.h>
22#import <Foundation/NSRunLoop.h>
23
24// ========================================================================
25// wxGUIEventLoop
26// ========================================================================
27
28// ----------------------------------------------------------------------------
29// wxGUIEventLoop running and exiting
30// ----------------------------------------------------------------------------
31
32int wxGUIEventLoop::DoRun()
33{
34    [[NSApplication sharedApplication] run];
35
36    OnExit();
37
38    return m_exitcode;
39}
40
41void wxGUIEventLoop::ScheduleExit(int rc)
42{
43    wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not started") );
44
45    m_exitcode = rc;
46
47    NSApplication *cocoaApp = [NSApplication sharedApplication];
48    wxLogTrace(wxTRACE_COCOA,wxT("wxEventLoop::Exit isRunning=%d"), (int)[cocoaApp isRunning]);
49    wxTheApp->WakeUpIdle();
50    /* Notes:
51    If we're being called from idle time (which occurs while checking the
52    queue for new events) there may or may not be any events in the queue.
53    In order to successfully stop the event loop, at least one event must
54    be processed.  To ensure this always happens, WakeUpIdle is called.
55
56    If the application was active when closed then this is unnecessary
57    because it would receive a deactivate event anyway.  However, if the
58    application was not active when closed, then no events would be
59    added to the queue by Cocoa and thus the application would wait
60    indefinitely for the next event.
61    */
62    [cocoaApp stop: cocoaApp];
63}
64
65// ----------------------------------------------------------------------------
66// wxEventLoop message processing dispatching
67// ----------------------------------------------------------------------------
68
69bool wxGUIEventLoop::Pending() const
70{
71    // a pointer to the event is returned if there is one, or nil if not
72    return [[NSApplication sharedApplication]
73            nextEventMatchingMask: NSAnyEventMask
74            untilDate: nil /* Equivalent to [NSDate distantPast] */
75            inMode: NSDefaultRunLoopMode
76            dequeue: NO];
77}
78
79bool wxGUIEventLoop::Dispatch()
80{
81    // This check is required by wxGTK but probably not really for wxCocoa
82    // Keep it here to encourage developers to write cross-platform code
83    wxCHECK_MSG( IsRunning(), false, wxT("can't call Dispatch() if not running") );
84    NSApplication *cocoaApp = [NSApplication sharedApplication];
85    // Block to retrieve an event then send it
86    if(NSEvent *event = [cocoaApp
87                nextEventMatchingMask:NSAnyEventMask
88                untilDate:[NSDate distantFuture]
89                inMode:NSDefaultRunLoopMode
90                dequeue: YES])
91    {
92        [cocoaApp sendEvent: event];
93    }
94
95    return true;
96}
97
98int wxGUIEventLoop::DispatchTimeout(unsigned long timeout)
99{
100    NSApplication *cocoaApp = [NSApplication sharedApplication];
101    NSEvent *event = [cocoaApp
102                nextEventMatchingMask:NSAnyEventMask
103                untilDate:[[NSDate alloc] initWithTimeIntervalSinceNow:timeout/1000]
104                inMode:NSDefaultRunLoopMode
105                dequeue: YES];
106    if ( !event )
107        return -1;
108
109    [cocoaApp sendEvent: event];
110
111    return true;
112}
113
114bool wxGUIEventLoop::YieldFor(long eventsToProcess)
115{
116#if wxUSE_LOG
117    // disable log flushing from here because a call to wxYield() shouldn't
118    // normally result in message boxes popping up &c
119    wxLog::Suspend();
120#endif // wxUSE_LOG
121
122    m_isInsideYield = true;
123    m_eventsToProcessInsideYield = eventsToProcess;
124
125    // Run the event loop until it is out of events
126    while (1)
127    {
128        // TODO: implement event filtering using the eventsToProcess mask
129
130        wxAutoNSAutoreleasePool pool;
131        /*  NOTE: It may be better to use something like
132            NSEventTrackingRunLoopMode since we don't necessarily want all
133            timers/sources/observers to run, only those which would
134            run while tracking events.  However, it should be noted that
135            NSEventTrackingRunLoopMode is in the common set of modes
136            so it may not effectively make much of a difference.
137         */
138        NSEvent *event = [GetNSApplication()
139                nextEventMatchingMask:NSAnyEventMask
140                untilDate:[NSDate distantPast]
141                inMode:NSDefaultRunLoopMode
142                dequeue: YES];
143        if(!event)
144            break;
145        [GetNSApplication() sendEvent: event];
146    }
147
148    /*
149        Because we just told NSApplication to avoid blocking it will in turn
150        run the CFRunLoop with a timeout of 0 seconds.  In that case, our
151        run loop observer on kCFRunLoopBeforeWaiting never fires because
152        no waiting occurs.  Therefore, no idle events are sent.
153
154        Believe it or not, this is actually desirable because we do not want
155        to process idle from here.  However, we do want to process pending
156        events because some user code expects to do work in a thread while
157        the main thread waits and then notify the main thread by posting
158        an event.
159     */
160    if (wxTheApp)
161        wxTheApp->ProcessPendingEvents();
162
163#if wxUSE_LOG
164    // let the logs be flashed again
165    wxLog::Resume();
166#endif // wxUSE_LOG
167
168    m_isInsideYield = false;
169
170    return true;
171}
172