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"
33 // FIXME: Currently under OS X testing paint event doesn't work because neither
34 // calling Refresh()+Update() nor even sending wxPaintEvent directly to
35 // the window doesn't result in calls to its event handlers, so disable
36 // some tests there. But this should be fixed and the tests reenabled
37 // because wxPaintEvent propagation in wxScrolledWindow is a perfect
38 // example of fragile code that could be broken under OS X.
40 #define CAN_TEST_PAINT_EVENTS
46 // this string will record the execution of all handlers
50 wxDEFINE_EVENT(TEST_EVT
, wxCommandEvent
);
52 // a custom event handler tracing the propagation of the events of the
54 template <class Event
>
55 class TestEvtHandlerBase
: public wxEvtHandler
58 TestEvtHandlerBase(wxEventType evtType
, char tag
)
63 static_cast<wxEventFunction
>(&TestEvtHandlerBase::OnTest
));
66 // override ProcessEvent() to confirm that it is called for all event
67 // handlers in the chain
68 virtual bool ProcessEvent(wxEvent
& event
)
70 if ( event
.GetEventType() == m_evtType
)
71 g_str
+= 'o'; // "o" == "overridden"
73 return wxEvtHandler::ProcessEvent(event
);
77 void OnTest(wxEvent
& event
)
84 const wxEventType m_evtType
;
87 wxDECLARE_NO_COPY_TEMPLATE_CLASS(TestEvtHandlerBase
, Event
);
90 struct TestEvtHandler
: TestEvtHandlerBase
<wxCommandEvent
>
92 TestEvtHandler(char tag
)
93 : TestEvtHandlerBase
<wxCommandEvent
>(TEST_EVT
, tag
)
98 struct TestMenuEvtHandler
: TestEvtHandlerBase
<wxCommandEvent
>
100 TestMenuEvtHandler(char tag
)
101 : TestEvtHandlerBase
<wxCommandEvent
>(wxEVT_MENU
, tag
)
106 struct TestPaintEvtHandler
: TestEvtHandlerBase
<wxPaintEvent
>
108 TestPaintEvtHandler(char tag
)
109 : TestEvtHandlerBase
<wxPaintEvent
>(wxEVT_PAINT
, tag
)
114 // Another custom event handler, suitable for use with Connect().
115 struct TestEvtSink
: wxEvtHandler
117 TestEvtSink(char tag
)
122 void Handle(wxEvent
& event
)
131 wxDECLARE_NO_COPY_CLASS(TestEvtSink
);
134 // a window handling the test event
135 class TestWindow
: public wxWindow
138 TestWindow(wxWindow
*parent
, char tag
)
139 : wxWindow(parent
, wxID_ANY
),
142 Connect(TEST_EVT
, wxCommandEventHandler(TestWindow::OnTest
));
146 void OnTest(wxCommandEvent
& event
)
155 DECLARE_NO_COPY_CLASS(TestWindow
)
158 // a scroll window handling paint event: we want to have a special test case
159 // for this because the event propagation is complicated even further than
160 // usual here by the presence of wxScrollHelperEvtHandler in the event handlers
161 // chain and the fact that OnDraw() virtual method must be called if EVT_PAINT
163 class TestScrollWindow
: public wxScrolledWindow
166 TestScrollWindow(wxWindow
*parent
)
167 : wxScrolledWindow(parent
, wxID_ANY
)
169 Connect(wxEVT_PAINT
, wxPaintEventHandler(TestScrollWindow::OnPaint
));
172 void GeneratePaintEvent()
175 // We need to map the window, otherwise we're not going to get any
176 // paint events for it.
179 // Ignore events generated during the initial mapping.
187 virtual void OnDraw(wxDC
& WXUNUSED(dc
))
189 g_str
+= 'D'; // draw
193 void OnPaint(wxPaintEvent
& event
)
195 g_str
+= 'P'; // paint
199 wxDECLARE_NO_COPY_CLASS(TestScrollWindow
);
202 int DoFilterEvent(wxEvent
& event
)
204 if ( event
.GetEventType() == TEST_EVT
||
205 event
.GetEventType() == wxEVT_MENU
)
211 bool DoProcessEvent(wxEvent
& event
)
213 if ( event
.GetEventType() == TEST_EVT
||
214 event
.GetEventType() == wxEVT_MENU
)
220 } // anonymous namespace
222 // --------------------------------------------------------------------------
224 // --------------------------------------------------------------------------
226 class EventPropagationTestCase
: public CppUnit::TestCase
229 EventPropagationTestCase() {}
231 virtual void setUp();
232 virtual void tearDown();
235 CPPUNIT_TEST_SUITE( EventPropagationTestCase
);
236 CPPUNIT_TEST( OneHandler
);
237 CPPUNIT_TEST( TwoHandlers
);
238 CPPUNIT_TEST( WindowWithoutHandler
);
239 CPPUNIT_TEST( WindowWithHandler
);
240 CPPUNIT_TEST( ForwardEvent
);
241 CPPUNIT_TEST( ScrollWindowWithoutHandler
);
242 CPPUNIT_TEST( ScrollWindowWithHandler
);
243 CPPUNIT_TEST( MenuEvent
);
244 CPPUNIT_TEST( DocView
);
245 CPPUNIT_TEST_SUITE_END();
249 void WindowWithoutHandler();
250 void WindowWithHandler();
252 void ScrollWindowWithoutHandler();
253 void ScrollWindowWithHandler();
257 DECLARE_NO_COPY_CLASS(EventPropagationTestCase
)
260 // register in the unnamed registry so that these tests are run by default
261 CPPUNIT_TEST_SUITE_REGISTRATION( EventPropagationTestCase
);
263 // also include in its own registry so that these tests can be run alone
264 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EventPropagationTestCase
, "EventPropagationTestCase" );
266 void EventPropagationTestCase::setUp()
268 SetFilterEventFunc(DoFilterEvent
);
269 SetProcessEventFunc(DoProcessEvent
);
274 void EventPropagationTestCase::tearDown()
276 SetFilterEventFunc(NULL
);
277 SetProcessEventFunc(NULL
);
280 void EventPropagationTestCase::OneHandler()
282 wxCommandEvent
event(TEST_EVT
);
283 TestEvtHandler
h1('1');
284 h1
.ProcessEvent(event
);
285 CPPUNIT_ASSERT_EQUAL( "oa1A", g_str
);
288 void EventPropagationTestCase::TwoHandlers()
290 wxCommandEvent
event(TEST_EVT
);
291 TestEvtHandler
h1('1');
292 TestEvtHandler
h2('2');
293 h1
.SetNextHandler(&h2
);
294 h2
.SetPreviousHandler(&h1
);
295 h1
.ProcessEvent(event
);
296 CPPUNIT_ASSERT_EQUAL( "oa1o2A", g_str
);
299 void EventPropagationTestCase::WindowWithoutHandler()
301 wxCommandEvent
event(TEST_EVT
);
302 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
303 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
305 TestWindow
* const child
= new TestWindow(parent
, 'c');
307 child
->GetEventHandler()->ProcessEvent(event
);
308 CPPUNIT_ASSERT_EQUAL( "acpA", g_str
);
311 void EventPropagationTestCase::WindowWithHandler()
313 wxCommandEvent
event(TEST_EVT
);
314 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
315 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
317 TestWindow
* const child
= new TestWindow(parent
, 'c');
319 TestEvtHandler
h1('1');
320 child
->PushEventHandler(&h1
);
321 wxON_BLOCK_EXIT_OBJ1( *child
, wxWindow::PopEventHandler
, false );
322 TestEvtHandler
h2('2');
323 child
->PushEventHandler(&h2
);
324 wxON_BLOCK_EXIT_OBJ1( *child
, wxWindow::PopEventHandler
, false );
326 child
->HandleWindowEvent(event
);
327 CPPUNIT_ASSERT_EQUAL( "oa2o1cpA", g_str
);
330 void EventPropagationTestCase::ForwardEvent()
332 // The idea of this test is to check that the events explicitly forwarded
333 // to another event handler still get pre/post-processed as usual as this
334 // used to be broken by the fixes trying to avoid duplicate processing.
335 TestWindow
* const win
= new TestWindow(wxTheApp
->GetTopWindow(), 'w');
336 wxON_BLOCK_EXIT_OBJ0( *win
, wxWindow::Destroy
);
338 TestEvtHandler
h1('1');
339 win
->PushEventHandler(&h1
);
340 wxON_BLOCK_EXIT_OBJ1( *win
, wxWindow::PopEventHandler
, false );
342 class ForwardEvtHandler
: public wxEvtHandler
345 ForwardEvtHandler(wxEvtHandler
& h
) : m_h(&h
) { }
347 virtual bool ProcessEvent(wxEvent
& event
)
351 return m_h
->ProcessEvent(event
);
358 // First send the event directly to f.
359 wxCommandEvent
event1(TEST_EVT
);
360 f
.ProcessEvent(event1
);
361 CPPUNIT_ASSERT_EQUAL( "foa1wA", g_str
);
364 // And then also test sending it to f indirectly.
365 wxCommandEvent
event2(TEST_EVT
);
366 TestEvtHandler
h2('2');
367 h2
.SetNextHandler(&f
);
368 h2
.ProcessEvent(event2
);
369 CPPUNIT_ASSERT_EQUAL( "oa2fo1wAA", g_str
);
372 void EventPropagationTestCase::ScrollWindowWithoutHandler()
374 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
375 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
377 TestScrollWindow
* const win
= new TestScrollWindow(parent
);
379 #ifdef CAN_TEST_PAINT_EVENTS
380 win
->GeneratePaintEvent();
381 CPPUNIT_ASSERT_EQUAL( "PD", g_str
);
385 wxCommandEvent
eventCmd(TEST_EVT
);
386 win
->HandleWindowEvent(eventCmd
);
387 CPPUNIT_ASSERT_EQUAL( "apA", g_str
);
390 void EventPropagationTestCase::ScrollWindowWithHandler()
392 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
393 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
395 TestScrollWindow
* const win
= new TestScrollWindow(parent
);
397 #ifdef CAN_TEST_PAINT_EVENTS
398 TestPaintEvtHandler
h('h');
399 win
->PushEventHandler(&h
);
400 wxON_BLOCK_EXIT_OBJ1( *win
, wxWindow::PopEventHandler
, false );
402 win
->GeneratePaintEvent();
403 CPPUNIT_ASSERT_EQUAL( "ohPD", g_str
);
407 wxCommandEvent
eventCmd(TEST_EVT
);
408 win
->HandleWindowEvent(eventCmd
);
409 CPPUNIT_ASSERT_EQUAL( "apA", g_str
);
412 // Create a menu bar with a single menu containing wxID_APPLY menu item and
413 // attach it to the specified frame.
414 wxMenu
* CreateTestMenu(wxFrame
* frame
)
416 wxMenu
* const menu
= new wxMenu
;
417 menu
->Append(wxID_APPLY
);
418 wxMenuBar
* const mb
= new wxMenuBar
;
419 mb
->Append(menu
, "&Menu");
420 frame
->SetMenuBar(mb
);
425 // Helper for checking that the menu event processing resulted in the expected
426 // output from the handlers.
428 // Notice that this is supposed to be used with ASSERT_MENU_EVENT_RESULT()
429 // macro to make the file name and line number of the caller appear in the
432 CheckMenuEvent(wxMenu
* menu
, const char* result
, CppUnit::SourceLine sourceLine
)
436 // Trigger the menu event: this is more reliable than using
437 // wxUIActionSimulator and currently works in all ports as they all call
438 // wxMenuBase::SendEvent() from their respective menu event handlers.
439 menu
->SendEvent(wxID_APPLY
);
441 CPPUNIT_NS::assertEquals( result
, g_str
, sourceLine
, "" );
444 #define ASSERT_MENU_EVENT_RESULT(menu, result) \
445 CheckMenuEvent((menu), (result), CPPUNIT_SOURCELINE())
447 void EventPropagationTestCase::MenuEvent()
449 wxFrame
* const frame
= static_cast<wxFrame
*>(wxTheApp
->GetTopWindow());
451 // Create a minimal menu bar.
452 wxMenu
* const menu
= CreateTestMenu(frame
);
453 wxMenuBar
* const mb
= menu
->GetMenuBar();
454 wxScopedPtr
<wxMenuBar
> ensureMenuBarDestruction(mb
);
455 wxON_BLOCK_EXIT_OBJ1( *frame
, wxFrame::SetMenuBar
, (wxMenuBar
*)NULL
);
457 // Check that wxApp gets the event exactly once.
458 ASSERT_MENU_EVENT_RESULT( menu
, "aA" );
461 // Check that the menu event handler is called.
462 TestMenuEvtHandler
hm('m'); // 'm' for "menu"
463 menu
->SetNextHandler(&hm
);
464 wxON_BLOCK_EXIT_OBJ1( *menu
,
465 wxEvtHandler::SetNextHandler
, (wxEvtHandler
*)NULL
);
466 ASSERT_MENU_EVENT_RESULT( menu
, "aomA" );
469 // Test that the event handler associated with the menu bar gets the event.
470 TestMenuEvtHandler
hb('b'); // 'b' for "menu Bar"
471 mb
->PushEventHandler(&hb
);
472 wxON_BLOCK_EXIT_OBJ1( *mb
, wxWindow::PopEventHandler
, false );
474 ASSERT_MENU_EVENT_RESULT( menu
, "aomobA" );
477 // Also test that the window to which the menu belongs gets the event.
478 TestMenuEvtHandler
hw('w'); // 'w' for "Window"
479 frame
->PushEventHandler(&hw
);
480 wxON_BLOCK_EXIT_OBJ1( *frame
, wxWindow::PopEventHandler
, false );
482 ASSERT_MENU_EVENT_RESULT( menu
, "aomobowA" );
485 // Minimal viable implementations of wxDocument and wxView.
486 class EventTestDocument
: public wxDocument
489 EventTestDocument() { }
491 wxDECLARE_DYNAMIC_CLASS(EventTestDocument
);
494 class EventTestView
: public wxView
499 virtual void OnDraw(wxDC
*) { }
501 wxDECLARE_DYNAMIC_CLASS(EventTestView
);
504 wxIMPLEMENT_DYNAMIC_CLASS(EventTestDocument
, wxDocument
);
505 wxIMPLEMENT_DYNAMIC_CLASS(EventTestView
, wxView
);
507 void EventPropagationTestCase::DocView()
509 // Set up the parent frame and its menu bar.
510 wxDocManager docManager
;
512 wxScopedPtr
<wxDocMDIParentFrame
>
513 parent(new wxDocMDIParentFrame(&docManager
, NULL
, wxID_ANY
, "Parent"));
515 wxMenu
* const menu
= CreateTestMenu(parent
.get());
518 // Set up the event handlers.
519 TestEvtSink
sinkDM('m');
520 docManager
.Connect(wxEVT_MENU
,
521 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkDM
);
523 TestEvtSink
sinkParent('p');
524 parent
->Connect(wxEVT_MENU
,
525 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkParent
);
528 // Check that wxDocManager and wxFrame get the event in order.
529 ASSERT_MENU_EVENT_RESULT( menu
, "ampA" );
532 // Now check what happens if we have an active document.
533 wxDocTemplate
docTemplate(&docManager
, "Test", "", "", "",
534 "Test Document", "Test View",
535 wxCLASSINFO(EventTestDocument
),
536 wxCLASSINFO(EventTestView
));
537 wxDocument
* const doc
= docTemplate
.CreateDocument("");
538 wxView
* const view
= doc
->GetFirstView();
541 child(new wxDocMDIChildFrame(doc
, view
, parent
.get(), wxID_ANY
, "Child"));
543 wxMenu
* const menuChild
= CreateTestMenu(child
.get());
546 // There are a lot of hacks related to child frame menu bar handling in
547 // wxGTK and, in particular, the code in src/gtk/mdi.cpp relies on getting
548 // idle events to really put everything in place. Moreover, as wxGTK uses
549 // GtkNotebook as its MDI pages container, the frame must be shown for all
550 // this to work as gtk_notebook_set_current_page() doesn't do anything if
551 // called for a hidden window (this incredible fact cost me quite some time
552 // to find empirically -- only to notice its confirmation in GTK+
553 // documentation immediately afterwards). So just do whatever it takes to
554 // make things work "as usual".
560 TestEvtSink
sinkDoc('d');
561 doc
->Connect(wxEVT_MENU
,
562 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkDoc
);
564 TestEvtSink
sinkView('v');
565 view
->Connect(wxEVT_MENU
,
566 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkView
);
568 TestEvtSink
sinkChild('c');
569 child
->Connect(wxEVT_MENU
,
570 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkChild
);
572 // Check that wxDocument, wxView, wxDocManager, child frame and the parent
573 // get the event in order.
574 ASSERT_MENU_EVENT_RESULT( menuChild
, "advmcpA" );