1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: tests/events/propagation.cpp
3 // Purpose: Test events propagation
4 // Author: Vadim Zeitlin
7 // Copyright: (c) 2009 Vadim Zeitlin <vadim@wxwidgets.org>
8 ///////////////////////////////////////////////////////////////////////////////
10 // ----------------------------------------------------------------------------
12 // ----------------------------------------------------------------------------
23 #include "wx/scrolwin.h"
24 #include "wx/window.h"
27 #include "wx/docmdi.h"
30 #include "wx/scopedptr.h"
31 #include "wx/scopeguard.h"
36 // this string will record the execution of all handlers
40 wxDEFINE_EVENT(TEST_EVT
, wxCommandEvent
);
42 // a custom event handler tracing the propagation of the events of the
44 template <class Event
>
45 class TestEvtHandlerBase
: public wxEvtHandler
48 TestEvtHandlerBase(wxEventType evtType
, char tag
)
53 static_cast<wxEventFunction
>(&TestEvtHandlerBase::OnTest
));
56 // override ProcessEvent() to confirm that it is called for all event
57 // handlers in the chain
58 virtual bool ProcessEvent(wxEvent
& event
)
60 if ( event
.GetEventType() == m_evtType
)
61 g_str
+= 'o'; // "o" == "overridden"
63 return wxEvtHandler::ProcessEvent(event
);
67 void OnTest(wxEvent
& event
)
74 const wxEventType m_evtType
;
77 wxDECLARE_NO_COPY_TEMPLATE_CLASS(TestEvtHandlerBase
, Event
);
80 struct TestEvtHandler
: TestEvtHandlerBase
<wxCommandEvent
>
82 TestEvtHandler(char tag
)
83 : TestEvtHandlerBase
<wxCommandEvent
>(TEST_EVT
, tag
)
88 struct TestMenuEvtHandler
: TestEvtHandlerBase
<wxCommandEvent
>
90 TestMenuEvtHandler(char tag
)
91 : TestEvtHandlerBase
<wxCommandEvent
>(wxEVT_MENU
, tag
)
96 struct TestPaintEvtHandler
: TestEvtHandlerBase
<wxPaintEvent
>
98 TestPaintEvtHandler(char tag
)
99 : TestEvtHandlerBase
<wxPaintEvent
>(wxEVT_PAINT
, tag
)
104 // Another custom event handler, suitable for use with Connect().
105 struct TestEvtSink
: wxEvtHandler
107 TestEvtSink(char tag
)
112 void Handle(wxEvent
& event
)
121 wxDECLARE_NO_COPY_CLASS(TestEvtSink
);
124 // a window handling the test event
125 class TestWindow
: public wxWindow
128 TestWindow(wxWindow
*parent
, char tag
)
129 : wxWindow(parent
, wxID_ANY
),
132 Connect(TEST_EVT
, wxCommandEventHandler(TestWindow::OnTest
));
136 void OnTest(wxCommandEvent
& event
)
145 DECLARE_NO_COPY_CLASS(TestWindow
)
148 // a scroll window handling paint event: we want to have a special test case
149 // for this because the event propagation is complicated even further than
150 // usual here by the presence of wxScrollHelperEvtHandler in the event handlers
151 // chain and the fact that OnDraw() virtual method must be called if EVT_PAINT
153 class TestScrollWindow
: public wxScrolledWindow
156 TestScrollWindow(wxWindow
*parent
)
157 : wxScrolledWindow(parent
, wxID_ANY
)
159 Connect(wxEVT_PAINT
, wxPaintEventHandler(TestScrollWindow::OnPaint
));
162 virtual void OnDraw(wxDC
& WXUNUSED(dc
))
164 g_str
+= 'D'; // draw
168 void OnPaint(wxPaintEvent
& event
)
170 g_str
+= 'P'; // paint
174 wxDECLARE_NO_COPY_CLASS(TestScrollWindow
);
177 int DoFilterEvent(wxEvent
& event
)
179 if ( event
.GetEventType() == TEST_EVT
||
180 event
.GetEventType() == wxEVT_MENU
)
186 bool DoProcessEvent(wxEvent
& event
)
188 if ( event
.GetEventType() == TEST_EVT
||
189 event
.GetEventType() == wxEVT_MENU
)
195 } // anonymous namespace
197 // --------------------------------------------------------------------------
199 // --------------------------------------------------------------------------
201 class EventPropagationTestCase
: public CppUnit::TestCase
204 EventPropagationTestCase() {}
206 virtual void setUp();
207 virtual void tearDown();
210 CPPUNIT_TEST_SUITE( EventPropagationTestCase
);
211 CPPUNIT_TEST( OneHandler
);
212 CPPUNIT_TEST( TwoHandlers
);
213 CPPUNIT_TEST( WindowWithoutHandler
);
214 CPPUNIT_TEST( WindowWithHandler
);
215 CPPUNIT_TEST( ForwardEvent
);
216 CPPUNIT_TEST( ScrollWindowWithoutHandler
);
217 CPPUNIT_TEST( ScrollWindowWithHandler
);
218 CPPUNIT_TEST( MenuEvent
);
219 CPPUNIT_TEST( DocView
);
220 CPPUNIT_TEST_SUITE_END();
224 void WindowWithoutHandler();
225 void WindowWithHandler();
227 void ScrollWindowWithoutHandler();
228 void ScrollWindowWithHandler();
232 DECLARE_NO_COPY_CLASS(EventPropagationTestCase
)
235 // register in the unnamed registry so that these tests are run by default
236 CPPUNIT_TEST_SUITE_REGISTRATION( EventPropagationTestCase
);
238 // also include in its own registry so that these tests can be run alone
239 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EventPropagationTestCase
, "EventPropagationTestCase" );
241 void EventPropagationTestCase::setUp()
243 SetFilterEventFunc(DoFilterEvent
);
244 SetProcessEventFunc(DoProcessEvent
);
249 void EventPropagationTestCase::tearDown()
251 SetFilterEventFunc(NULL
);
252 SetProcessEventFunc(NULL
);
255 void EventPropagationTestCase::OneHandler()
257 wxCommandEvent
event(TEST_EVT
);
258 TestEvtHandler
h1('1');
259 h1
.ProcessEvent(event
);
260 CPPUNIT_ASSERT_EQUAL( "oa1A", g_str
);
263 void EventPropagationTestCase::TwoHandlers()
265 wxCommandEvent
event(TEST_EVT
);
266 TestEvtHandler
h1('1');
267 TestEvtHandler
h2('2');
268 h1
.SetNextHandler(&h2
);
269 h2
.SetPreviousHandler(&h1
);
270 h1
.ProcessEvent(event
);
271 CPPUNIT_ASSERT_EQUAL( "oa1o2A", g_str
);
274 void EventPropagationTestCase::WindowWithoutHandler()
276 wxCommandEvent
event(TEST_EVT
);
277 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
278 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
280 TestWindow
* const child
= new TestWindow(parent
, 'c');
282 child
->GetEventHandler()->ProcessEvent(event
);
283 CPPUNIT_ASSERT_EQUAL( "acpA", g_str
);
286 void EventPropagationTestCase::WindowWithHandler()
288 wxCommandEvent
event(TEST_EVT
);
289 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
290 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
292 TestWindow
* const child
= new TestWindow(parent
, 'c');
294 TestEvtHandler
h1('1');
295 child
->PushEventHandler(&h1
);
296 wxON_BLOCK_EXIT_OBJ1( *child
, wxWindow::PopEventHandler
, false );
297 TestEvtHandler
h2('2');
298 child
->PushEventHandler(&h2
);
299 wxON_BLOCK_EXIT_OBJ1( *child
, wxWindow::PopEventHandler
, false );
301 child
->HandleWindowEvent(event
);
302 CPPUNIT_ASSERT_EQUAL( "oa2o1cpA", g_str
);
305 void EventPropagationTestCase::ForwardEvent()
307 // The idea of this test is to check that the events explicitly forwarded
308 // to another event handler still get pre/post-processed as usual as this
309 // used to be broken by the fixes trying to avoid duplicate processing.
310 TestWindow
* const win
= new TestWindow(wxTheApp
->GetTopWindow(), 'w');
311 wxON_BLOCK_EXIT_OBJ0( *win
, wxWindow::Destroy
);
313 TestEvtHandler
h1('1');
314 win
->PushEventHandler(&h1
);
315 wxON_BLOCK_EXIT_OBJ1( *win
, wxWindow::PopEventHandler
, false );
317 class ForwardEvtHandler
: public wxEvtHandler
320 ForwardEvtHandler(wxEvtHandler
& h
) : m_h(&h
) { }
322 virtual bool ProcessEvent(wxEvent
& event
)
326 return m_h
->ProcessEvent(event
);
333 // First send the event directly to f.
334 wxCommandEvent
event1(TEST_EVT
);
335 f
.ProcessEvent(event1
);
336 CPPUNIT_ASSERT_EQUAL( "foa1wA", g_str
);
339 // And then also test sending it to f indirectly.
340 wxCommandEvent
event2(TEST_EVT
);
341 TestEvtHandler
h2('2');
342 h2
.SetNextHandler(&f
);
343 h2
.ProcessEvent(event2
);
344 CPPUNIT_ASSERT_EQUAL( "oa2fo1wAA", g_str
);
347 void EventPropagationTestCase::ScrollWindowWithoutHandler()
349 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
350 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
352 TestScrollWindow
* const win
= new TestScrollWindow(parent
);
354 #if !defined(__WXOSX__) && !defined(__WXGTK3__)
355 wxPaintEvent
event(win
->GetId());
356 win
->ProcessWindowEvent(event
);
357 CPPUNIT_ASSERT_EQUAL( "PD", g_str
);
360 wxCommandEvent
eventCmd(TEST_EVT
);
361 win
->HandleWindowEvent(eventCmd
);
362 CPPUNIT_ASSERT_EQUAL( "apA", g_str
);
365 void EventPropagationTestCase::ScrollWindowWithHandler()
367 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
368 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
370 TestScrollWindow
* const win
= new TestScrollWindow(parent
);
372 #if !defined(__WXOSX__) && !defined(__WXGTK3__)
373 TestPaintEvtHandler
h('h');
374 win
->PushEventHandler(&h
);
375 wxON_BLOCK_EXIT_OBJ1( *win
, wxWindow::PopEventHandler
, false );
377 wxPaintEvent
event(win
->GetId());
378 win
->ProcessWindowEvent(event
);
379 CPPUNIT_ASSERT_EQUAL( "ohPD", g_str
);
383 wxCommandEvent
eventCmd(TEST_EVT
);
384 win
->HandleWindowEvent(eventCmd
);
385 CPPUNIT_ASSERT_EQUAL( "apA", g_str
);
388 // Create a menu bar with a single menu containing wxID_APPLY menu item and
389 // attach it to the specified frame.
390 wxMenu
* CreateTestMenu(wxFrame
* frame
)
392 wxMenu
* const menu
= new wxMenu
;
393 menu
->Append(wxID_APPLY
);
394 wxMenuBar
* const mb
= new wxMenuBar
;
395 mb
->Append(menu
, "&Menu");
396 frame
->SetMenuBar(mb
);
401 // Helper for checking that the menu event processing resulted in the expected
402 // output from the handlers.
404 // Notice that this is supposed to be used with ASSERT_MENU_EVENT_RESULT()
405 // macro to make the file name and line number of the caller appear in the
408 CheckMenuEvent(wxMenu
* menu
, const char* result
, CppUnit::SourceLine sourceLine
)
412 // Trigger the menu event: this is more reliable than using
413 // wxUIActionSimulator and currently works in all ports as they all call
414 // wxMenuBase::SendEvent() from their respective menu event handlers.
415 menu
->SendEvent(wxID_APPLY
);
417 CPPUNIT_NS::assertEquals( result
, g_str
, sourceLine
, "" );
420 #define ASSERT_MENU_EVENT_RESULT(menu, result) \
421 CheckMenuEvent((menu), (result), CPPUNIT_SOURCELINE())
423 void EventPropagationTestCase::MenuEvent()
425 wxFrame
* const frame
= static_cast<wxFrame
*>(wxTheApp
->GetTopWindow());
427 // Create a minimal menu bar.
428 wxMenu
* const menu
= CreateTestMenu(frame
);
429 wxMenuBar
* const mb
= menu
->GetMenuBar();
430 wxScopedPtr
<wxMenuBar
> ensureMenuBarDestruction(mb
);
431 wxON_BLOCK_EXIT_OBJ1( *frame
, wxFrame::SetMenuBar
, (wxMenuBar
*)NULL
);
433 // Check that wxApp gets the event exactly once.
434 ASSERT_MENU_EVENT_RESULT( menu
, "aA" );
437 // Check that the menu event handler is called.
438 TestMenuEvtHandler
hm('m'); // 'm' for "menu"
439 menu
->SetNextHandler(&hm
);
440 wxON_BLOCK_EXIT_OBJ1( *menu
,
441 wxEvtHandler::SetNextHandler
, (wxEvtHandler
*)NULL
);
442 ASSERT_MENU_EVENT_RESULT( menu
, "aomA" );
445 // Test that the event handler associated with the menu bar gets the event.
446 TestMenuEvtHandler
hb('b'); // 'b' for "menu Bar"
447 mb
->PushEventHandler(&hb
);
448 wxON_BLOCK_EXIT_OBJ1( *mb
, wxWindow::PopEventHandler
, false );
450 ASSERT_MENU_EVENT_RESULT( menu
, "aomobA" );
453 // Also test that the window to which the menu belongs gets the event.
454 TestMenuEvtHandler
hw('w'); // 'w' for "Window"
455 frame
->PushEventHandler(&hw
);
456 wxON_BLOCK_EXIT_OBJ1( *frame
, wxWindow::PopEventHandler
, false );
458 ASSERT_MENU_EVENT_RESULT( menu
, "aomobowA" );
461 // Minimal viable implementations of wxDocument and wxView.
462 class EventTestDocument
: public wxDocument
465 EventTestDocument() { }
467 wxDECLARE_DYNAMIC_CLASS(EventTestDocument
);
470 class EventTestView
: public wxView
475 virtual void OnDraw(wxDC
*) { }
477 wxDECLARE_DYNAMIC_CLASS(EventTestView
);
480 wxIMPLEMENT_DYNAMIC_CLASS(EventTestDocument
, wxDocument
);
481 wxIMPLEMENT_DYNAMIC_CLASS(EventTestView
, wxView
);
483 void EventPropagationTestCase::DocView()
485 // Set up the parent frame and its menu bar.
486 wxDocManager docManager
;
488 wxScopedPtr
<wxDocMDIParentFrame
>
489 parent(new wxDocMDIParentFrame(&docManager
, NULL
, wxID_ANY
, "Parent"));
491 wxMenu
* const menu
= CreateTestMenu(parent
.get());
494 // Set up the event handlers.
495 TestEvtSink
sinkDM('m');
496 docManager
.Connect(wxEVT_MENU
,
497 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkDM
);
499 TestEvtSink
sinkParent('p');
500 parent
->Connect(wxEVT_MENU
,
501 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkParent
);
504 // Check that wxDocManager and wxFrame get the event in order.
505 ASSERT_MENU_EVENT_RESULT( menu
, "ampA" );
508 // Now check what happens if we have an active document.
509 wxDocTemplate
docTemplate(&docManager
, "Test", "", "", "",
510 "Test Document", "Test View",
511 wxCLASSINFO(EventTestDocument
),
512 wxCLASSINFO(EventTestView
));
513 wxDocument
* const doc
= docTemplate
.CreateDocument("");
514 wxView
* const view
= doc
->GetFirstView();
517 child(new wxDocMDIChildFrame(doc
, view
, parent
.get(), wxID_ANY
, "Child"));
519 wxMenu
* const menuChild
= CreateTestMenu(child
.get());
522 // There are a lot of hacks related to child frame menu bar handling in
523 // wxGTK and, in particular, the code in src/gtk/mdi.cpp relies on getting
524 // idle events to really put everything in place. Moreover, as wxGTK uses
525 // GtkNotebook as its MDI pages container, the frame must be shown for all
526 // this to work as gtk_notebook_set_current_page() doesn't do anything if
527 // called for a hidden window (this incredible fact cost me quite some time
528 // to find empirically -- only to notice its confirmation in GTK+
529 // documentation immediately afterwards). So just do whatever it takes to
530 // make things work "as usual".
536 TestEvtSink
sinkDoc('d');
537 doc
->Connect(wxEVT_MENU
,
538 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkDoc
);
540 TestEvtSink
sinkView('v');
541 view
->Connect(wxEVT_MENU
,
542 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkView
);
544 TestEvtSink
sinkChild('c');
545 child
->Connect(wxEVT_MENU
,
546 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkChild
);
548 // Check that wxDocument, wxView, wxDocManager, child frame and the parent
549 // get the event in order.
550 ASSERT_MENU_EVENT_RESULT( menuChild
, "advmcpA" );