From 9af42efda6c78093872a67180d43d5eeba261fee Mon Sep 17 00:00:00 2001
From: Vadim Zeitlin <vadim@wxwidgets.org>
Date: Fri, 26 Dec 2008 22:28:34 +0000
Subject: [PATCH] added wxEventLoop::DispatchTimeout()

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@57571 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
---
 docs/changes.txt                |  1 +
 include/wx/dfb/evtloop.h        |  1 +
 include/wx/evtloop.h            | 10 ++++
 include/wx/msw/evtloop.h        |  9 ++++
 include/wx/osx/carbon/evtloop.h | 10 +++-
 include/wx/palmos/evtloop.h     |  1 +
 include/wx/unix/evtloop.h       |  1 +
 interface/wx/evtloop.h          | 18 ++++++++
 src/common/evtloopcmn.cpp       | 20 ++++++++
 src/dfb/evtloop.cpp             | 22 ++++++---
 src/msw/evtloop.cpp             | 82 +++++++++++++++++++++++++++++----
 src/osx/carbon/evtloop.cpp      | 44 ++++++++++++++----
 src/palmos/evtloop.cpp          |  5 ++
 src/unix/evtloopunix.cpp        | 28 ++++++-----
 14 files changed, 215 insertions(+), 37 deletions(-)

diff --git a/docs/changes.txt b/docs/changes.txt
index f08a7c08c3..0941ac08bd 100644
--- a/docs/changes.txt
+++ b/docs/changes.txt
@@ -291,6 +291,7 @@ All:
 - Added wxMemoryInputStream(wxInputStream&) ctor (Stas Sergeev).
 - Implemented wxMemoryInputStream::CanRead().
 - Implemented wxMemoryFSHandler::FindFirst/Next().
+- Added wxEventLoop::DispatchTimeout().
 - Added wxEXEC_BLOCK flag (Hank Schultz).
 - Add support for wxStream-derived classes to wxRTTI (Stas Sergeev).
 - Added wxStreamBuffer::Truncate() (Stas Sergeev).
diff --git a/include/wx/dfb/evtloop.h b/include/wx/dfb/evtloop.h
index 80c0b1f069..4c3380819a 100644
--- a/include/wx/dfb/evtloop.h
+++ b/include/wx/dfb/evtloop.h
@@ -27,6 +27,7 @@ public:
 
     virtual bool Pending() const;
     virtual bool Dispatch();
+    virtual int DispatchTimeout(unsigned long timeout);
 
     // returns DirectFB event buffer used by wx
     static wxIDirectFBEventBufferPtr GetDirectFBEventBuffer();
diff --git a/include/wx/evtloop.h b/include/wx/evtloop.h
index bb22bb84db..5d78a3bf56 100644
--- a/include/wx/evtloop.h
+++ b/include/wx/evtloop.h
@@ -31,6 +31,7 @@ public:
     // using it
     virtual bool IsOk() const { return true; }
 
+
     // start the event loop, return the exit code when it is finished
     virtual int Run() = 0;
 
@@ -43,6 +44,12 @@ public:
     // dispatch a single event, return false if we should exit from the loop
     virtual bool Dispatch() = 0;
 
+    // same as Dispatch() but doesn't wait for longer than the specified (in
+    // ms) timeout, return true if an event was processed, false if we should
+    // exit the loop or -1 if timeout expired
+    virtual int DispatchTimeout(unsigned long timeout) = 0;
+
+
     // return currently active (running) event loop, may be NULL
     static wxEventLoopBase *GetActive() { return ms_activeLoop; }
 
@@ -121,6 +128,8 @@ protected:
     #include "wx/dfb/evtloop.h"
 #else // other platform
 
+#define wxNEEDS_GENERIC_DISPATCH_TIMEOUT
+
 class WXDLLIMPEXP_FWD_CORE wxEventLoopImpl;
 
 class WXDLLIMPEXP_CORE wxGUIEventLoop : public wxEventLoopBase
@@ -133,6 +142,7 @@ public:
     virtual void Exit(int rc = 0);
     virtual bool Pending() const;
     virtual bool Dispatch();
+    virtual int DispatchTimeout(unsigned long timeout);
     virtual void WakeUp() { }
 
 protected:
diff --git a/include/wx/msw/evtloop.h b/include/wx/msw/evtloop.h
index fd548b87f2..88ab13492b 100644
--- a/include/wx/msw/evtloop.h
+++ b/include/wx/msw/evtloop.h
@@ -32,6 +32,10 @@ protected:
     // get the next message from queue and return true or return false if we
     // got WM_QUIT or an error occurred
     bool GetNextMessage(WXMSG *msg);
+
+    // same as above but with a timeout and return value can be -1 meaning that
+    // time out expired in addition to
+    int GetNextMessageTimeout(WXMSG *msg, unsigned long timeout);
 };
 
 #if wxUSE_GUI
@@ -66,6 +70,7 @@ public:
 
     // override/implement base class virtuals
     virtual bool Dispatch();
+    virtual int DispatchTimeout(unsigned long timeout);
     virtual void WakeUp();
 
 protected:
@@ -92,8 +97,12 @@ public:
 
     // override/implement base class virtuals
     virtual bool Dispatch();
+    virtual int DispatchTimeout(unsigned long timeout);
     virtual void WakeUp();
 
+    // MSW-specific function to process a single message
+    virtual void ProcessMessage(WXMSG *msg);
+
 protected:
     virtual void OnNextIteration();
 };
diff --git a/include/wx/osx/carbon/evtloop.h b/include/wx/osx/carbon/evtloop.h
index 60f5704574..b55112994f 100644
--- a/include/wx/osx/carbon/evtloop.h
+++ b/include/wx/osx/carbon/evtloop.h
@@ -12,17 +12,25 @@
 #ifndef _WX_MAC_CARBON_EVTLOOP_H_
 #define _WX_MAC_CARBON_EVTLOOP_H_
 
+class OpaqueEventRef;
+typedef OpaqueEventRef *EventRef;
+
 class WXDLLIMPEXP_CORE wxGUIEventLoop : public wxEventLoopManual
 {
 public:
     wxGUIEventLoop();
 
+    // implement/override base class pure virtual
     virtual bool Pending() const;
     virtual bool Dispatch();
+    virtual int DispatchTimeout(unsigned long timeout);
 
-    // implement base class pure virtual
     virtual void WakeUp();
+
 private:
+    // dispatch an event and release it
+    void DispatchAndReleaseEvent(EventRef event);
+
     double      m_sleepTime;
 };
 
diff --git a/include/wx/palmos/evtloop.h b/include/wx/palmos/evtloop.h
index 6cd10d6245..36341946e8 100644
--- a/include/wx/palmos/evtloop.h
+++ b/include/wx/palmos/evtloop.h
@@ -26,6 +26,7 @@ public:
     virtual void Exit(int rc = 0);
     virtual bool Pending() const;
     virtual bool Dispatch();
+    virtual int DispatchTimeout(unsigned long timeout);
     virtual bool IsRunning() const;
 
     // MSW-specific methods
diff --git a/include/wx/unix/evtloop.h b/include/wx/unix/evtloop.h
index 3f5b54913c..ddb101516f 100644
--- a/include/wx/unix/evtloop.h
+++ b/include/wx/unix/evtloop.h
@@ -29,6 +29,7 @@ public:
     // implement base class pure virtuals
     virtual bool Pending() const;
     virtual bool Dispatch();
+    virtual int DispatchTimeout(unsigned long timeout);
     virtual void WakeUp();
     virtual bool IsOk() const { return m_dispatcher != NULL; }
 
diff --git a/interface/wx/evtloop.h b/interface/wx/evtloop.h
index d4a7966a95..be246731b5 100644
--- a/interface/wx/evtloop.h
+++ b/interface/wx/evtloop.h
@@ -82,6 +82,24 @@ public:
      */
     virtual bool Dispatch() = 0;
 
+    /**
+        Dispatch an event but not wait longer than the specified timeout for
+        it.
+
+        If an event is received before the specified @a timeout expires, it is
+        processed and the function returns 1 normally or 0 if the event loop
+        should quite. Otherwise, i.e. if the timeout expires, the functions
+        returns -1 without processing any events.
+
+        @param timeout
+            The maximal time to wait for the events in milliseconds.
+
+        @return
+            1 if an event was processed, 0 if the event loop should quit or -1
+            if the timeout expired.
+     */
+    virtual int DispatchTimeout(unsigned long timeout) = 0;
+
     /**
         Return true if this event loop is currently running.
 
diff --git a/src/common/evtloopcmn.cpp b/src/common/evtloopcmn.cpp
index 3db4ddacf6..997718b44a 100644
--- a/src/common/evtloopcmn.cpp
+++ b/src/common/evtloopcmn.cpp
@@ -153,3 +153,23 @@ void wxEventLoopManual::Exit(int rc)
 }
 
 #endif // __WXMSW__ || __WXMAC__ || __WXDFB__
+
+#ifdef wxNEEDS_GENERIC_DISPATCH_TIMEOUT
+
+int wxGUIEventLoop::DispatchTimeout(unsigned long timeout)
+{
+    // TODO: this is, of course, horribly inefficient and a proper wait with
+    //       timeout should be implemented for all ports natively...
+    const wxMilliClock_t timeEnd = wxGetLocalTimeMillis() + timeout;
+    for ( ;; )
+    {
+        if ( Pending() )
+            return Dispatch();
+
+        if ( wxGetLocalTimeMillis() >= timeEnd )
+            return -1;
+    }
+}
+
+#endif // wxNEEDS_GENERIC_DISPATCH_TIMEOUT
+
diff --git a/src/dfb/evtloop.cpp b/src/dfb/evtloop.cpp
index 6c6bef402d..7f7807ac72 100644
--- a/src/dfb/evtloop.cpp
+++ b/src/dfb/evtloop.cpp
@@ -83,18 +83,28 @@ bool wxGUIEventLoop::Pending() const
 
 bool wxGUIEventLoop::Dispatch()
 {
-    wxCHECK_MSG( ms_buffer, false, "invalid event buffer" );
-
     // NB: we don't block indefinitely waiting for an event, but instead
     //     time out after a brief period in order to make sure that
     //     OnNextIteration() will be called frequently enough
+    //
+    // TODO: remove this hack, instead use CreateFileDescriptor() to properly
+    //       multiplex GUI and socket input
     const int TIMEOUT = 100;
 
+    // treat time out (-1 return value) as normal successful return so that
+    // OnNextIteration() is called
+    return !!DispatchTimeout(TIMEOUT);
+}
+
+int wxGUIEventLoop::DispatchTimeout(unsigned long timeout)
+{
+    wxCHECK_MSG( ms_buffer, 0, "invalid event buffer" );
+
     // release the GUI mutex so that other threads have a chance to post
     // events:
     wxMutexGuiLeave();
 
-    bool rv = ms_buffer->WaitForEventWithTimeout(0, TIMEOUT);
+    bool rv = ms_buffer->WaitForEventWithTimeout(0, timeout);
 
     // and acquire it back before calling any event handlers:
     wxMutexGuiEnter();
@@ -112,9 +122,7 @@ bool wxGUIEventLoop::Dispatch()
             }
 
             case DFB_TIMEOUT:
-                // timed out, pretend we processed an event so that
-                // OnNextIteration is called
-                break;
+                return -1;
 
             default:
                 // don't terminate the loop due to errors (they were reported
@@ -123,7 +131,7 @@ bool wxGUIEventLoop::Dispatch()
         }
     }
 
-    return true;
+    return 1;
 }
 
 void wxGUIEventLoop::WakeUp()
diff --git a/src/msw/evtloop.cpp b/src/msw/evtloop.cpp
index 601b8b870b..7c1ab86c19 100644
--- a/src/msw/evtloop.cpp
+++ b/src/msw/evtloop.cpp
@@ -99,6 +99,41 @@ bool wxMSWEventLoopBase::GetNextMessage(WXMSG* msg)
     return true;
 }
 
+int wxMSWEventLoopBase::GetNextMessageTimeout(WXMSG *msg, unsigned long timeout)
+{
+    // MsgWaitForMultipleObjects() won't notice any input which was already
+    // examined (e.g. using PeekMessage()) but not yet removed from the queue
+    // so we need to remove any immediately messages manually
+    //
+    // NB: using MsgWaitForMultipleObjectsEx() could simplify the code here but
+    //     it is not available in very old Windows versions
+    if ( !::PeekMessage(msg, 0, 0, 0, PM_REMOVE) )
+    {
+        // we use this function just in order to not block longer than the
+        // given timeout, so we don't pass any handles to it at all
+        if ( ::MsgWaitForMultipleObjects
+               (
+                0, NULL,
+                FALSE,
+                timeout,
+                QS_ALLINPUT
+               ) == WAIT_TIMEOUT )
+        {
+            return -1;
+        }
+
+        if ( !::PeekMessage(msg, 0, 0, 0, PM_REMOVE) )
+        {
+            wxFAIL_MSG( _T("PeekMessage() should have succeeded") );
+
+            return -1;
+        }
+    }
+
+    return msg->message != WM_QUIT;
+}
+
+
 #endif // wxUSE_BASE
 
 #if wxUSE_GUI
@@ -289,6 +324,18 @@ bool wxGUIEventLoop::Dispatch()
     return true;
 }
 
+int wxGUIEventLoop::DispatchTimeout(unsigned long timeout)
+{
+    MSG msg;
+    int rc = GetNextMessageTimeout(&msg, timeout);
+    if ( rc != 1 )
+        return rc;
+
+    ProcessMessage(&msg);
+
+    return 1;
+}
+
 void wxGUIEventLoop::OnNextIteration()
 {
 #if wxUSE_THREADS
@@ -318,22 +365,39 @@ void wxConsoleEventLoop::WakeUp()
 #endif
 }
 
-bool wxConsoleEventLoop::Dispatch()
+void wxConsoleEventLoop::ProcessMessage(WXMSG *msg)
 {
-    MSG msg;
-    if ( !GetNextMessage(&msg) )
-        return false;
-
-    if ( msg.message == WM_TIMER )
+    if ( msg->message == WM_TIMER )
     {
-        TIMERPROC proc = (TIMERPROC)msg.lParam;
+        TIMERPROC proc = (TIMERPROC)msg->lParam;
         if ( proc )
-            (*proc)(NULL, 0, msg.wParam, 0);
+            (*proc)(NULL, 0, msg->wParam, 0);
     }
     else
     {
-        ::DispatchMessage(&msg);
+        ::DispatchMessage(msg);
     }
+}
+
+bool wxConsoleEventLoop::Dispatch()
+{
+    MSG msg;
+    if ( !GetNextMessage(&msg) )
+        return false;
+
+    ProcessMessage(&msg);
+
+    return !m_shouldExit;
+}
+
+int wxConsoleEventLoop::DispatchTimeout(unsigned long timeout)
+{
+    MSG msg;
+    int rc = GetNextMessageTimeout(&msg, timeout);
+    if ( rc != 1 )
+        return rc;
+
+    ProcessMessage(&msg);
 
     return !m_shouldExit;
 }
diff --git a/src/osx/carbon/evtloop.cpp b/src/osx/carbon/evtloop.cpp
index ec51ee972b..45aad41e25 100644
--- a/src/osx/carbon/evtloop.cpp
+++ b/src/osx/carbon/evtloop.cpp
@@ -48,6 +48,18 @@ void wxGUIEventLoop::WakeUp()
     wxMacWakeUp();
 }
 
+void wxGUIEventLoop::DispatchAndReleaseEvent(EventRef theEvent)
+{
+    if ( wxTheApp )
+        wxTheApp->MacSetCurrentEvent( theEvent, NULL );
+
+    OSStatus status = SendEventToEventTarget(theEvent, GetEventDispatcherTarget());
+    if (status == eventNotHandledErr && wxTheApp)
+        wxTheApp->MacHandleUnhandledEvent(theEvent);
+
+    ReleaseEvent( theEvent );
+}
+
 bool wxGUIEventLoop::Pending() const
 {
     EventRef theEvent;
@@ -95,17 +107,33 @@ bool wxGUIEventLoop::Dispatch()
             break;
 
         default:
-            if ( wxTheApp )
-                wxTheApp->MacSetCurrentEvent( theEvent, NULL );
-
-            OSStatus status = SendEventToEventTarget(theEvent, GetEventDispatcherTarget());
-            if (status == eventNotHandledErr && wxTheApp)
-                wxTheApp->MacHandleUnhandledEvent(theEvent);
-
-            ReleaseEvent( theEvent );
+            DispatchAndReleaseEvent(theEvent);
             m_sleepTime = kEventDurationNoWait ;
             break;
     }
 
     return true;
 }
+
+int wxGUIEventLoop::DispatchTimeout(unsigned long timeout)
+{
+    EventRef event;
+    OSStatus status = ReceiveNextEvent(0, NULL, timeout/1000, true, &event);
+    switch ( status )
+    {
+        default:
+            wxFAIL_MSG( "unexpected ReceiveNextEvent() error" );
+            // fall through
+
+        case eventLoopTimedOutErr:
+            return -1;
+
+        case eventLoopQuitErr:
+            return 0;
+
+        case noErr:
+            DispatchAndReleaseEvent(event);
+            return 1;
+    }
+}
+
diff --git a/src/palmos/evtloop.cpp b/src/palmos/evtloop.cpp
index 01bd21e982..5dc164d157 100644
--- a/src/palmos/evtloop.cpp
+++ b/src/palmos/evtloop.cpp
@@ -135,6 +135,11 @@ bool wxGUIEventLoop::Dispatch()
     return false;
 }
 
+int wxGUIEventLoop::DispatchTimeout(unsigned long timeout)
+{
+    return -1;
+}
+
 void wxGUIEventLoop::WakeUp()
 {
     return;
diff --git a/src/unix/evtloopunix.cpp b/src/unix/evtloopunix.cpp
index fc1f469ae4..7f27c1c2a2 100644
--- a/src/unix/evtloopunix.cpp
+++ b/src/unix/evtloopunix.cpp
@@ -149,30 +149,34 @@ bool wxConsoleEventLoop::Pending() const
 
 bool wxConsoleEventLoop::Dispatch()
 {
-    // calculate the timeout until the next timer expiration
-    int timeout;
+    DispatchTimeout(wxFDIODispatcher::TIMEOUT_INFINITE);
 
+    return true;
+}
+
+int wxConsoleEventLoop::DispatchTimeout(unsigned long timeout)
+{
 #if wxUSE_TIMER
+    // check if we need to decrease the timeout to account for a timer
     wxUsecClock_t nextTimer;
     if ( wxTimerScheduler::Get().GetNext(&nextTimer) )
     {
-        // timeout is in ms
-        timeout = (nextTimer / 1000).ToLong();
+        unsigned long timeUntilNextTimer = wxMilliClockToLong(nextTimer / 1000);
+        if ( timeUntilNextTimer < timeout )
+            timeout = timeUntilNextTimer;
     }
-    else // no timers, we can block forever
 #endif // wxUSE_TIMER
-    {
-        timeout = wxFDIODispatcher::TIMEOUT_INFINITE;
-    }
 
-    m_dispatcher->Dispatch(timeout);
+    bool hadEvent = m_dispatcher->Dispatch(timeout);
 
 #if wxUSE_TIMER
-    wxTimerScheduler::Get().NotifyExpired();
-#endif
+    if ( wxTimerScheduler::Get().NotifyExpired() )
+        hadEvent = true;
+#endif // wxUSE_TIMER
 
     wxTheApp->ProcessPendingEvents();
-    return true;
+
+    return hadEvent ? 1 : -1;
 }
 
 void wxConsoleEventLoop::WakeUp()
-- 
2.47.2