From eb537cfb1cc0066c0d53a2415972afa6e303c4b5 Mon Sep 17 00:00:00 2001 From: David Elliott Date: Mon, 15 Dec 2003 15:57:42 +0000 Subject: [PATCH] Improved idle event processing. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@24870 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/cocoa/app.h | 6 +- src/cocoa/app.mm | 124 +++++++++++++++++++---------------------- src/cocoa/evtloop.mm | 16 ++---- src/cocoa/toplevel.mm | 4 -- 4 files changed, 63 insertions(+), 87 deletions(-) diff --git a/include/wx/cocoa/app.h b/include/wx/cocoa/app.h index e8a604544a..d0e7c78261 100644 --- a/include/wx/cocoa/app.h +++ b/include/wx/cocoa/app.h @@ -33,8 +33,6 @@ public: // ------------------------------------------------------------------------ public: inline WX_NSApplication GetNSApplication() { return m_cocoaApp; } - void CocoaInstallRequestedIdleHandler() { if(m_isIdle) CocoaInstallIdleHandler(); } - inline void CocoaRequestIdle() { m_isIdle = true; } virtual void CocoaDelegate_applicationWillBecomeActive(); virtual void CocoaDelegate_applicationDidBecomeActive(); virtual void CocoaDelegate_applicationWillResignActive(); @@ -43,8 +41,6 @@ protected: WX_NSApplication m_cocoaApp; struct objc_object *m_cocoaAppDelegate; WX_NSThread m_cocoaMainThread; - void CocoaInstallIdleHandler(); - bool m_isIdle; // ------------------------------------------------------------------------ // Implementation @@ -54,7 +50,7 @@ public: virtual void Exit(); virtual bool Yield(bool onlyIfNeeded = FALSE); - virtual void WakeUpIdle() { CocoaRequestIdle(); } + virtual void WakeUpIdle(); virtual bool Initialize(int& argc, wxChar **argv); virtual void CleanUp(); diff --git a/src/cocoa/app.mm b/src/cocoa/app.mm index 4ef6974b2f..99eb4261c8 100644 --- a/src/cocoa/app.mm +++ b/src/cocoa/app.mm @@ -36,12 +36,15 @@ #import #import #import +#import // ======================================================================== // wxPoseAsInitializer // ======================================================================== wxPoseAsInitializer *wxPoseAsInitializer::sm_first = NULL; +static bool sg_needIdle = true; + // ======================================================================== // wxPoserNSApplication // ======================================================================== @@ -49,6 +52,7 @@ wxPoseAsInitializer *wxPoseAsInitializer::sm_first = NULL; { } +- (NSEvent *)nextEventMatchingMask:(unsigned int)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)flag; - (void)sendEvent: (NSEvent*)anEvent; @end // wxPoserNSApplication @@ -56,10 +60,53 @@ WX_IMPLEMENT_POSER(wxPoserNSApplication); @implementation wxPoserNSApplication : NSApplication +/* NOTE: The old method of idle event handling added the handler using the + [NSRunLoop -performSelector:target:argument:order:modes] which caused + the invocation to occur at the begining of [NSApplication + -nextEventMatchingMask:untilDate:expiration:inMode:dequeue:]. However, + the code would be scheduled for invocation with every iteration of + the event loop. This new method simply overrides the method. The + same caveats apply. In particular, by the time the event loop has + called this method, it usually expects to receive an event. If you + plan on stopping the event loop, it is wise to send an event through + the queue to ensure this method will return. + See wxEventLoop::Exit() for more information. +*/ + +- (NSEvent *)nextEventMatchingMask:(unsigned int)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)flag +{ + // Get the same events except don't block + NSEvent *event = [super nextEventMatchingMask:mask untilDate:nil/* equivalent to [NSDate distantPast] */ inMode:mode dequeue:flag]; + // If we got one, simply return it + if(event) + return event; + // No events, try doing some idle stuff + if(sg_needIdle && !wxTheApp->IsInAssert() && ([NSDefaultRunLoopMode isEqualToString:mode] || [NSModalPanelRunLoopMode isEqualToString:mode])) + { + sg_needIdle = false; + wxLogDebug("Processing idle events"); + while(wxTheApp->ProcessIdle()) + { + // Get the same events except don't block + NSEvent *event = [super nextEventMatchingMask:mask untilDate:nil/* equivalent to [NSDate distantPast] */ inMode:mode dequeue:flag]; + // If we got one, simply return it + if(event) + return event; + // we didn't get one, do some idle work + wxLogDebug("Looping idle events"); + } + // No more idle work requested, block + wxLogDebug("Finished idle processing"); + } + else + wxLogDebug("Avoiding idle processing sg_needIdle=%d",sg_needIdle); + return [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue:flag]; +} + - (void)sendEvent: (NSEvent*)anEvent { wxLogDebug("SendEvent"); - wxTheApp->CocoaInstallRequestedIdleHandler(); + sg_needIdle = true; [super sendEvent: anEvent]; } @@ -72,7 +119,6 @@ WX_IMPLEMENT_POSER(wxPoserNSApplication); { } -- (void)doIdle: (id)data; // Delegate methods - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication; - (void)applicationWillBecomeActive:(NSNotification *)notification; @@ -83,38 +129,6 @@ WX_IMPLEMENT_POSER(wxPoserNSApplication); @implementation wxNSApplicationDelegate : NSObject -- (void)doIdle: (id)data -{ - wxASSERT(wxTheApp); - wxASSERT(wxMenuBarManager::GetInstance()); - wxLogDebug("doIdle called"); -#ifdef __WXDEBUG__ - if(wxTheApp->IsInAssert()) - { - wxLogDebug("Idle events ignored durring assertion dialog"); - } - else -#endif - { - NSRunLoop *rl = [NSRunLoop currentRunLoop]; - // runMode: beforeDate returns YES if something was done - while(wxTheApp->ProcessIdle()) // FIXME: AND NO EVENTS ARE PENDING - { - wxLogDebug("Looping for idle events"); - #if 1 - if( [rl runMode:[rl currentMode] beforeDate:[NSDate distantPast]]) - { - wxLogDebug("Found actual work to do"); - break; - } - #endif - } - } - wxLogDebug("Idle processing complete, requesting next idle event"); - // Add ourself back into the run loop (on next event) if necessary - wxTheApp->CocoaRequestIdle(); -} - // NOTE: Terminate means that the event loop does NOT return and thus // cleanup code doesn't properly execute. Furthermore, wxWindows has its // own exit on frame delete mechanism. @@ -209,7 +223,6 @@ wxApp::wxApp() { m_topWindow = NULL; - m_isIdle = true; #if WXWIN_COMPATIBILITY_2_2 m_wantDebugOutput = TRUE; #endif @@ -223,34 +236,6 @@ wxApp::wxApp() m_cocoaAppDelegate = NULL; } -void wxApp::CocoaInstallIdleHandler() -{ - // If we're not the main thread, don't install the idle handler - if(m_cocoaMainThread != [NSThread currentThread]) - { - wxLogDebug("Attempt to install idle handler from secondary thread"); - return; - } - // If we're supposed to be stopping, don't add more idle events - if(![m_cocoaApp isRunning]) - return; - wxLogDebug("wxApp::CocoaInstallIdleHandler"); - m_isIdle = false; - // Call doIdle for EVERYTHING dammit -// We'd need Foundation/NSConnection.h for this next constant, do we need it? - [[ NSRunLoop currentRunLoop ] performSelector:@selector(doIdle:) target:m_cocoaAppDelegate argument:NULL order:0 modes:[NSArray arrayWithObjects:NSDefaultRunLoopMode, /* NSConnectionReplyRunLoopMode,*/ NSModalPanelRunLoopMode, /**/NSEventTrackingRunLoopMode,/**/ nil] ]; - /* Notes: - In the Mac OS X implementation of Cocoa, the above method schedules - doIdle: to be called from *within* [NSApplication - -nextEventMatchingMask:untilDate:inMode:dequeue:]. That is, no - NSEvent object is generated and control does not return from that - method. In fact, control will only return from that method for the - usual reasons (e.g. a real event is received or the untilDate is reached). - This has implications when trying to stop the event loop and return to - its caller. See wxEventLoop::Exit - */ -} - void wxApp::CocoaDelegate_applicationWillBecomeActive() { } @@ -283,11 +268,6 @@ bool wxApp::OnInitGui() wxDC::CocoaInitializeTextSystem(); // [ m_cocoaApp setDelegate:m_cocoaApp ]; - #if 0 - wxLogDebug("Just for kicks"); - [ m_cocoaAppDelegate performSelector:@selector(doIdle:) withObject:NULL ]; - wxLogDebug("okay.. done now"); - #endif return TRUE; } @@ -356,6 +336,14 @@ bool wxApp::Yield(bool onlyIfNeeded) return true; } +void wxApp::WakeUpIdle() +{ + [m_cocoaApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint modifierFlags:NSAnyEventMask + timestamp:0 windowNumber:0 context:nil + subtype:0 data1:0 data2:0] atStart:NO]; +} + #ifdef __WXDEBUG__ void wxApp::OnAssert(const wxChar *file, int line, const wxChar* cond, const wxChar *msg) { diff --git a/src/cocoa/evtloop.mm b/src/cocoa/evtloop.mm index 9381f37e59..c6ab3ea5c6 100644 --- a/src/cocoa/evtloop.mm +++ b/src/cocoa/evtloop.mm @@ -12,6 +12,7 @@ #include "wx/wxprec.h" #ifndef WX_PRECOMP #include "wx/log.h" + #include "wx/app.h" #endif //WX_PRECOMP #include "wx/evtloop.h" @@ -88,17 +89,12 @@ void wxEventLoop::Exit(int rc) NSApplication *cocoaApp = [NSApplication sharedApplication]; wxLogDebug("wxEventLoop::Exit isRunning=%d", (int)[cocoaApp isRunning]); - // This works around a bug in Cocoa. - [NSEvent startPeriodicEventsAfterDelay:0.0 withPeriod:5.0]; + wxTheApp->WakeUpIdle(); /* Notes: - This function is most often called during idle time. See - wxApp::CocoaInstallIdleHandler() for an overview of the implications - of idle event time. In short, Cocoa must have at least one real event - in the queue (of which an idle "event" is not) in order for it to - realize that the application has been stopped. The above method - generates the first periodic event immediately, and would generate - further events every 5 seconds if not for the fact that the next - method stops the event loop. + If we're being called from idle time (which occurs while checking the + queue for new events) there may or may not be any events in the queue. + In order to successfully stop the event loop, at least one event must + be processed. To ensure this always happens, WakeUpIdle is called. If the application was active when closed then this is unnecessary because it would receive a deactivate event anyway. However, if the diff --git a/src/cocoa/toplevel.mm b/src/cocoa/toplevel.mm index 7a7c11cbb0..a7c3f22380 100644 --- a/src/cocoa/toplevel.mm +++ b/src/cocoa/toplevel.mm @@ -225,10 +225,6 @@ void wxTopLevelWindowCocoa::CocoaDelegate_windowWillClose(void) { m_closed = true; Destroy(); - /* Be SURE that idle events get ran. If the window was not active when - it was closed, then there will be no more events to trigger this and - therefore it must be done here */ - wxTheApp->CocoaInstallRequestedIdleHandler(); } bool wxTopLevelWindowCocoa::CocoaDelegate_windowShouldClose() -- 2.45.2