]> git.saurik.com Git - wxWidgets.git/commitdiff
Add wxEventLoop::ScheduleExit().
authorVadim Zeitlin <vadim@wxwidgets.org>
Wed, 3 Jul 2013 00:26:13 +0000 (00:26 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Wed, 3 Jul 2013 00:26:13 +0000 (00:26 +0000)
This method allows to request exiting from the given event loop even if it's
not the currently active one, unlike Exit() which would assert in this case.
With it, it becomes possible to ask the loop to terminate as soon as possible
even if a nested loop is currently running.

See #10258.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@74335 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

30 files changed:
docs/changes.txt
include/wx/cocoa/evtloop.h
include/wx/evtloop.h
include/wx/gtk/evtloop.h
include/wx/osx/core/evtloop.h
interface/wx/evtloop.h
src/cocoa/evtloop.mm
src/common/evtloopcmn.cpp
src/gtk/evtloop.cpp
src/gtk1/evtloop.cpp
src/motif/evtloop.cpp
src/osx/carbon/evtloop.cpp
src/osx/cocoa/evtloop.mm
src/osx/core/evtloop_cf.cpp
src/x11/evtloop.cpp
tests/Makefile.in
tests/events/evtlooptest.cpp [new file with mode: 0644]
tests/makefile.bcc
tests/makefile.gcc
tests/makefile.vc
tests/makefile.wat
tests/test.bkl
tests/test_test.dsp
tests/test_test_gui.dsp
tests/test_vc7_test.vcproj
tests/test_vc7_test_gui.vcproj
tests/test_vc8_test.vcproj
tests/test_vc8_test_gui.vcproj
tests/test_vc9_test.vcproj
tests/test_vc9_test_gui.vcproj

index 4a4e8018fb0f2fb87792aa14f58bc109f3d15c59..2cfac6cc8f75a122885390ff9829711d057e9939 100644 (file)
@@ -584,6 +584,7 @@ All:
 - Add new wxFSW_EVENT_ATTRIB and wxFSW_EVENT_UNMOUNT flags (David Hart).
 - Add separate read/written bytes counters and per-direction NOWAIT and WAITALL
   flags to wxSocket (Rob Bresalier).
+- Add wxEventLoop::ScheduleExit() (Rob Bresalier).
 - Add wxProcess::SetPriority() (Marian Meravy).
 - Add wxDir::Close() method (Silverstorm82).
 - Fix wxDateTime::GetWeekOfYear() for the last week of year (aimo).
index 0790014c9ad119dcd847c33fdcf4e736745396ab..f957dd89679abba7d1847d5a94eece672f2387e1 100644 (file)
@@ -20,7 +20,7 @@ class WXDLLIMPEXP_CORE wxGUIEventLoop : public wxEventLoopBase
 public:
     wxGUIEventLoop() { m_exitcode = 0; }
 
-    virtual void Exit(int rc = 0);
+    virtual void ScheduleExit(int rc = 0);
     virtual bool Pending() const;
     virtual bool Dispatch();
     virtual int DispatchTimeout(unsigned long timeout);
index 52084d997dec5d5d88bbeabec2e4be5364731651..2abbd9cef49ec63cf1aa1e7765bc2df52d9d3ef8 100644 (file)
@@ -100,7 +100,15 @@ public:
     bool IsRunning() const;
 
     // exit from the loop with the given exit code
-    virtual void Exit(int rc = 0) = 0;
+    //
+    // this can be only used to exit the currently running loop, use
+    // ScheduleExit() if this might not be the case
+    virtual void Exit(int rc = 0);
+
+    // ask the event loop to exit with the given exit code, can be used even if
+    // this loop is not running right now but the loop must have been started,
+    // i.e. Run() should have been already called
+    virtual void ScheduleExit(int rc = 0) = 0;
 
     // return true if any events are available
     virtual bool Pending() const = 0;
@@ -180,6 +188,12 @@ protected:
     // an exception thrown from inside the loop)
     virtual void OnExit();
 
+    // Return true if we're currently inside our Run(), even if another nested
+    // event loop is currently running, unlike IsRunning() (which should have
+    // been really called IsActive() but it's too late to change this now).
+    bool IsInsideRun() const { return m_isInsideRun; }
+
+
     // the pointer to currently active loop
     static wxEventLoopBase *ms_activeLoop;
 
@@ -190,6 +204,10 @@ protected:
     bool m_isInsideYield;
     long m_eventsToProcessInsideYield;
 
+private:
+    // this flag is set on entry into Run() and reset before leaving it
+    bool m_isInsideRun;
+
     wxDECLARE_NO_COPY_CLASS(wxEventLoopBase);
 };
 
@@ -206,7 +224,7 @@ public:
 
     // sets the "should exit" flag and wakes up the loop so that it terminates
     // soon
-    virtual void Exit(int rc = 0);
+    virtual void ScheduleExit(int rc = 0);
 
 protected:
     // enters a loop calling OnNextIteration(), Pending() and Dispatch() and
@@ -291,7 +309,7 @@ public:
     }
 #endif // wxUSE_EVENTLOOP_SOURCE
 
-    virtual void Exit(int rc = 0);
+    virtual void ScheduleExit(int rc = 0);
     virtual bool Pending() const;
     virtual bool Dispatch();
     virtual int DispatchTimeout(unsigned long timeout)
index d7f5205f37919fc8c082c5b963bde1cecb698a41..af4d00348df8d089216360d144d3a052610b0dde 100644 (file)
@@ -22,7 +22,7 @@ class WXDLLIMPEXP_CORE wxGUIEventLoop : public wxEventLoopBase
 public:
     wxGUIEventLoop();
 
-    virtual void Exit(int rc = 0);
+    virtual void ScheduleExit(int rc = 0);
     virtual bool Pending() const;
     virtual bool Dispatch();
     virtual int DispatchTimeout(unsigned long timeout);
index 02830f8c40d441d2522ff72584493b49606628f6..862a4d9d54689c07c77fad35e751fbafd177301c 100644 (file)
@@ -26,7 +26,7 @@ public:
 
     // sets the "should exit" flag and wakes up the loop so that it terminates
     // soon
-    virtual void Exit(int rc = 0);
+    virtual void ScheduleExit(int rc = 0);
 
     // return true if any events are available
     virtual bool Pending() const;
index 4e42be0dc46f8b819890e0fc58fb9c59040ecf28..c9c6e6cb42e54e782c57612ec5c627ad97c6983f 100644 (file)
     You can create your own event loop if you need, provided that you restore
     the main event loop once yours is destroyed (see wxEventLoopActivator).
 
+    Notice that there can be more than one event loop at any given moment, e.g.
+    an event handler called from the main loop can show a modal dialog, which
+    starts its own loop resulting in two nested loops, with the modal dialog
+    being the active one (its IsRunning() returns @true). And a handler for a
+    button inside the modal dialog can, of course, create another modal dialog
+    with its own event loop and so on. So in general event loops form a stack
+    and only the event loop at the top of the stack is considered to be active.
+    It is also the only loop that can be directly asked to terminate by calling
+    Exit() (which is done by wxDialog::EndModal()), an outer event loop can't
+    be stopped while an inner one is still running. It is however possible to
+    ask an outer event loop to terminate as soon as all its nested loops exit
+    and the control returns back to it by using ScheduleExit().
+
     @library{wxbase}
     @category{appmanagement}
 
@@ -90,9 +103,32 @@ public:
     virtual bool IsOk() const;
 
     /**
-        Exit from the loop with the given exit code.
+        Exit the currently running loop with the given exit code.
+
+        The loop will exit, i.e. its Run() method will return, during the next
+        event loop iteration.
+
+        Notice that this method can only be used if this event loop is the
+        currently running one, i.e. its IsRunning() returns @true. If this is
+        not the case, an assert failure is triggered and nothing is done as
+        outer event loops can't be exited from immediately. Use ScheduleExit()
+        if you'd like to exit this loop even if it doesn't run currently.
+     */
+    virtual void Exit(int rc = 0);
+
+    /**
+        Schedule an exit from the loop with the given exit code.
+
+        This method is similar to Exit() but can be called even if this event
+        loop is not the currently running one -- and if it is the active loop,
+        then it works in exactly the same way as Exit().
+
+        The loop will exit as soon as the control flow returns to it, i.e.
+        after any nested loops terminate.
+
+        @since 2.9.5
      */
-    virtual void Exit(int rc = 0) = 0;
+    virtual void ScheduleExit(int rc = 0) = 0;
 
     /**
         Return true if any events are available.
index c73a2307d1ab5966cf77a0348c5e0991bcf24739..4ec9e52acef314bc82e2bb85cee30decc0c44290 100644 (file)
@@ -39,9 +39,9 @@ int wxGUIEventLoop::DoRun()
     return m_exitcode;
 }
 
-void wxGUIEventLoop::Exit(int rc)
+void wxGUIEventLoop::ScheduleExit(int rc)
 {
-    wxCHECK_RET( IsRunning(), wxT("can't call Exit() if not running") );
+    wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not started") );
 
     m_exitcode = rc;
 
index 2f922c248ddfb66c167bdf3f52f1767f6946568b..da0b79fba477db3a2d3e24443bb7a7fd0ad3e74a 100644 (file)
@@ -22,6 +22,8 @@
     #include "wx/app.h"
 #endif //WX_PRECOMP
 
+#include "wx/scopeguard.h"
+
 // ----------------------------------------------------------------------------
 // wxEventLoopBase
 // ----------------------------------------------------------------------------
@@ -30,6 +32,7 @@ wxEventLoopBase *wxEventLoopBase::ms_activeLoop = NULL;
 
 wxEventLoopBase::wxEventLoopBase()
 {
+    m_isInsideRun = false;
     m_shouldExit = false;
 
     m_isInsideYield = false;
@@ -55,7 +58,7 @@ void wxEventLoopBase::SetActive(wxEventLoopBase* loop)
 int wxEventLoopBase::Run()
 {
     // event loops are not recursive, you need to create another loop!
-    wxCHECK_MSG( !IsRunning(), -1, wxT("can't reenter a message loop") );
+    wxCHECK_MSG( !IsInsideRun(), -1, wxT("can't reenter a message loop") );
 
     // ProcessIdle() and ProcessEvents() below may throw so the code here should
     // be exception-safe, hence we must use local objects for all actions we
@@ -66,10 +69,21 @@ int wxEventLoopBase::Run()
     // reset this flag.
     m_shouldExit = false;
 
+    // Set this variable to true for the duration of this method.
+    m_isInsideRun = true;
+    wxON_BLOCK_EXIT_SET(m_isInsideRun, false);
+
     // Finally really run the loop.
     return DoRun();
 }
 
+void wxEventLoopBase::Exit(int rc)
+{
+    wxCHECK_RET( IsRunning(), wxS("Use ScheduleExit() on not running loop") );
+
+    ScheduleExit(rc);
+}
+
 void wxEventLoopBase::OnExit()
 {
     if (wxTheApp)
@@ -231,9 +245,9 @@ int wxEventLoopManual::DoRun()
     return m_exitcode;
 }
 
-void wxEventLoopManual::Exit(int rc)
+void wxEventLoopManual::ScheduleExit(int rc)
 {
-    wxCHECK_RET( IsRunning(), wxT("can't call Exit() if not running") );
+    wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not running") );
 
     m_exitcode = rc;
     m_shouldExit = true;
index 68f65545e4cdc82aa845d706aded1e61d2129282..d104d82f0c7c09935b9901ef1d25a620f85e2998 100644 (file)
@@ -52,19 +52,41 @@ wxGUIEventLoop::wxGUIEventLoop()
 
 int wxGUIEventLoop::DoRun()
 {
-    gtk_main();
+    guint loopLevel = gtk_main_level();
+
+    // This is placed inside of a loop to take into account nested
+    // event loops.  For example, inside this event loop, we may receive
+    // Exit() for a different event loop (which we are currently inside of)
+    // That Exit() will cause this gtk_main() to exit so we need to re-enter it.
+    while ( !m_shouldExit )
+    {
+        gtk_main();
+    }
+
+    // Force the enclosing event loop to also exit to see if it is done in case
+    // that event loop had Exit() called inside of the just ended loop. If it
+    // is not time yet for that event loop to exit, it will be executed again
+    // due to the while() loop on m_shouldExit().
+    //
+    // This is unnecessary if we are the top level loop, i.e. loop of level 0.
+    if ( loopLevel )
+    {
+        gtk_main_quit();
+    }
 
     OnExit();
 
     return m_exitcode;
 }
 
-void wxGUIEventLoop::Exit(int rc)
+void wxGUIEventLoop::ScheduleExit(int rc)
 {
-    wxCHECK_RET( IsRunning(), "can't call Exit() if not running" );
+    wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not started") );
 
     m_exitcode = rc;
 
+    m_shouldExit = true;
+
     gtk_main_quit();
 }
 
index 5f4a91f9959882881f8cef04ca0546434cb8f4bb..b66c7015cb1a0d220831310814d607a1e34097bc 100644 (file)
@@ -69,7 +69,27 @@ int wxGUIEventLoop::DoRun()
 {
     m_impl = new wxEventLoopImpl;
 
-    gtk_main();
+    guint loopLevel = gtk_main_level();
+
+    // This is placed inside of a loop to take into account nested
+    // event loops.  For example, inside this event loop, we may recieve
+    // Exit() for a different event loop (which we are currently inside of)
+    // That Exit() will cause this gtk_main() to exit so we need to re-enter it.
+    while ( !m_shouldExit )
+    {
+        gtk_main();
+    }
+
+    // Force the enclosing event loop to also exit to see if it is done
+    // in case that event loop ended inside of this one.  If it is not time
+    // yet for that event loop to exit, it will be executed again due to
+    // the while() loop on m_shouldExit().
+    //
+    // This is unnecessary if we are the top level loop, i.e. loop of level 0.
+    if ( loopLevel )
+    {
+        gtk_main_quit();
+    }
 
     OnExit();
 
@@ -79,12 +99,14 @@ int wxGUIEventLoop::DoRun()
     return exitcode;
 }
 
-void wxGUIEventLoop::Exit(int rc)
+void wxGUIEventLoop::ScheduleExit(int rc)
 {
-    wxCHECK_RET( IsRunning(), wxT("can't call Exit() if not running") );
+    wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not started") );
 
     m_impl->SetExitCode(rc);
 
+    m_shouldExit = true;
+
     gtk_main_quit();
 }
 
index 7ec41cd123c6ee438c6401d2b0fd73595d55ad6d..c2b0f367e4c9d1ccd07cb5aa0ca15b82af2003cd 100644 (file)
@@ -121,9 +121,9 @@ int wxGUIEventLoop::DoRun()
     return exitcode;
 }
 
-void wxGUIEventLoop::Exit(int rc)
+void wxGUIEventLoop::SchduleExit(int rc)
 {
-    wxCHECK_RET( IsRunning(), wxT("can't call Exit() if not running") );
+    wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not started") );
 
     m_impl->SetExitCode(rc);
     m_impl->SetKeepGoing( false );
index d064a8dbd1c597f69507dfbfacf06321639534c6..288210641b76b1ec33483d866135bfc0a0987fbf 100644 (file)
@@ -96,7 +96,15 @@ void wxGUIEventLoop::WakeUp()
 void wxGUIEventLoop::OSXDoRun()
 {
     wxMacAutoreleasePool autoreleasepool;
-    RunApplicationEventLoop();
+
+    while (!m_shouldExit)
+    {
+        RunApplicationEventLoop();
+    }
+
+    // Force enclosing event loop to temporarily exit and check
+    // if it should be stopped.
+    QuitApplicationEventLoop();
 }
 
 void wxGUIEventLoop::OSXDoStop()
index 00a3008dbc242a82b21cccace3c450e05782ddf3..a4d6cc1239b7f1a29c3919537f8163099b8e2dda 100644 (file)
@@ -32,6 +32,7 @@
 #endif // WX_PRECOMP
 
 #include "wx/log.h"
+#include "wx/scopeguard.h"
 
 #include "wx/osx/private.h"
 
@@ -240,17 +241,113 @@ int wxGUIEventLoop::DoDispatchTimeout(unsigned long timeout)
     }
 }
 
+static int gs_loopNestingLevel = 0;
+
 void wxGUIEventLoop::OSXDoRun()
 {
-    wxMacAutoreleasePool autoreleasepool;
-    [NSApp run];
+    /*
+       In order to properly nest GUI event loops in Cocoa, it is important to
+       have [NSApp run] only as the main/outermost event loop.  There are many
+       problems if [NSApp run] is used as an inner event loop.  The main issue
+       is that a call to [NSApp stop] is needed to exit an [NSApp run] event
+       loop. But the [NSApp stop] has some side effects that we do not want -
+       such as if there was a modal dialog box with a modal event loop running,
+       that event loop would also get exited, and the dialog would be closed.
+       The call to [NSApp stop] would also cause the enclosing event loop to
+       exit as well.
+
+       webkit's webcore library uses CFRunLoopRun() for nested event loops. See
+       the log of the commit log about the change in webkit's webcore module:
+       http://www.mail-archive.com/webkit-changes@lists.webkit.org/msg07397.html
+
+       See here for the latest run loop that is used in webcore:
+       https://github.com/WebKit/webkit/blob/master/Source/WebCore/platform/mac/RunLoopMac.mm
+
+       CFRunLoopRun() was tried for the nested event loop here but it causes a
+       problem in that all user input is disabled - and there is no way to
+       re-enable it.  The caller of this event loop may not want user input
+       disabled (such as synchronous wxExecute with wxEXEC_NODISABLE flag).
+
+       In order to have an inner event loop where user input can be enabled,
+       the old wxCocoa code that used the [NSApp nextEventMatchingMask] was
+       borrowed but changed to use blocking instead of polling. By specifying
+       'distantFuture' in 'untildate', we can have it block until the next
+       event.  Then we can keep looping on each new event until m_shouldExit is
+       raised to exit the event loop.
+    */
+    gs_loopNestingLevel++;
+    wxON_BLOCK_EXIT_SET(gs_loopNestingLevel, gs_loopNestingLevel - 1);
+
+    while ( !m_shouldExit )
+    {
+        // By putting this inside the loop, we can drain it in each
+        // loop iteration.
+        wxMacAutoreleasePool autoreleasepool;
+
+        if ( gs_loopNestingLevel == 1 )
+        {
+            // Use -[NSApplication run] for the main run loop.
+            [NSApp run];
+        }
+        else
+        {
+            // We use this blocking call to [NSApp nextEventMatchingMask:...]
+            // because the other methods (such as CFRunLoopRun() and [runLoop
+            // runMode:beforeDate] were always disabling input to the windows
+            // (even if we wanted it enabled).
+            //
+            // Here are the other run loops which were tried, but always left
+            // user input disabled:
+            //
+            // [runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
+            // CFRunLoopRun();
+            // CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10 , true);
+            //
+            // Using [NSApp nextEventMatchingMask:...] would leave windows
+            // enabled if we wanted them to be, so that is why it is used.
+            NSEvent *event = [NSApp
+                    nextEventMatchingMask:NSAnyEventMask
+                    untilDate:[NSDate distantFuture]
+                    inMode:NSDefaultRunLoopMode
+                    dequeue: YES];
+
+            [NSApp sendEvent: event];
+
+            /**
+              The NSApplication documentation states that:
+
+              "
+              This method is invoked automatically in the main event loop
+              after each event when running in NSDefaultRunLoopMode or
+              NSModalRunLoopMode. This method is not invoked automatically
+              when running in NSEventTrackingRunLoopMode.
+              "
+
+              So to be safe, we also invoke it here in this event loop.
+
+              See: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/Reference/Reference.html
+            */
+            [NSApp updateWindows];
+        }
+    }
+
+    // Wake up the enclosing loop so that it can check if it also needs
+    // to exit.
+    WakeUp();
 }
 
 void wxGUIEventLoop::OSXDoStop()
 {
-    // only calling stop: is not enough when called from a runloop-observer,
-    // therefore add a dummy event, to make sure the runloop gets another round
-    [NSApp stop:0];
+    // We should only stop the top level event loop.
+    if ( gs_loopNestingLevel <= 1 )
+    {
+        [NSApp stop:0];
+    }
+
+    // For the top level loop only calling stop: is not enough when called from
+    // a runloop-observer, therefore add a dummy event, to make sure the
+    // runloop gets another round. And for the nested loops we need to wake it
+    // up to notice that it should exit, so do this unconditionally.
     WakeUp();
 }
 
index 5ef10ae43215627e54fa1f4f81984a2fa9c21929..12ecb2c87292d3e0945739a81bfc141a23a63266 100644 (file)
@@ -440,7 +440,7 @@ int wxCFEventLoop::DoRun()
 
 // sets the "should exit" flag and wakes up the loop so that it terminates
 // soon
-void wxCFEventLoop::Exit(int rc)
+void wxCFEventLoop::ScheduleExit(int rc)
 {
     m_exitcode = rc;
     m_shouldExit = true;
index 1f988f97620182e4019fa8432e2b3f9937176c5d..e35109226703bc255794bcc1cd698b4c75e32436 100644 (file)
@@ -162,7 +162,7 @@ int wxGUIEventLoop::DoRun()
     return exitcode;
 }
 
-void wxGUIEventLoop::Exit(int rc)
+void wxGUIEventLoop::ScheduleExit(int rc)
 {
     if ( m_impl )
     {
index 0c786be59e4b67a28f0fb7b9f10c8416b2b3ff62..14da03c03cbc2628fb3fb5df43d66d42cb68f014 100644 (file)
@@ -65,6 +65,7 @@ TEST_OBJECTS =  \
        test_regconf.o \
        test_datetimetest.o \
        test_evthandler.o \
+       test_evtlooptest.o \
        test_evtsource.o \
        test_stopwatch.o \
        test_timertest.o \
@@ -208,6 +209,7 @@ TEST_GUI_OBJECTS =  \
        test_gui_windowtest.o \
        test_gui_dialogtest.o \
        test_gui_clone.o \
+       test_gui_evtlooptest.o \
        test_gui_propagation.o \
        test_gui_keyboard.o \
        test_gui_fonttest.o \
@@ -476,6 +478,9 @@ test_datetimetest.o: $(srcdir)/datetime/datetimetest.cpp $(TEST_ODEP)
 test_evthandler.o: $(srcdir)/events/evthandler.cpp $(TEST_ODEP)
        $(CXXC) -c -o $@ $(TEST_CXXFLAGS) $(srcdir)/events/evthandler.cpp
 
+test_evtlooptest.o: $(srcdir)/events/evtlooptest.cpp $(TEST_ODEP)
+       $(CXXC) -c -o $@ $(TEST_CXXFLAGS) $(srcdir)/events/evtlooptest.cpp
+
 test_evtsource.o: $(srcdir)/events/evtsource.cpp $(TEST_ODEP)
        $(CXXC) -c -o $@ $(TEST_CXXFLAGS) $(srcdir)/events/evtsource.cpp
 
@@ -881,6 +886,9 @@ test_gui_dialogtest.o: $(srcdir)/controls/dialogtest.cpp $(TEST_GUI_ODEP)
 test_gui_clone.o: $(srcdir)/events/clone.cpp $(TEST_GUI_ODEP)
        $(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/events/clone.cpp
 
+test_gui_evtlooptest.o: $(srcdir)/events/evtlooptest.cpp $(TEST_GUI_ODEP)
+       $(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/events/evtlooptest.cpp
+
 test_gui_propagation.o: $(srcdir)/events/propagation.cpp $(TEST_GUI_ODEP)
        $(CXXC) -c -o $@ $(TEST_GUI_CXXFLAGS) $(srcdir)/events/propagation.cpp
 
diff --git a/tests/events/evtlooptest.cpp b/tests/events/evtlooptest.cpp
new file mode 100644 (file)
index 0000000..a75014d
--- /dev/null
@@ -0,0 +1,128 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        tests/events/evtloop.cpp
+// Purpose:     Tests for the event loop classes
+// Author:      Rob Bresalier
+// Created:     2013-05-02
+// RCS-ID:      $Id$
+// Copyright:   (c) 2013 Rob Bresalier
+///////////////////////////////////////////////////////////////////////////////
+
+// ----------------------------------------------------------------------------
+// headers
+// ----------------------------------------------------------------------------
+
+#include "testprec.h"
+
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+#include "wx/timer.h"
+
+// ----------------------------------------------------------------------------
+// constants
+// ----------------------------------------------------------------------------
+
+// Use two arbitrary but different return codes for the two loops.
+const int EXIT_CODE_OUTER_LOOP = 99;
+const int EXIT_CODE_INNER_LOOP = 55;
+
+// ----------------------------------------------------------------------------
+// test class
+// ----------------------------------------------------------------------------
+
+class EvtloopTestCase : public CppUnit::TestCase
+{
+public:
+    EvtloopTestCase() { }
+
+private:
+    CPPUNIT_TEST_SUITE( EvtloopTestCase );
+        CPPUNIT_TEST( TestExit );
+    CPPUNIT_TEST_SUITE_END();
+
+    void TestExit();
+
+    DECLARE_NO_COPY_CLASS(EvtloopTestCase)
+};
+
+// register in the unnamed registry so that these tests are run by default
+CPPUNIT_TEST_SUITE_REGISTRATION( EvtloopTestCase );
+
+// also include in its own registry so that these tests can be run alone
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EvtloopTestCase, "EvtloopTestCase" );
+
+
+// Helper class to schedule exit of the given event loop after the specified
+// delay.
+class ScheduleLoopExitTimer : public wxTimer
+{
+public:
+    ScheduleLoopExitTimer(wxEventLoop& loop, int rc)
+        : m_loop(loop),
+          m_rc(rc)
+    {
+    }
+
+    virtual void Notify()
+    {
+        m_loop.ScheduleExit(m_rc);
+    }
+
+private:
+    wxEventLoop& m_loop;
+    const int m_rc;
+};
+
+// Another helper which runs a nested loop and schedules exiting both the outer
+// and the inner loop after the specified delays.
+class RunNestedAndExitBothLoopsTimer : public wxTimer
+{
+public:
+    RunNestedAndExitBothLoopsTimer(wxTimer& timerOuter,
+                                   int loopOuterDuration,
+                                   int loopInnerDuration)
+        : m_timerOuter(timerOuter),
+          m_loopOuterDuration(loopOuterDuration),
+          m_loopInnerDuration(loopInnerDuration)
+    {
+    }
+
+    virtual void Notify()
+    {
+        wxEventLoop loopInner;
+        ScheduleLoopExitTimer timerInner(loopInner, EXIT_CODE_INNER_LOOP);
+
+        m_timerOuter.StartOnce(m_loopOuterDuration);
+        timerInner.StartOnce(m_loopInnerDuration);
+
+        CPPUNIT_ASSERT_EQUAL( EXIT_CODE_INNER_LOOP, loopInner.Run() );
+    }
+
+private:
+    wxTimer& m_timerOuter;
+    const int m_loopOuterDuration;
+    const int m_loopInnerDuration;
+};
+
+void EvtloopTestCase::TestExit()
+{
+    // Test that simply exiting the loop works.
+    wxEventLoop loopOuter;
+    ScheduleLoopExitTimer timerExit(loopOuter, EXIT_CODE_OUTER_LOOP);
+    timerExit.StartOnce(1);
+    CPPUNIT_ASSERT_EQUAL( EXIT_CODE_OUTER_LOOP, loopOuter.Run() );
+
+    // Test that exiting the outer loop before the inner loop (outer duration
+    // parameter less than inner duration in the timer ctor below) works.
+    ScheduleLoopExitTimer timerExitOuter(loopOuter, EXIT_CODE_OUTER_LOOP);
+    RunNestedAndExitBothLoopsTimer timerRun(timerExitOuter, 5, 10);
+    timerRun.StartOnce(1);
+    CPPUNIT_ASSERT_EQUAL( EXIT_CODE_OUTER_LOOP, loopOuter.Run() );
+
+    // Test that exiting the inner loop before the outer one works too.
+    ScheduleLoopExitTimer timerExitOuter2(loopOuter, EXIT_CODE_OUTER_LOOP);
+    RunNestedAndExitBothLoopsTimer timerRun2(timerExitOuter, 10, 5);
+    timerRun2.StartOnce(1);
+    CPPUNIT_ASSERT_EQUAL( EXIT_CODE_OUTER_LOOP, loopOuter.Run() );
+}
index 331b2563cb7c4ca5792d5b8f6a95987888f10ffa..4013df583ebe4796edae59e7acf84db9cd6f29f8 100644 (file)
@@ -50,6 +50,7 @@ TEST_OBJECTS =  \
        $(OBJS)\test_regconf.obj \\r
        $(OBJS)\test_datetimetest.obj \\r
        $(OBJS)\test_evthandler.obj \\r
+       $(OBJS)\test_evtlooptest.obj \
        $(OBJS)\test_evtsource.obj \\r
        $(OBJS)\test_stopwatch.obj \\r
        $(OBJS)\test_timertest.obj \\r
@@ -194,6 +195,7 @@ TEST_GUI_OBJECTS =  \
        $(OBJS)\test_gui_windowtest.obj \\r
        $(OBJS)\test_gui_dialogtest.obj \\r
        $(OBJS)\test_gui_clone.obj \\r
+       $(OBJS)\test_gui_evtlooptest.obj \
        $(OBJS)\test_gui_propagation.obj \\r
        $(OBJS)\test_gui_keyboard.obj \\r
        $(OBJS)\test_gui_fonttest.obj \\r
@@ -531,6 +533,9 @@ $(OBJS)\test_datetimetest.obj: .\datetime\datetimetest.cpp
 $(OBJS)\test_evthandler.obj: .\events\evthandler.cpp\r
        $(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\events\evthandler.cpp\r
 \r
+$(OBJS)\test_evtlooptest.obj: .\events\evtlooptest.cpp
+       $(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\events\evtlooptest.cpp
+
 $(OBJS)\test_evtsource.obj: .\events\evtsource.cpp\r
        $(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\events\evtsource.cpp\r
 \r
@@ -939,6 +944,9 @@ $(OBJS)\test_gui_dialogtest.obj: .\controls\dialogtest.cpp
 $(OBJS)\test_gui_clone.obj: .\events\clone.cpp\r
        $(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\events\clone.cpp\r
 \r
+$(OBJS)\test_gui_evtlooptest.obj: .\events\evtlooptest.cpp
+       $(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\events\evtlooptest.cpp
+
 $(OBJS)\test_gui_propagation.obj: .\events\propagation.cpp\r
        $(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\events\propagation.cpp\r
 \r
index b64a38ad07b7a9240942286547793e003d9902c5..875dd6c0c9e6c7f7c21aba25ce7a3f2c353def57 100644 (file)
@@ -43,6 +43,7 @@ TEST_OBJECTS =  \
        $(OBJS)\test_regconf.o \\r
        $(OBJS)\test_datetimetest.o \\r
        $(OBJS)\test_evthandler.o \\r
+       $(OBJS)\test_evtlooptest.o \
        $(OBJS)\test_evtsource.o \\r
        $(OBJS)\test_stopwatch.o \\r
        $(OBJS)\test_timertest.o \\r
@@ -188,6 +189,7 @@ TEST_GUI_OBJECTS =  \
        $(OBJS)\test_gui_windowtest.o \\r
        $(OBJS)\test_gui_dialogtest.o \\r
        $(OBJS)\test_gui_clone.o \\r
+       $(OBJS)\test_gui_evtlooptest.o \
        $(OBJS)\test_gui_propagation.o \\r
        $(OBJS)\test_gui_keyboard.o \\r
        $(OBJS)\test_gui_fonttest.o \\r
@@ -520,6 +522,9 @@ $(OBJS)\test_datetimetest.o: ./datetime/datetimetest.cpp
 $(OBJS)\test_evthandler.o: ./events/evthandler.cpp\r
        $(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<\r
 \r
+$(OBJS)\test_evtlooptest.o: ./events/evtlooptest.cpp
+       $(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<
+
 $(OBJS)\test_evtsource.o: ./events/evtsource.cpp\r
        $(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<\r
 \r
@@ -928,6 +933,9 @@ $(OBJS)\test_gui_dialogtest.o: ./controls/dialogtest.cpp
 $(OBJS)\test_gui_clone.o: ./events/clone.cpp\r
        $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<\r
 \r
+$(OBJS)\test_gui_evtlooptest.o: ./events/evtlooptest.cpp
+       $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
+
 $(OBJS)\test_gui_propagation.o: ./events/propagation.cpp\r
        $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<\r
 \r
index 4054a2f575df24efcd4eadbb37d0dc5655465431..09066b24626c4ba57720a307d0b34da8e2054fe9 100644 (file)
@@ -44,6 +44,7 @@ TEST_OBJECTS =  \
        $(OBJS)\test_regconf.obj \\r
        $(OBJS)\test_datetimetest.obj \\r
        $(OBJS)\test_evthandler.obj \\r
+       $(OBJS)\test_evtlooptest.obj \
        $(OBJS)\test_evtsource.obj \\r
        $(OBJS)\test_stopwatch.obj \\r
        $(OBJS)\test_timertest.obj \\r
@@ -191,6 +192,7 @@ TEST_GUI_OBJECTS =  \
        $(OBJS)\test_gui_windowtest.obj \\r
        $(OBJS)\test_gui_dialogtest.obj \\r
        $(OBJS)\test_gui_clone.obj \\r
+       $(OBJS)\test_gui_evtlooptest.obj \
        $(OBJS)\test_gui_propagation.obj \\r
        $(OBJS)\test_gui_keyboard.obj \\r
        $(OBJS)\test_gui_fonttest.obj \\r
@@ -671,6 +673,9 @@ $(OBJS)\test_datetimetest.obj: .\datetime\datetimetest.cpp
 $(OBJS)\test_evthandler.obj: .\events\evthandler.cpp\r
        $(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\events\evthandler.cpp\r
 \r
+$(OBJS)\test_evtlooptest.obj: .\events\evtlooptest.cpp
+       $(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\events\evtlooptest.cpp
+
 $(OBJS)\test_evtsource.obj: .\events\evtsource.cpp\r
        $(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\events\evtsource.cpp\r
 \r
@@ -1079,6 +1084,9 @@ $(OBJS)\test_gui_dialogtest.obj: .\controls\dialogtest.cpp
 $(OBJS)\test_gui_clone.obj: .\events\clone.cpp\r
        $(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\clone.cpp\r
 \r
+$(OBJS)\test_gui_evtlooptest.obj: .\events\evtlooptest.cpp
+       $(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\evtlooptest.cpp
+
 $(OBJS)\test_gui_propagation.obj: .\events\propagation.cpp\r
        $(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\propagation.cpp\r
 \r
index 88a3f23823189208a3f4d227cbce71afb7441829..505bfa323205d8d4bf147573e2785cca440113c6 100644 (file)
@@ -319,6 +319,7 @@ TEST_OBJECTS =  &
        $(OBJS)\test_regconf.obj &\r
        $(OBJS)\test_datetimetest.obj &\r
        $(OBJS)\test_evthandler.obj &\r
+       $(OBJS)\test_evtlooptest.obj &
        $(OBJS)\test_evtsource.obj &\r
        $(OBJS)\test_stopwatch.obj &\r
        $(OBJS)\test_timertest.obj &\r
@@ -463,6 +464,7 @@ TEST_GUI_OBJECTS =  &
        $(OBJS)\test_gui_windowtest.obj &\r
        $(OBJS)\test_gui_dialogtest.obj &\r
        $(OBJS)\test_gui_clone.obj &\r
+       $(OBJS)\test_gui_evtlooptest.obj &
        $(OBJS)\test_gui_propagation.obj &\r
        $(OBJS)\test_gui_keyboard.obj &\r
        $(OBJS)\test_gui_fonttest.obj &\r
@@ -580,6 +582,9 @@ $(OBJS)\test_datetimetest.obj :  .AUTODEPEND .\datetime\datetimetest.cpp
 $(OBJS)\test_evthandler.obj :  .AUTODEPEND .\events\evthandler.cpp\r
        $(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<\r
 \r
+$(OBJS)\test_evtlooptest.obj :  .AUTODEPEND .\events\evtlooptest.cpp
+       $(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<
+
 $(OBJS)\test_evtsource.obj :  .AUTODEPEND .\events\evtsource.cpp\r
        $(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<\r
 \r
@@ -988,6 +993,9 @@ $(OBJS)\test_gui_dialogtest.obj :  .AUTODEPEND .\controls\dialogtest.cpp
 $(OBJS)\test_gui_clone.obj :  .AUTODEPEND .\events\clone.cpp\r
        $(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<\r
 \r
+$(OBJS)\test_gui_evtlooptest.obj :  .AUTODEPEND .\events\evtlooptest.cpp
+       $(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<
+
 $(OBJS)\test_gui_propagation.obj :  .AUTODEPEND .\events\propagation.cpp\r
        $(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<\r
 \r
index 4fc52db5a830858c25524e0ced8de3fea7ec7027..afe4637ef0cb5e6635a72a698fc7dd7789f0f2a6 100644 (file)
@@ -40,6 +40,7 @@
             config/regconf.cpp
             datetime/datetimetest.cpp
             events/evthandler.cpp
+            events/evtlooptest.cpp
             events/evtsource.cpp
             events/stopwatch.cpp
             events/timertest.cpp
             controls/windowtest.cpp
             controls/dialogtest.cpp
             events/clone.cpp
+            <!--
+                Duplicate this file here to test GUI event loops too.
+             -->
+            events/evtlooptest.cpp
             events/propagation.cpp
             events/keyboard.cpp
             font/fonttest.cpp
index f1e68bec39675994d68ee122f3cbfe8415deeb5c..287a816edce875461adc947922b5b471f1239f1f 100644 (file)
@@ -205,6 +205,10 @@ SOURCE=.\events\evthandler.cpp
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=.\events\evtlooptest.cpp
+# End Source File
+# Begin Source File
+
 SOURCE=.\events\evtsource.cpp\r
 # End Source File\r
 # Begin Source File\r
index 2569507311777534c2c627b0d2492f9cbc64f037..14a6c42d77afbd6a43274e34828e8d92438a53f2 100644 (file)
@@ -229,6 +229,10 @@ SOURCE=.\graphics\ellipsization.cpp
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=.\events\evtlooptest.cpp
+# End Source File
+# Begin Source File
+
 SOURCE=.\font\fonttest.cpp\r
 # End Source File\r
 # Begin Source File\r
index 5a0bd705a8dc51003a9005d695690d7fd9f96989..06d87811a1cceacfddd7cc0490f7f398634f892b 100644 (file)
                        <File\r
                                RelativePath=".\events\evthandler.cpp">\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\events\evtlooptest.cpp">\r
+                       </File>\r
                        <File\r
                                RelativePath=".\events\evtsource.cpp">\r
                        </File>\r
index f3345afef84f6091d86e338a24b28b03371ca58d..012dcdaf5a8e51c63bb676f722d8aa090318e2c2 100644 (file)
                        <File\r
                                RelativePath=".\graphics\ellipsization.cpp">\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\events\evtlooptest.cpp">\r
+                       </File>\r
                        <File\r
                                RelativePath=".\font\fonttest.cpp">\r
                        </File>\r
index 9fb69ed0ae7f9255ff5234de492f75db3ee4e1c1..c5e36fc8016b2d93bcc4baf5ae5096a4a8beae82 100644 (file)
                                RelativePath=".\events\evthandler.cpp"\r
                                >\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\events\evtlooptest.cpp"\r
+                               >\r
+                       </File>\r
                        <File\r
                                RelativePath=".\events\evtsource.cpp"\r
                                >\r
index a4bd5961b872e9c9eccc846f50383b48e17fc6b0..a9abb0833fcc14e452074421517857bc2ac481d3 100644 (file)
                                RelativePath=".\graphics\ellipsization.cpp"\r
                                >\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\events\evtlooptest.cpp"\r
+                               >\r
+                       </File>\r
                        <File\r
                                RelativePath=".\font\fonttest.cpp"\r
                                >\r
index 3324157e5147472191ea7476f8b2b84cfbc2c2ab..f1ca266e4f7555e8de8070a6814657ad8f91dc3d 100644 (file)
                                RelativePath=".\events\evthandler.cpp"\r
                                >\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\events\evtlooptest.cpp"\r
+                               >\r
+                       </File>\r
                        <File\r
                                RelativePath=".\events\evtsource.cpp"\r
                                >\r
index 746fd5d0a28eb6355edab4f594e3d39382620040..640de7a67d630cec553191bb51a895ba27efc98c 100644 (file)
                                RelativePath=".\graphics\ellipsization.cpp"\r
                                >\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\events\evtlooptest.cpp"\r
+                               >\r
+                       </File>\r
                        <File\r
                                RelativePath=".\font\fonttest.cpp"\r
                                >\r