From d3ad22bdb33cdba830923932e208f030f61669b4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 3 Jul 2013 00:26:13 +0000 Subject: [PATCH] Add wxEventLoop::ScheduleExit(). 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 --- docs/changes.txt | 1 + include/wx/cocoa/evtloop.h | 2 +- include/wx/evtloop.h | 24 ++++++- include/wx/gtk/evtloop.h | 2 +- include/wx/osx/core/evtloop.h | 2 +- interface/wx/evtloop.h | 40 ++++++++++- src/cocoa/evtloop.mm | 4 +- src/common/evtloopcmn.cpp | 20 +++++- src/gtk/evtloop.cpp | 28 +++++++- src/gtk1/evtloop.cpp | 28 +++++++- src/motif/evtloop.cpp | 4 +- src/osx/carbon/evtloop.cpp | 10 ++- src/osx/cocoa/evtloop.mm | 107 +++++++++++++++++++++++++-- src/osx/core/evtloop_cf.cpp | 2 +- src/x11/evtloop.cpp | 2 +- tests/Makefile.in | 8 +++ tests/events/evtlooptest.cpp | 128 +++++++++++++++++++++++++++++++++ tests/makefile.bcc | 8 +++ tests/makefile.gcc | 8 +++ tests/makefile.vc | 8 +++ tests/makefile.wat | 8 +++ tests/test.bkl | 5 ++ tests/test_test.dsp | 4 ++ tests/test_test_gui.dsp | 4 ++ tests/test_vc7_test.vcproj | 3 + tests/test_vc7_test_gui.vcproj | 3 + tests/test_vc8_test.vcproj | 4 ++ tests/test_vc8_test_gui.vcproj | 4 ++ tests/test_vc9_test.vcproj | 4 ++ tests/test_vc9_test_gui.vcproj | 4 ++ 30 files changed, 450 insertions(+), 29 deletions(-) create mode 100644 tests/events/evtlooptest.cpp 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); + + /** + 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. 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" > + + -- 2.45.2