]> git.saurik.com Git - wxWidgets.git/blob - tests/events/propagation.cpp
5bd36a3bd2a349da530abebc81d43916e1cec30d
[wxWidgets.git] / tests / events / propagation.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: tests/events/propagation.cpp
3 // Purpose: Test events propagation
4 // Author: Vadim Zeitlin
5 // Created: 2009-01-16
6 // RCS-ID: $Id$
7 // Copyright: (c) 2009 Vadim Zeitlin <vadim@wxwidgets.org>
8 ///////////////////////////////////////////////////////////////////////////////
9
10 // ----------------------------------------------------------------------------
11 // headers
12 // ----------------------------------------------------------------------------
13
14 #include "testprec.h"
15
16 #ifdef __BORLANDC__
17 #pragma hdrstop
18 #endif
19
20 #ifndef WX_PRECOMP
21 #include "wx/app.h"
22 #include "wx/event.h"
23 #include "wx/scrolwin.h"
24 #include "wx/window.h"
25 #endif // WX_PRECOMP
26
27 #include "wx/docmdi.h"
28 #include "wx/frame.h"
29 #include "wx/menu.h"
30 #include "wx/scopedptr.h"
31 #include "wx/scopeguard.h"
32
33 namespace
34 {
35
36 // this string will record the execution of all handlers
37 wxString g_str;
38
39 // a custom event
40 wxDEFINE_EVENT(TEST_EVT, wxCommandEvent);
41
42 // a custom event handler tracing the propagation of the events of the
43 // specified types
44 template <class Event>
45 class TestEvtHandlerBase : public wxEvtHandler
46 {
47 public:
48 TestEvtHandlerBase(wxEventType evtType, char tag)
49 : m_evtType(evtType),
50 m_tag(tag)
51 {
52 Connect(evtType,
53 static_cast<wxEventFunction>(&TestEvtHandlerBase::OnTest));
54 }
55
56 // override ProcessEvent() to confirm that it is called for all event
57 // handlers in the chain
58 virtual bool ProcessEvent(wxEvent& event)
59 {
60 if ( event.GetEventType() == m_evtType )
61 g_str += 'o'; // "o" == "overridden"
62
63 return wxEvtHandler::ProcessEvent(event);
64 }
65
66 private:
67 void OnTest(wxEvent& event)
68 {
69 g_str += m_tag;
70
71 event.Skip();
72 }
73
74 const wxEventType m_evtType;
75 const char m_tag;
76
77 wxDECLARE_NO_COPY_TEMPLATE_CLASS(TestEvtHandlerBase, Event);
78 };
79
80 struct TestEvtHandler : TestEvtHandlerBase<wxCommandEvent>
81 {
82 TestEvtHandler(char tag)
83 : TestEvtHandlerBase<wxCommandEvent>(TEST_EVT, tag)
84 {
85 }
86 };
87
88 struct TestMenuEvtHandler : TestEvtHandlerBase<wxCommandEvent>
89 {
90 TestMenuEvtHandler(char tag)
91 : TestEvtHandlerBase<wxCommandEvent>(wxEVT_MENU, tag)
92 {
93 }
94 };
95
96 struct TestPaintEvtHandler : TestEvtHandlerBase<wxPaintEvent>
97 {
98 TestPaintEvtHandler(char tag)
99 : TestEvtHandlerBase<wxPaintEvent>(wxEVT_PAINT, tag)
100 {
101 }
102 };
103
104 // Another custom event handler, suitable for use with Connect().
105 struct TestEvtSink : wxEvtHandler
106 {
107 TestEvtSink(char tag)
108 : m_tag(tag)
109 {
110 }
111
112 void Handle(wxEvent& event)
113 {
114 g_str += m_tag;
115
116 event.Skip();
117 }
118
119 const char m_tag;
120
121 wxDECLARE_NO_COPY_CLASS(TestEvtSink);
122 };
123
124 // a window handling the test event
125 class TestWindow : public wxWindow
126 {
127 public:
128 TestWindow(wxWindow *parent, char tag)
129 : wxWindow(parent, wxID_ANY),
130 m_tag(tag)
131 {
132 Connect(TEST_EVT, wxCommandEventHandler(TestWindow::OnTest));
133 }
134
135 private:
136 void OnTest(wxCommandEvent& event)
137 {
138 g_str += m_tag;
139
140 event.Skip();
141 }
142
143 const char m_tag;
144
145 DECLARE_NO_COPY_CLASS(TestWindow)
146 };
147
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
152 // is not handled
153 class TestScrollWindow : public wxScrolledWindow
154 {
155 public:
156 TestScrollWindow(wxWindow *parent)
157 : wxScrolledWindow(parent, wxID_ANY)
158 {
159 Connect(wxEVT_PAINT, wxPaintEventHandler(TestScrollWindow::OnPaint));
160 }
161
162 virtual void OnDraw(wxDC& WXUNUSED(dc))
163 {
164 g_str += 'D'; // draw
165 }
166
167 private:
168 void OnPaint(wxPaintEvent& event)
169 {
170 g_str += 'P'; // paint
171 event.Skip();
172 }
173
174 wxDECLARE_NO_COPY_CLASS(TestScrollWindow);
175 };
176
177 int DoFilterEvent(wxEvent& event)
178 {
179 if ( event.GetEventType() == TEST_EVT ||
180 event.GetEventType() == wxEVT_MENU )
181 g_str += 'a';
182
183 return -1;
184 }
185
186 bool DoProcessEvent(wxEvent& event)
187 {
188 if ( event.GetEventType() == TEST_EVT ||
189 event.GetEventType() == wxEVT_MENU )
190 g_str += 'A';
191
192 return false;
193 }
194
195 } // anonymous namespace
196
197 // --------------------------------------------------------------------------
198 // test class
199 // --------------------------------------------------------------------------
200
201 class EventPropagationTestCase : public CppUnit::TestCase
202 {
203 public:
204 EventPropagationTestCase() {}
205
206 virtual void setUp();
207 virtual void tearDown();
208
209 private:
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();
221
222 void OneHandler();
223 void TwoHandlers();
224 void WindowWithoutHandler();
225 void WindowWithHandler();
226 void ForwardEvent();
227 void ScrollWindowWithoutHandler();
228 void ScrollWindowWithHandler();
229 void MenuEvent();
230 void DocView();
231
232 DECLARE_NO_COPY_CLASS(EventPropagationTestCase)
233 };
234
235 // register in the unnamed registry so that these tests are run by default
236 CPPUNIT_TEST_SUITE_REGISTRATION( EventPropagationTestCase );
237
238 // also include in its own registry so that these tests can be run alone
239 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EventPropagationTestCase, "EventPropagationTestCase" );
240
241 void EventPropagationTestCase::setUp()
242 {
243 SetFilterEventFunc(DoFilterEvent);
244 SetProcessEventFunc(DoProcessEvent);
245
246 g_str.clear();
247 }
248
249 void EventPropagationTestCase::tearDown()
250 {
251 SetFilterEventFunc(NULL);
252 SetProcessEventFunc(NULL);
253 }
254
255 void EventPropagationTestCase::OneHandler()
256 {
257 wxCommandEvent event(TEST_EVT);
258 TestEvtHandler h1('1');
259 h1.ProcessEvent(event);
260 CPPUNIT_ASSERT_EQUAL( "oa1A", g_str );
261 }
262
263 void EventPropagationTestCase::TwoHandlers()
264 {
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 );
272 }
273
274 void EventPropagationTestCase::WindowWithoutHandler()
275 {
276 wxCommandEvent event(TEST_EVT);
277 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
278 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
279
280 TestWindow * const child = new TestWindow(parent, 'c');
281
282 child->GetEventHandler()->ProcessEvent(event);
283 CPPUNIT_ASSERT_EQUAL( "acpA", g_str );
284 }
285
286 void EventPropagationTestCase::WindowWithHandler()
287 {
288 wxCommandEvent event(TEST_EVT);
289 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
290 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
291
292 TestWindow * const child = new TestWindow(parent, 'c');
293
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 );
300
301 child->HandleWindowEvent(event);
302 CPPUNIT_ASSERT_EQUAL( "oa2o1cpA", g_str );
303 }
304
305 void EventPropagationTestCase::ForwardEvent()
306 {
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 );
312
313 TestEvtHandler h1('1');
314 win->PushEventHandler(&h1);
315 wxON_BLOCK_EXIT_OBJ1( *win, wxWindow::PopEventHandler, false );
316
317 class ForwardEvtHandler : public wxEvtHandler
318 {
319 public:
320 ForwardEvtHandler(wxEvtHandler& h) : m_h(&h) { }
321
322 virtual bool ProcessEvent(wxEvent& event)
323 {
324 g_str += 'f';
325
326 return m_h->ProcessEvent(event);
327 }
328
329 private:
330 wxEvtHandler *m_h;
331 } f(h1);
332
333 // First send the event directly to f.
334 wxCommandEvent event1(TEST_EVT);
335 f.ProcessEvent(event1);
336 CPPUNIT_ASSERT_EQUAL( "foa1wA", g_str );
337 g_str.clear();
338
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 );
345 }
346
347 void EventPropagationTestCase::ScrollWindowWithoutHandler()
348 {
349 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
350 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
351
352 TestScrollWindow * const win = new TestScrollWindow(parent);
353
354 #if !defined(__WXOSX__) && !defined(__WXGTK3__)
355 wxPaintEvent event(win->GetId());
356 win->ProcessWindowEvent(event);
357 CPPUNIT_ASSERT_EQUAL( "PD", g_str );
358 #endif
359 g_str.clear();
360 wxCommandEvent eventCmd(TEST_EVT);
361 win->HandleWindowEvent(eventCmd);
362 CPPUNIT_ASSERT_EQUAL( "apA", g_str );
363 }
364
365 void EventPropagationTestCase::ScrollWindowWithHandler()
366 {
367 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
368 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
369
370 TestScrollWindow * const win = new TestScrollWindow(parent);
371
372 #if !defined(__WXOSX__) && !defined(__WXGTK3__)
373 TestPaintEvtHandler h('h');
374 win->PushEventHandler(&h);
375 wxON_BLOCK_EXIT_OBJ1( *win, wxWindow::PopEventHandler, false );
376
377 wxPaintEvent event(win->GetId());
378 win->ProcessWindowEvent(event);
379 CPPUNIT_ASSERT_EQUAL( "ohPD", g_str );
380 #endif
381
382 g_str.clear();
383 wxCommandEvent eventCmd(TEST_EVT);
384 win->HandleWindowEvent(eventCmd);
385 CPPUNIT_ASSERT_EQUAL( "apA", g_str );
386 }
387
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)
391 {
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);
397
398 return menu;
399 }
400
401 // Helper for checking that the menu event processing resulted in the expected
402 // output from the handlers.
403 //
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
406 // failure messages.
407 void
408 CheckMenuEvent(wxMenu* menu, const char* result, CppUnit::SourceLine sourceLine)
409 {
410 g_str.clear();
411
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);
416
417 CPPUNIT_NS::assertEquals( result, g_str, sourceLine, "" );
418 }
419
420 #define ASSERT_MENU_EVENT_RESULT(menu, result) \
421 CheckMenuEvent((menu), (result), CPPUNIT_SOURCELINE())
422
423 void EventPropagationTestCase::MenuEvent()
424 {
425 wxFrame* const frame = static_cast<wxFrame*>(wxTheApp->GetTopWindow());
426
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 );
432
433 // Check that wxApp gets the event exactly once.
434 ASSERT_MENU_EVENT_RESULT( menu, "aA" );
435
436
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" );
443
444
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 );
449
450 ASSERT_MENU_EVENT_RESULT( menu, "aomobA" );
451
452
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 );
457
458 ASSERT_MENU_EVENT_RESULT( menu, "aomobowA" );
459 }
460
461 // Minimal viable implementations of wxDocument and wxView.
462 class EventTestDocument : public wxDocument
463 {
464 public:
465 EventTestDocument() { }
466
467 wxDECLARE_DYNAMIC_CLASS(EventTestDocument);
468 };
469
470 class EventTestView : public wxView
471 {
472 public:
473 EventTestView() { }
474
475 virtual void OnDraw(wxDC*) { }
476
477 wxDECLARE_DYNAMIC_CLASS(EventTestView);
478 };
479
480 wxIMPLEMENT_DYNAMIC_CLASS(EventTestDocument, wxDocument);
481 wxIMPLEMENT_DYNAMIC_CLASS(EventTestView, wxView);
482
483 void EventPropagationTestCase::DocView()
484 {
485 // Set up the parent frame and its menu bar.
486 wxDocManager docManager;
487
488 wxScopedPtr<wxDocMDIParentFrame>
489 parent(new wxDocMDIParentFrame(&docManager, NULL, wxID_ANY, "Parent"));
490
491 wxMenu* const menu = CreateTestMenu(parent.get());
492
493
494 // Set up the event handlers.
495 TestEvtSink sinkDM('m');
496 docManager.Connect(wxEVT_MENU,
497 wxEventHandler(TestEvtSink::Handle), NULL, &sinkDM);
498
499 TestEvtSink sinkParent('p');
500 parent->Connect(wxEVT_MENU,
501 wxEventHandler(TestEvtSink::Handle), NULL, &sinkParent);
502
503
504 // Check that wxDocManager and wxFrame get the event in order.
505 ASSERT_MENU_EVENT_RESULT( menu, "ampA" );
506
507
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();
515
516 wxScopedPtr<wxFrame>
517 child(new wxDocMDIChildFrame(doc, view, parent.get(), wxID_ANY, "Child"));
518
519 wxMenu* const menuChild = CreateTestMenu(child.get());
520
521 #ifdef __WXGTK__
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".
531 child->Show();
532 parent->Show();
533 wxYield();
534 #endif // __WXGTK__
535
536 TestEvtSink sinkDoc('d');
537 doc->Connect(wxEVT_MENU,
538 wxEventHandler(TestEvtSink::Handle), NULL, &sinkDoc);
539
540 TestEvtSink sinkView('v');
541 view->Connect(wxEVT_MENU,
542 wxEventHandler(TestEvtSink::Handle), NULL, &sinkView);
543
544 TestEvtSink sinkChild('c');
545 child->Connect(wxEVT_MENU,
546 wxEventHandler(TestEvtSink::Handle), NULL, &sinkChild);
547
548 // Check that wxDocument, wxView, wxDocManager, child frame and the parent
549 // get the event in order.
550 ASSERT_MENU_EVENT_RESULT( menuChild, "advmcpA" );
551 }