diff --git a/docs/changes.txt b/docs/changes.txt
index 4a4e8018fb..2cfac6cc8f 100644
--- a/docs/changes.txt
+++ b/docs/changes.txt
@@ -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).
diff --git a/include/wx/cocoa/evtloop.h b/include/wx/cocoa/evtloop.h
index 0790014c9a..f957dd8967 100644
--- a/include/wx/cocoa/evtloop.h
+++ b/include/wx/cocoa/evtloop.h
@@ -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);
diff --git a/include/wx/evtloop.h b/include/wx/evtloop.h
index 52084d997d..2abbd9cef4 100644
--- a/include/wx/evtloop.h
+++ b/include/wx/evtloop.h
@@ -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)
diff --git a/include/wx/gtk/evtloop.h b/include/wx/gtk/evtloop.h
index d7f5205f37..af4d00348d 100644
--- a/include/wx/gtk/evtloop.h
+++ b/include/wx/gtk/evtloop.h
@@ -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);
diff --git a/include/wx/osx/core/evtloop.h b/include/wx/osx/core/evtloop.h
index 02830f8c40..862a4d9d54 100644
--- a/include/wx/osx/core/evtloop.h
+++ b/include/wx/osx/core/evtloop.h
@@ -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;
diff --git a/interface/wx/evtloop.h b/interface/wx/evtloop.h
index 4e42be0dc4..c9c6e6cb42 100644
--- a/interface/wx/evtloop.h
+++ b/interface/wx/evtloop.h
@@ -23,6 +23,19 @@
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) = 0;
+ 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 ScheduleExit(int rc = 0) = 0;
/**
Return true if any events are available.
diff --git a/src/cocoa/evtloop.mm b/src/cocoa/evtloop.mm
index c73a2307d1..4ec9e52ace 100644
--- a/src/cocoa/evtloop.mm
+++ b/src/cocoa/evtloop.mm
@@ -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;
diff --git a/src/common/evtloopcmn.cpp b/src/common/evtloopcmn.cpp
index 2f922c248d..da0b79fba4 100644
--- a/src/common/evtloopcmn.cpp
+++ b/src/common/evtloopcmn.cpp
@@ -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;
diff --git a/src/gtk/evtloop.cpp b/src/gtk/evtloop.cpp
index 68f65545e4..d104d82f0c 100644
--- a/src/gtk/evtloop.cpp
+++ b/src/gtk/evtloop.cpp
@@ -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();
}
diff --git a/src/gtk1/evtloop.cpp b/src/gtk1/evtloop.cpp
index 5f4a91f995..b66c7015cb 100644
--- a/src/gtk1/evtloop.cpp
+++ b/src/gtk1/evtloop.cpp
@@ -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();
}
diff --git a/src/motif/evtloop.cpp b/src/motif/evtloop.cpp
index 7ec41cd123..c2b0f367e4 100644
--- a/src/motif/evtloop.cpp
+++ b/src/motif/evtloop.cpp
@@ -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 );
diff --git a/src/osx/carbon/evtloop.cpp b/src/osx/carbon/evtloop.cpp
index d064a8dbd1..288210641b 100644
--- a/src/osx/carbon/evtloop.cpp
+++ b/src/osx/carbon/evtloop.cpp
@@ -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()
diff --git a/src/osx/cocoa/evtloop.mm b/src/osx/cocoa/evtloop.mm
index 00a3008dbc..a4d6cc1239 100644
--- a/src/osx/cocoa/evtloop.mm
+++ b/src/osx/cocoa/evtloop.mm
@@ -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();
}
diff --git a/src/osx/core/evtloop_cf.cpp b/src/osx/core/evtloop_cf.cpp
index 5ef10ae432..12ecb2c872 100644
--- a/src/osx/core/evtloop_cf.cpp
+++ b/src/osx/core/evtloop_cf.cpp
@@ -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;
diff --git a/src/x11/evtloop.cpp b/src/x11/evtloop.cpp
index 1f988f9762..e351092267 100644
--- a/src/x11/evtloop.cpp
+++ b/src/x11/evtloop.cpp
@@ -162,7 +162,7 @@ int wxGUIEventLoop::DoRun()
return exitcode;
}
-void wxGUIEventLoop::Exit(int rc)
+void wxGUIEventLoop::ScheduleExit(int rc)
{
if ( m_impl )
{
diff --git a/tests/Makefile.in b/tests/Makefile.in
index 0c786be59e..14da03c03c 100644
--- a/tests/Makefile.in
+++ b/tests/Makefile.in
@@ -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
index 0000000000..a75014d1ce
--- /dev/null
+++ b/tests/events/evtlooptest.cpp
@@ -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() );
+}
diff --git a/tests/makefile.bcc b/tests/makefile.bcc
index 331b2563cb..4013df583e 100644
--- a/tests/makefile.bcc
+++ b/tests/makefile.bcc
@@ -50,6 +50,7 @@ TEST_OBJECTS = \
$(OBJS)\test_regconf.obj \
$(OBJS)\test_datetimetest.obj \
$(OBJS)\test_evthandler.obj \
+ $(OBJS)\test_evtlooptest.obj \
$(OBJS)\test_evtsource.obj \
$(OBJS)\test_stopwatch.obj \
$(OBJS)\test_timertest.obj \
@@ -194,6 +195,7 @@ TEST_GUI_OBJECTS = \
$(OBJS)\test_gui_windowtest.obj \
$(OBJS)\test_gui_dialogtest.obj \
$(OBJS)\test_gui_clone.obj \
+ $(OBJS)\test_gui_evtlooptest.obj \
$(OBJS)\test_gui_propagation.obj \
$(OBJS)\test_gui_keyboard.obj \
$(OBJS)\test_gui_fonttest.obj \
@@ -531,6 +533,9 @@ $(OBJS)\test_datetimetest.obj: .\datetime\datetimetest.cpp
$(OBJS)\test_evthandler.obj: .\events\evthandler.cpp
$(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\events\evthandler.cpp
+$(OBJS)\test_evtlooptest.obj: .\events\evtlooptest.cpp
+ $(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\events\evtlooptest.cpp
+
$(OBJS)\test_evtsource.obj: .\events\evtsource.cpp
$(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\events\evtsource.cpp
@@ -939,6 +944,9 @@ $(OBJS)\test_gui_dialogtest.obj: .\controls\dialogtest.cpp
$(OBJS)\test_gui_clone.obj: .\events\clone.cpp
$(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\events\clone.cpp
+$(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
$(CXX) -q -c -P -o$@ $(TEST_GUI_CXXFLAGS) .\events\propagation.cpp
diff --git a/tests/makefile.gcc b/tests/makefile.gcc
index b64a38ad07..875dd6c0c9 100644
--- a/tests/makefile.gcc
+++ b/tests/makefile.gcc
@@ -43,6 +43,7 @@ TEST_OBJECTS = \
$(OBJS)\test_regconf.o \
$(OBJS)\test_datetimetest.o \
$(OBJS)\test_evthandler.o \
+ $(OBJS)\test_evtlooptest.o \
$(OBJS)\test_evtsource.o \
$(OBJS)\test_stopwatch.o \
$(OBJS)\test_timertest.o \
@@ -188,6 +189,7 @@ TEST_GUI_OBJECTS = \
$(OBJS)\test_gui_windowtest.o \
$(OBJS)\test_gui_dialogtest.o \
$(OBJS)\test_gui_clone.o \
+ $(OBJS)\test_gui_evtlooptest.o \
$(OBJS)\test_gui_propagation.o \
$(OBJS)\test_gui_keyboard.o \
$(OBJS)\test_gui_fonttest.o \
@@ -520,6 +522,9 @@ $(OBJS)\test_datetimetest.o: ./datetime/datetimetest.cpp
$(OBJS)\test_evthandler.o: ./events/evthandler.cpp
$(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<
+$(OBJS)\test_evtlooptest.o: ./events/evtlooptest.cpp
+ $(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<
+
$(OBJS)\test_evtsource.o: ./events/evtsource.cpp
$(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<
@@ -928,6 +933,9 @@ $(OBJS)\test_gui_dialogtest.o: ./controls/dialogtest.cpp
$(OBJS)\test_gui_clone.o: ./events/clone.cpp
$(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
+$(OBJS)\test_gui_evtlooptest.o: ./events/evtlooptest.cpp
+ $(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
+
$(OBJS)\test_gui_propagation.o: ./events/propagation.cpp
$(CXX) -c -o $@ $(TEST_GUI_CXXFLAGS) $(CPPDEPS) $<
diff --git a/tests/makefile.vc b/tests/makefile.vc
index 4054a2f575..09066b2462 100644
--- a/tests/makefile.vc
+++ b/tests/makefile.vc
@@ -44,6 +44,7 @@ TEST_OBJECTS = \
$(OBJS)\test_regconf.obj \
$(OBJS)\test_datetimetest.obj \
$(OBJS)\test_evthandler.obj \
+ $(OBJS)\test_evtlooptest.obj \
$(OBJS)\test_evtsource.obj \
$(OBJS)\test_stopwatch.obj \
$(OBJS)\test_timertest.obj \
@@ -191,6 +192,7 @@ TEST_GUI_OBJECTS = \
$(OBJS)\test_gui_windowtest.obj \
$(OBJS)\test_gui_dialogtest.obj \
$(OBJS)\test_gui_clone.obj \
+ $(OBJS)\test_gui_evtlooptest.obj \
$(OBJS)\test_gui_propagation.obj \
$(OBJS)\test_gui_keyboard.obj \
$(OBJS)\test_gui_fonttest.obj \
@@ -671,6 +673,9 @@ $(OBJS)\test_datetimetest.obj: .\datetime\datetimetest.cpp
$(OBJS)\test_evthandler.obj: .\events\evthandler.cpp
$(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\events\evthandler.cpp
+$(OBJS)\test_evtlooptest.obj: .\events\evtlooptest.cpp
+ $(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\events\evtlooptest.cpp
+
$(OBJS)\test_evtsource.obj: .\events\evtsource.cpp
$(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\events\evtsource.cpp
@@ -1079,6 +1084,9 @@ $(OBJS)\test_gui_dialogtest.obj: .\controls\dialogtest.cpp
$(OBJS)\test_gui_clone.obj: .\events\clone.cpp
$(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\clone.cpp
+$(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
$(CXX) /c /nologo /TP /Fo$@ $(TEST_GUI_CXXFLAGS) .\events\propagation.cpp
diff --git a/tests/makefile.wat b/tests/makefile.wat
index 88a3f23823..505bfa3232 100644
--- a/tests/makefile.wat
+++ b/tests/makefile.wat
@@ -319,6 +319,7 @@ TEST_OBJECTS = &
$(OBJS)\test_regconf.obj &
$(OBJS)\test_datetimetest.obj &
$(OBJS)\test_evthandler.obj &
+ $(OBJS)\test_evtlooptest.obj &
$(OBJS)\test_evtsource.obj &
$(OBJS)\test_stopwatch.obj &
$(OBJS)\test_timertest.obj &
@@ -463,6 +464,7 @@ TEST_GUI_OBJECTS = &
$(OBJS)\test_gui_windowtest.obj &
$(OBJS)\test_gui_dialogtest.obj &
$(OBJS)\test_gui_clone.obj &
+ $(OBJS)\test_gui_evtlooptest.obj &
$(OBJS)\test_gui_propagation.obj &
$(OBJS)\test_gui_keyboard.obj &
$(OBJS)\test_gui_fonttest.obj &
@@ -580,6 +582,9 @@ $(OBJS)\test_datetimetest.obj : .AUTODEPEND .\datetime\datetimetest.cpp
$(OBJS)\test_evthandler.obj : .AUTODEPEND .\events\evthandler.cpp
$(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<
+$(OBJS)\test_evtlooptest.obj : .AUTODEPEND .\events\evtlooptest.cpp
+ $(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<
+
$(OBJS)\test_evtsource.obj : .AUTODEPEND .\events\evtsource.cpp
$(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<
@@ -988,6 +993,9 @@ $(OBJS)\test_gui_dialogtest.obj : .AUTODEPEND .\controls\dialogtest.cpp
$(OBJS)\test_gui_clone.obj : .AUTODEPEND .\events\clone.cpp
$(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<
+$(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
$(CXX) -bt=nt -zq -fo=$^@ $(TEST_GUI_CXXFLAGS) $<
diff --git a/tests/test.bkl b/tests/test.bkl
index 4fc52db5a8..afe4637ef0 100644
--- a/tests/test.bkl
+++ b/tests/test.bkl
@@ -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
@@ -189,6 +190,10 @@
controls/windowtest.cpp
controls/dialogtest.cpp
events/clone.cpp
+
+ events/evtlooptest.cpp
events/propagation.cpp
events/keyboard.cpp
font/fonttest.cpp
diff --git a/tests/test_test.dsp b/tests/test_test.dsp
index f1e68bec39..287a816edc 100644
--- a/tests/test_test.dsp
+++ b/tests/test_test.dsp
@@ -205,6 +205,10 @@ SOURCE=.\events\evthandler.cpp
# End Source File
# Begin Source File
+SOURCE=.\events\evtlooptest.cpp
+# End Source File
+# Begin Source File
+
SOURCE=.\events\evtsource.cpp
# End Source File
# Begin Source File
diff --git a/tests/test_test_gui.dsp b/tests/test_test_gui.dsp
index 2569507311..14a6c42d77 100644
--- a/tests/test_test_gui.dsp
+++ b/tests/test_test_gui.dsp
@@ -229,6 +229,10 @@ SOURCE=.\graphics\ellipsization.cpp
# End Source File
# Begin Source File
+SOURCE=.\events\evtlooptest.cpp
+# End Source File
+# Begin Source File
+
SOURCE=.\font\fonttest.cpp
# End Source File
# Begin Source File
diff --git a/tests/test_vc7_test.vcproj b/tests/test_vc7_test.vcproj
index 5a0bd705a8..06d87811a1 100644
--- a/tests/test_vc7_test.vcproj
+++ b/tests/test_vc7_test.vcproj
@@ -370,6 +370,9 @@
+
+
diff --git a/tests/test_vc7_test_gui.vcproj b/tests/test_vc7_test_gui.vcproj
index f3345afef8..012dcdaf5a 100644
--- a/tests/test_vc7_test_gui.vcproj
+++ b/tests/test_vc7_test_gui.vcproj
@@ -388,6 +388,9 @@
+
+
diff --git a/tests/test_vc8_test.vcproj b/tests/test_vc8_test.vcproj
index 9fb69ed0ae..c5e36fc801 100644
--- a/tests/test_vc8_test.vcproj
+++ b/tests/test_vc8_test.vcproj
@@ -529,6 +529,10 @@
RelativePath=".\events\evthandler.cpp"
>
+
+
diff --git a/tests/test_vc8_test_gui.vcproj b/tests/test_vc8_test_gui.vcproj
index a4bd5961b8..a9abb0833f 100644
--- a/tests/test_vc8_test_gui.vcproj
+++ b/tests/test_vc8_test_gui.vcproj
@@ -553,6 +553,10 @@
RelativePath=".\graphics\ellipsization.cpp"
>
+
+
diff --git a/tests/test_vc9_test.vcproj b/tests/test_vc9_test.vcproj
index 3324157e51..f1ca266e4f 100644
--- a/tests/test_vc9_test.vcproj
+++ b/tests/test_vc9_test.vcproj
@@ -515,6 +515,10 @@
RelativePath=".\events\evthandler.cpp"
>
+
+
diff --git a/tests/test_vc9_test_gui.vcproj b/tests/test_vc9_test_gui.vcproj
index 746fd5d0a2..640de7a67d 100644
--- a/tests/test_vc9_test_gui.vcproj
+++ b/tests/test_vc9_test_gui.vcproj
@@ -539,6 +539,10 @@
RelativePath=".\graphics\ellipsization.cpp"
>
+
+