1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: tests/events/propagation.cpp
3 // Purpose: Test events propagation
4 // Author: Vadim Zeitlin
6 // Copyright: (c) 2009 Vadim Zeitlin <vadim@wxwidgets.org>
7 ///////////////////////////////////////////////////////////////////////////////
9 // ----------------------------------------------------------------------------
11 // ----------------------------------------------------------------------------
22 #include "wx/scrolwin.h"
23 #include "wx/window.h"
26 #include "wx/docmdi.h"
29 #include "wx/scopedptr.h"
30 #include "wx/scopeguard.h"
31 #include "wx/toolbar.h"
32 #include "wx/uiaction.h"
34 // FIXME: Currently under OS X testing paint event doesn't work because neither
35 // calling Refresh()+Update() nor even sending wxPaintEvent directly to
36 // the window doesn't result in calls to its event handlers, so disable
37 // some tests there. But this should be fixed and the tests reenabled
38 // because wxPaintEvent propagation in wxScrolledWindow is a perfect
39 // example of fragile code that could be broken under OS X.
41 #define CAN_TEST_PAINT_EVENTS
47 // this string will record the execution of all handlers
51 wxDEFINE_EVENT(TEST_EVT
, wxCommandEvent
);
53 // a custom event handler tracing the propagation of the events of the
55 template <class Event
>
56 class TestEvtHandlerBase
: public wxEvtHandler
59 TestEvtHandlerBase(wxEventType evtType
, char tag
)
64 static_cast<wxEventFunction
>(&TestEvtHandlerBase::OnTest
));
67 // override ProcessEvent() to confirm that it is called for all event
68 // handlers in the chain
69 virtual bool ProcessEvent(wxEvent
& event
)
71 if ( event
.GetEventType() == m_evtType
)
72 g_str
+= 'o'; // "o" == "overridden"
74 return wxEvtHandler::ProcessEvent(event
);
78 void OnTest(wxEvent
& event
)
85 const wxEventType m_evtType
;
88 wxDECLARE_NO_COPY_TEMPLATE_CLASS(TestEvtHandlerBase
, Event
);
91 struct TestEvtHandler
: TestEvtHandlerBase
<wxCommandEvent
>
93 TestEvtHandler(char tag
)
94 : TestEvtHandlerBase
<wxCommandEvent
>(TEST_EVT
, tag
)
99 struct TestMenuEvtHandler
: TestEvtHandlerBase
<wxCommandEvent
>
101 TestMenuEvtHandler(char tag
)
102 : TestEvtHandlerBase
<wxCommandEvent
>(wxEVT_MENU
, tag
)
107 struct TestPaintEvtHandler
: TestEvtHandlerBase
<wxPaintEvent
>
109 TestPaintEvtHandler(char tag
)
110 : TestEvtHandlerBase
<wxPaintEvent
>(wxEVT_PAINT
, tag
)
115 // Another custom event handler, suitable for use with Connect().
116 struct TestEvtSink
: wxEvtHandler
118 TestEvtSink(char tag
)
123 void Handle(wxEvent
& event
)
132 wxDECLARE_NO_COPY_CLASS(TestEvtSink
);
135 // a window handling the test event
136 class TestWindow
: public wxWindow
139 TestWindow(wxWindow
*parent
, char tag
)
140 : wxWindow(parent
, wxID_ANY
),
143 Connect(TEST_EVT
, wxCommandEventHandler(TestWindow::OnTest
));
147 void OnTest(wxCommandEvent
& event
)
156 DECLARE_NO_COPY_CLASS(TestWindow
)
159 // a scroll window handling paint event: we want to have a special test case
160 // for this because the event propagation is complicated even further than
161 // usual here by the presence of wxScrollHelperEvtHandler in the event handlers
162 // chain and the fact that OnDraw() virtual method must be called if EVT_PAINT
164 class TestScrollWindow
: public wxScrolledWindow
167 TestScrollWindow(wxWindow
*parent
)
168 : wxScrolledWindow(parent
, wxID_ANY
)
170 Connect(wxEVT_PAINT
, wxPaintEventHandler(TestScrollWindow::OnPaint
));
173 void GeneratePaintEvent()
176 // We need to map the window, otherwise we're not going to get any
177 // paint events for it.
180 // Ignore events generated during the initial mapping.
188 virtual void OnDraw(wxDC
& WXUNUSED(dc
))
190 g_str
+= 'D'; // draw
194 void OnPaint(wxPaintEvent
& event
)
196 g_str
+= 'P'; // paint
200 wxDECLARE_NO_COPY_CLASS(TestScrollWindow
);
203 int DoFilterEvent(wxEvent
& event
)
205 if ( event
.GetEventType() == TEST_EVT
||
206 event
.GetEventType() == wxEVT_MENU
)
212 bool DoProcessEvent(wxEvent
& event
)
214 if ( event
.GetEventType() == TEST_EVT
||
215 event
.GetEventType() == wxEVT_MENU
)
221 } // anonymous namespace
223 // --------------------------------------------------------------------------
225 // --------------------------------------------------------------------------
227 class EventPropagationTestCase
: public CppUnit::TestCase
230 EventPropagationTestCase() {}
232 virtual void setUp();
233 virtual void tearDown();
236 CPPUNIT_TEST_SUITE( EventPropagationTestCase
);
237 CPPUNIT_TEST( OneHandler
);
238 CPPUNIT_TEST( TwoHandlers
);
239 CPPUNIT_TEST( WindowWithoutHandler
);
240 CPPUNIT_TEST( WindowWithHandler
);
241 CPPUNIT_TEST( ForwardEvent
);
242 CPPUNIT_TEST( ScrollWindowWithoutHandler
);
243 CPPUNIT_TEST( ScrollWindowWithHandler
);
244 CPPUNIT_TEST( MenuEvent
);
245 CPPUNIT_TEST( DocView
);
246 WXUISIM_TEST( ContextMenuEvent
);
247 CPPUNIT_TEST_SUITE_END();
251 void WindowWithoutHandler();
252 void WindowWithHandler();
254 void ScrollWindowWithoutHandler();
255 void ScrollWindowWithHandler();
258 void ContextMenuEvent();
260 DECLARE_NO_COPY_CLASS(EventPropagationTestCase
)
263 // register in the unnamed registry so that these tests are run by default
264 CPPUNIT_TEST_SUITE_REGISTRATION( EventPropagationTestCase
);
266 // also include in its own registry so that these tests can be run alone
267 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EventPropagationTestCase
, "EventPropagationTestCase" );
269 void EventPropagationTestCase::setUp()
271 SetFilterEventFunc(DoFilterEvent
);
272 SetProcessEventFunc(DoProcessEvent
);
277 void EventPropagationTestCase::tearDown()
279 SetFilterEventFunc(NULL
);
280 SetProcessEventFunc(NULL
);
283 void EventPropagationTestCase::OneHandler()
285 wxCommandEvent
event(TEST_EVT
);
286 TestEvtHandler
h1('1');
287 h1
.ProcessEvent(event
);
288 CPPUNIT_ASSERT_EQUAL( "oa1A", g_str
);
291 void EventPropagationTestCase::TwoHandlers()
293 wxCommandEvent
event(TEST_EVT
);
294 TestEvtHandler
h1('1');
295 TestEvtHandler
h2('2');
296 h1
.SetNextHandler(&h2
);
297 h2
.SetPreviousHandler(&h1
);
298 h1
.ProcessEvent(event
);
299 CPPUNIT_ASSERT_EQUAL( "oa1o2A", g_str
);
302 void EventPropagationTestCase::WindowWithoutHandler()
304 wxCommandEvent
event(TEST_EVT
);
305 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
306 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
308 TestWindow
* const child
= new TestWindow(parent
, 'c');
310 child
->GetEventHandler()->ProcessEvent(event
);
311 CPPUNIT_ASSERT_EQUAL( "acpA", g_str
);
314 void EventPropagationTestCase::WindowWithHandler()
316 wxCommandEvent
event(TEST_EVT
);
317 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
318 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
320 TestWindow
* const child
= new TestWindow(parent
, 'c');
322 TestEvtHandler
h1('1');
323 child
->PushEventHandler(&h1
);
324 wxON_BLOCK_EXIT_OBJ1( *child
, wxWindow::PopEventHandler
, false );
325 TestEvtHandler
h2('2');
326 child
->PushEventHandler(&h2
);
327 wxON_BLOCK_EXIT_OBJ1( *child
, wxWindow::PopEventHandler
, false );
329 child
->HandleWindowEvent(event
);
330 CPPUNIT_ASSERT_EQUAL( "oa2o1cpA", g_str
);
333 void EventPropagationTestCase::ForwardEvent()
335 // The idea of this test is to check that the events explicitly forwarded
336 // to another event handler still get pre/post-processed as usual as this
337 // used to be broken by the fixes trying to avoid duplicate processing.
338 TestWindow
* const win
= new TestWindow(wxTheApp
->GetTopWindow(), 'w');
339 wxON_BLOCK_EXIT_OBJ0( *win
, wxWindow::Destroy
);
341 TestEvtHandler
h1('1');
342 win
->PushEventHandler(&h1
);
343 wxON_BLOCK_EXIT_OBJ1( *win
, wxWindow::PopEventHandler
, false );
345 class ForwardEvtHandler
: public wxEvtHandler
348 ForwardEvtHandler(wxEvtHandler
& h
) : m_h(&h
) { }
350 virtual bool ProcessEvent(wxEvent
& event
)
354 return m_h
->ProcessEvent(event
);
361 // First send the event directly to f.
362 wxCommandEvent
event1(TEST_EVT
);
363 f
.ProcessEvent(event1
);
364 CPPUNIT_ASSERT_EQUAL( "foa1wA", g_str
);
367 // And then also test sending it to f indirectly.
368 wxCommandEvent
event2(TEST_EVT
);
369 TestEvtHandler
h2('2');
370 h2
.SetNextHandler(&f
);
371 h2
.ProcessEvent(event2
);
372 CPPUNIT_ASSERT_EQUAL( "oa2fo1wAA", g_str
);
375 void EventPropagationTestCase::ScrollWindowWithoutHandler()
377 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
378 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
380 TestScrollWindow
* const win
= new TestScrollWindow(parent
);
382 #ifdef CAN_TEST_PAINT_EVENTS
383 win
->GeneratePaintEvent();
384 CPPUNIT_ASSERT_EQUAL( "PD", g_str
);
388 wxCommandEvent
eventCmd(TEST_EVT
);
389 win
->HandleWindowEvent(eventCmd
);
390 CPPUNIT_ASSERT_EQUAL( "apA", g_str
);
393 void EventPropagationTestCase::ScrollWindowWithHandler()
395 TestWindow
* const parent
= new TestWindow(wxTheApp
->GetTopWindow(), 'p');
396 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
398 TestScrollWindow
* const win
= new TestScrollWindow(parent
);
400 #ifdef CAN_TEST_PAINT_EVENTS
401 TestPaintEvtHandler
h('h');
402 win
->PushEventHandler(&h
);
403 wxON_BLOCK_EXIT_OBJ1( *win
, wxWindow::PopEventHandler
, false );
405 win
->GeneratePaintEvent();
406 CPPUNIT_ASSERT_EQUAL( "ohPD", g_str
);
410 wxCommandEvent
eventCmd(TEST_EVT
);
411 win
->HandleWindowEvent(eventCmd
);
412 CPPUNIT_ASSERT_EQUAL( "apA", g_str
);
415 // Create a menu bar with a single menu containing wxID_APPLY menu item and
416 // attach it to the specified frame.
417 wxMenu
* CreateTestMenu(wxFrame
* frame
)
419 wxMenu
* const menu
= new wxMenu
;
420 menu
->Append(wxID_APPLY
);
421 wxMenuBar
* const mb
= new wxMenuBar
;
422 mb
->Append(menu
, "&Menu");
423 frame
->SetMenuBar(mb
);
428 // Helper for checking that the menu event processing resulted in the expected
429 // output from the handlers.
431 // Notice that this is supposed to be used with ASSERT_MENU_EVENT_RESULT()
432 // macro to make the file name and line number of the caller appear in the
435 CheckMenuEvent(wxMenu
* menu
, const char* result
, CppUnit::SourceLine sourceLine
)
439 // Trigger the menu event: this is more reliable than using
440 // wxUIActionSimulator and currently works in all ports as they all call
441 // wxMenuBase::SendEvent() from their respective menu event handlers.
442 menu
->SendEvent(wxID_APPLY
);
444 CPPUNIT_NS::assertEquals( result
, g_str
, sourceLine
, "" );
447 #define ASSERT_MENU_EVENT_RESULT(menu, result) \
448 CheckMenuEvent((menu), (result), CPPUNIT_SOURCELINE())
450 void EventPropagationTestCase::MenuEvent()
452 wxFrame
* const frame
= static_cast<wxFrame
*>(wxTheApp
->GetTopWindow());
454 // Create a minimal menu bar.
455 wxMenu
* const menu
= CreateTestMenu(frame
);
456 wxMenuBar
* const mb
= menu
->GetMenuBar();
457 wxScopedPtr
<wxMenuBar
> ensureMenuBarDestruction(mb
);
458 wxON_BLOCK_EXIT_OBJ1( *frame
, wxFrame::SetMenuBar
, (wxMenuBar
*)NULL
);
460 // Check that wxApp gets the event exactly once.
461 ASSERT_MENU_EVENT_RESULT( menu
, "aA" );
464 // Check that the menu event handler is called.
465 TestMenuEvtHandler
hm('m'); // 'm' for "menu"
466 menu
->SetNextHandler(&hm
);
467 wxON_BLOCK_EXIT_OBJ1( *menu
,
468 wxEvtHandler::SetNextHandler
, (wxEvtHandler
*)NULL
);
469 ASSERT_MENU_EVENT_RESULT( menu
, "aomA" );
472 // Test that the event handler associated with the menu bar gets the event.
473 TestMenuEvtHandler
hb('b'); // 'b' for "menu Bar"
474 mb
->PushEventHandler(&hb
);
475 wxON_BLOCK_EXIT_OBJ1( *mb
, wxWindow::PopEventHandler
, false );
477 ASSERT_MENU_EVENT_RESULT( menu
, "aomobA" );
480 // Also test that the window to which the menu belongs gets the event.
481 TestMenuEvtHandler
hw('w'); // 'w' for "Window"
482 frame
->PushEventHandler(&hw
);
483 wxON_BLOCK_EXIT_OBJ1( *frame
, wxWindow::PopEventHandler
, false );
485 ASSERT_MENU_EVENT_RESULT( menu
, "aomobowA" );
488 // Minimal viable implementations of wxDocument and wxView.
489 class EventTestDocument
: public wxDocument
492 EventTestDocument() { }
494 wxDECLARE_DYNAMIC_CLASS(EventTestDocument
);
497 class EventTestView
: public wxView
502 virtual void OnDraw(wxDC
*) { }
504 wxDECLARE_DYNAMIC_CLASS(EventTestView
);
507 wxIMPLEMENT_DYNAMIC_CLASS(EventTestDocument
, wxDocument
);
508 wxIMPLEMENT_DYNAMIC_CLASS(EventTestView
, wxView
);
510 void EventPropagationTestCase::DocView()
512 // Set up the parent frame and its menu bar.
513 wxDocManager docManager
;
515 wxScopedPtr
<wxDocMDIParentFrame
>
516 parent(new wxDocMDIParentFrame(&docManager
, NULL
, wxID_ANY
, "Parent"));
518 wxMenu
* const menu
= CreateTestMenu(parent
.get());
521 // Set up the event handlers.
522 TestEvtSink
sinkDM('m');
523 docManager
.Connect(wxEVT_MENU
,
524 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkDM
);
526 TestEvtSink
sinkParent('p');
527 parent
->Connect(wxEVT_MENU
,
528 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkParent
);
531 // Check that wxDocManager and wxFrame get the event in order.
532 ASSERT_MENU_EVENT_RESULT( menu
, "ampA" );
535 // Now check what happens if we have an active document.
536 wxDocTemplate
docTemplate(&docManager
, "Test", "", "", "",
537 "Test Document", "Test View",
538 wxCLASSINFO(EventTestDocument
),
539 wxCLASSINFO(EventTestView
));
540 wxDocument
* const doc
= docTemplate
.CreateDocument("");
541 wxView
* const view
= doc
->GetFirstView();
543 wxScopedPtr
<wxMDIChildFrame
>
544 child(new wxDocMDIChildFrame(doc
, view
, parent
.get(), wxID_ANY
, "Child"));
546 wxMenu
* const menuChild
= CreateTestMenu(child
.get());
548 // Ensure that the child that we've just created is the active one.
552 // There are a lot of hacks related to child frame menu bar handling in
553 // wxGTK and, in particular, the code in src/gtk/mdi.cpp relies on getting
554 // idle events to really put everything in place. Moreover, as wxGTK uses
555 // GtkNotebook as its MDI pages container, the frame must be shown for all
556 // this to work as gtk_notebook_set_current_page() doesn't do anything if
557 // called for a hidden window (this incredible fact cost me quite some time
558 // to find empirically -- only to notice its confirmation in GTK+
559 // documentation immediately afterwards). So just do whatever it takes to
560 // make things work "as usual".
566 TestEvtSink
sinkDoc('d');
567 doc
->Connect(wxEVT_MENU
,
568 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkDoc
);
570 TestEvtSink
sinkView('v');
571 view
->Connect(wxEVT_MENU
,
572 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkView
);
574 TestEvtSink
sinkChild('c');
575 child
->Connect(wxEVT_MENU
,
576 wxEventHandler(TestEvtSink::Handle
), NULL
, &sinkChild
);
578 // Check that wxDocument, wxView, wxDocManager, child frame and the parent
579 // get the event in order.
580 ASSERT_MENU_EVENT_RESULT( menuChild
, "advmcpA" );
584 // Also check that toolbar events get forwarded to the active child.
585 wxToolBar
* const tb
= parent
->CreateToolBar(wxTB_NOICONS
);
586 tb
->AddTool(wxID_APPLY
, "Apply", wxNullBitmap
);
589 // As in CheckMenuEvent(), use toolbar method actually sending the event
590 // instead of bothering with wxUIActionSimulator which would have been
593 tb
->OnLeftClick(wxID_APPLY
, true /* doesn't matter */);
595 CPPUNIT_ASSERT_EQUAL( "advmcpA", g_str
);
596 #endif // wxUSE_TOOLBAR
599 #if wxUSE_UIACTIONSIMULATOR
601 class ContextMenuTestWindow
: public wxWindow
604 ContextMenuTestWindow(wxWindow
*parent
, char tag
)
605 : wxWindow(parent
, wxID_ANY
),
608 Connect(wxEVT_CONTEXT_MENU
,
609 wxContextMenuEventHandler(ContextMenuTestWindow::OnMenu
));
613 void OnMenu(wxContextMenuEvent
& event
)
622 wxDECLARE_NO_COPY_CLASS(ContextMenuTestWindow
);
625 void EventPropagationTestCase::ContextMenuEvent()
627 ContextMenuTestWindow
* const
628 parent
= new ContextMenuTestWindow(wxTheApp
->GetTopWindow(), 'p');
629 wxON_BLOCK_EXIT_OBJ0( *parent
, wxWindow::Destroy
);
631 ContextMenuTestWindow
* const
632 child
= new ContextMenuTestWindow(parent
, 'c');
633 parent
->SetSize(100, 100);
634 child
->SetSize(0, 0, 50, 50);
637 wxUIActionSimulator sim
;
638 const wxPoint origin
= parent
->ClientToScreen(wxPoint(0, 0));
640 // Right clicking in the child should generate an event for it and the
643 sim
.MouseMove(origin
+ wxPoint(10, 10));
644 sim
.MouseClick(wxMOUSE_BTN_RIGHT
);
646 // At least with MSW, for WM_CONTEXTMENU to be synthesized by the system
647 // from the right mouse click event, we must dispatch the mouse messages.
650 CPPUNIT_ASSERT_EQUAL( "cp", g_str
);
652 // Right clicking outside the child should generate the event just in the
655 sim
.MouseMove(origin
+ wxPoint(60, 60));
656 sim
.MouseClick(wxMOUSE_BTN_RIGHT
);
658 CPPUNIT_ASSERT_EQUAL( "p", g_str
);
661 #endif // wxUSE_UIACTIONSIMULATOR