Whack-a-mole with wxOSX/PPC unit tests continued.
[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 // 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.
39 #ifndef __WXOSX__
40 #define CAN_TEST_PAINT_EVENTS
41 #endif
42
43 namespace
44 {
45
46 // this string will record the execution of all handlers
47 wxString g_str;
48
49 // a custom event
50 wxDEFINE_EVENT(TEST_EVT, wxCommandEvent);
51
52 // a custom event handler tracing the propagation of the events of the
53 // specified types
54 template <class Event>
55 class TestEvtHandlerBase : public wxEvtHandler
56 {
57 public:
58 TestEvtHandlerBase(wxEventType evtType, char tag)
59 : m_evtType(evtType),
60 m_tag(tag)
61 {
62 Connect(evtType,
63 static_cast<wxEventFunction>(&TestEvtHandlerBase::OnTest));
64 }
65
66 // override ProcessEvent() to confirm that it is called for all event
67 // handlers in the chain
68 virtual bool ProcessEvent(wxEvent& event)
69 {
70 if ( event.GetEventType() == m_evtType )
71 g_str += 'o'; // "o" == "overridden"
72
73 return wxEvtHandler::ProcessEvent(event);
74 }
75
76 private:
77 void OnTest(wxEvent& event)
78 {
79 g_str += m_tag;
80
81 event.Skip();
82 }
83
84 const wxEventType m_evtType;
85 const char m_tag;
86
87 wxDECLARE_NO_COPY_TEMPLATE_CLASS(TestEvtHandlerBase, Event);
88 };
89
90 struct TestEvtHandler : TestEvtHandlerBase<wxCommandEvent>
91 {
92 TestEvtHandler(char tag)
93 : TestEvtHandlerBase<wxCommandEvent>(TEST_EVT, tag)
94 {
95 }
96 };
97
98 struct TestMenuEvtHandler : TestEvtHandlerBase<wxCommandEvent>
99 {
100 TestMenuEvtHandler(char tag)
101 : TestEvtHandlerBase<wxCommandEvent>(wxEVT_MENU, tag)
102 {
103 }
104 };
105
106 struct TestPaintEvtHandler : TestEvtHandlerBase<wxPaintEvent>
107 {
108 TestPaintEvtHandler(char tag)
109 : TestEvtHandlerBase<wxPaintEvent>(wxEVT_PAINT, tag)
110 {
111 }
112 };
113
114 // Another custom event handler, suitable for use with Connect().
115 struct TestEvtSink : wxEvtHandler
116 {
117 TestEvtSink(char tag)
118 : m_tag(tag)
119 {
120 }
121
122 void Handle(wxEvent& event)
123 {
124 g_str += m_tag;
125
126 event.Skip();
127 }
128
129 const char m_tag;
130
131 wxDECLARE_NO_COPY_CLASS(TestEvtSink);
132 };
133
134 // a window handling the test event
135 class TestWindow : public wxWindow
136 {
137 public:
138 TestWindow(wxWindow *parent, char tag)
139 : wxWindow(parent, wxID_ANY),
140 m_tag(tag)
141 {
142 Connect(TEST_EVT, wxCommandEventHandler(TestWindow::OnTest));
143 }
144
145 private:
146 void OnTest(wxCommandEvent& event)
147 {
148 g_str += m_tag;
149
150 event.Skip();
151 }
152
153 const char m_tag;
154
155 DECLARE_NO_COPY_CLASS(TestWindow)
156 };
157
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
162 // is not handled
163 class TestScrollWindow : public wxScrolledWindow
164 {
165 public:
166 TestScrollWindow(wxWindow *parent)
167 : wxScrolledWindow(parent, wxID_ANY)
168 {
169 Connect(wxEVT_PAINT, wxPaintEventHandler(TestScrollWindow::OnPaint));
170 }
171
172 void GeneratePaintEvent()
173 {
174 #ifdef __WXGTK__
175 // We need to map the window, otherwise we're not going to get any
176 // paint events for it.
177 wxYield();
178
179 // Ignore events generated during the initial mapping.
180 g_str.clear();
181 #endif // __WXGTK__
182
183 Refresh();
184 Update();
185 }
186
187 virtual void OnDraw(wxDC& WXUNUSED(dc))
188 {
189 g_str += 'D'; // draw
190 }
191
192 private:
193 void OnPaint(wxPaintEvent& event)
194 {
195 g_str += 'P'; // paint
196 event.Skip();
197 }
198
199 wxDECLARE_NO_COPY_CLASS(TestScrollWindow);
200 };
201
202 int DoFilterEvent(wxEvent& event)
203 {
204 if ( event.GetEventType() == TEST_EVT ||
205 event.GetEventType() == wxEVT_MENU )
206 g_str += 'a';
207
208 return -1;
209 }
210
211 bool DoProcessEvent(wxEvent& event)
212 {
213 if ( event.GetEventType() == TEST_EVT ||
214 event.GetEventType() == wxEVT_MENU )
215 g_str += 'A';
216
217 return false;
218 }
219
220 } // anonymous namespace
221
222 // --------------------------------------------------------------------------
223 // test class
224 // --------------------------------------------------------------------------
225
226 class EventPropagationTestCase : public CppUnit::TestCase
227 {
228 public:
229 EventPropagationTestCase() {}
230
231 virtual void setUp();
232 virtual void tearDown();
233
234 private:
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();
246
247 void OneHandler();
248 void TwoHandlers();
249 void WindowWithoutHandler();
250 void WindowWithHandler();
251 void ForwardEvent();
252 void ScrollWindowWithoutHandler();
253 void ScrollWindowWithHandler();
254 void MenuEvent();
255 void DocView();
256
257 DECLARE_NO_COPY_CLASS(EventPropagationTestCase)
258 };
259
260 // register in the unnamed registry so that these tests are run by default
261 CPPUNIT_TEST_SUITE_REGISTRATION( EventPropagationTestCase );
262
263 // also include in its own registry so that these tests can be run alone
264 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EventPropagationTestCase, "EventPropagationTestCase" );
265
266 void EventPropagationTestCase::setUp()
267 {
268 SetFilterEventFunc(DoFilterEvent);
269 SetProcessEventFunc(DoProcessEvent);
270
271 g_str.clear();
272 }
273
274 void EventPropagationTestCase::tearDown()
275 {
276 SetFilterEventFunc(NULL);
277 SetProcessEventFunc(NULL);
278 }
279
280 void EventPropagationTestCase::OneHandler()
281 {
282 wxCommandEvent event(TEST_EVT);
283 TestEvtHandler h1('1');
284 h1.ProcessEvent(event);
285 CPPUNIT_ASSERT_EQUAL( "oa1A", g_str );
286 }
287
288 void EventPropagationTestCase::TwoHandlers()
289 {
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 );
297 }
298
299 void EventPropagationTestCase::WindowWithoutHandler()
300 {
301 wxCommandEvent event(TEST_EVT);
302 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
303 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
304
305 TestWindow * const child = new TestWindow(parent, 'c');
306
307 child->GetEventHandler()->ProcessEvent(event);
308 CPPUNIT_ASSERT_EQUAL( "acpA", g_str );
309 }
310
311 void EventPropagationTestCase::WindowWithHandler()
312 {
313 wxCommandEvent event(TEST_EVT);
314 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
315 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
316
317 TestWindow * const child = new TestWindow(parent, 'c');
318
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 );
325
326 child->HandleWindowEvent(event);
327 CPPUNIT_ASSERT_EQUAL( "oa2o1cpA", g_str );
328 }
329
330 void EventPropagationTestCase::ForwardEvent()
331 {
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 );
337
338 TestEvtHandler h1('1');
339 win->PushEventHandler(&h1);
340 wxON_BLOCK_EXIT_OBJ1( *win, wxWindow::PopEventHandler, false );
341
342 class ForwardEvtHandler : public wxEvtHandler
343 {
344 public:
345 ForwardEvtHandler(wxEvtHandler& h) : m_h(&h) { }
346
347 virtual bool ProcessEvent(wxEvent& event)
348 {
349 g_str += 'f';
350
351 return m_h->ProcessEvent(event);
352 }
353
354 private:
355 wxEvtHandler *m_h;
356 } f(h1);
357
358 // First send the event directly to f.
359 wxCommandEvent event1(TEST_EVT);
360 f.ProcessEvent(event1);
361 CPPUNIT_ASSERT_EQUAL( "foa1wA", g_str );
362 g_str.clear();
363
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 );
370 }
371
372 void EventPropagationTestCase::ScrollWindowWithoutHandler()
373 {
374 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
375 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
376
377 TestScrollWindow * const win = new TestScrollWindow(parent);
378
379 #ifdef CAN_TEST_PAINT_EVENTS
380 win->GeneratePaintEvent();
381 CPPUNIT_ASSERT_EQUAL( "PD", g_str );
382 #endif
383
384 g_str.clear();
385 wxCommandEvent eventCmd(TEST_EVT);
386 win->HandleWindowEvent(eventCmd);
387 CPPUNIT_ASSERT_EQUAL( "apA", g_str );
388 }
389
390 void EventPropagationTestCase::ScrollWindowWithHandler()
391 {
392 TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
393 wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
394
395 TestScrollWindow * const win = new TestScrollWindow(parent);
396
397 #ifdef CAN_TEST_PAINT_EVENTS
398 TestPaintEvtHandler h('h');
399 win->PushEventHandler(&h);
400 wxON_BLOCK_EXIT_OBJ1( *win, wxWindow::PopEventHandler, false );
401
402 win->GeneratePaintEvent();
403 CPPUNIT_ASSERT_EQUAL( "ohPD", g_str );
404 #endif
405
406 g_str.clear();
407 wxCommandEvent eventCmd(TEST_EVT);
408 win->HandleWindowEvent(eventCmd);
409 CPPUNIT_ASSERT_EQUAL( "apA", g_str );
410 }
411
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)
415 {
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);
421
422 return menu;
423 }
424
425 // Helper for checking that the menu event processing resulted in the expected
426 // output from the handlers.
427 //
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
430 // failure messages.
431 void
432 CheckMenuEvent(wxMenu* menu, const char* result, CppUnit::SourceLine sourceLine)
433 {
434 g_str.clear();
435
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);
440
441 CPPUNIT_NS::assertEquals( result, g_str, sourceLine, "" );
442 }
443
444 #define ASSERT_MENU_EVENT_RESULT(menu, result) \
445 CheckMenuEvent((menu), (result), CPPUNIT_SOURCELINE())
446
447 void EventPropagationTestCase::MenuEvent()
448 {
449 wxFrame* const frame = static_cast<wxFrame*>(wxTheApp->GetTopWindow());
450
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 );
456
457 // Check that wxApp gets the event exactly once.
458 ASSERT_MENU_EVENT_RESULT( menu, "aA" );
459
460
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" );
467
468
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 );
473
474 ASSERT_MENU_EVENT_RESULT( menu, "aomobA" );
475
476
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 );
481
482 ASSERT_MENU_EVENT_RESULT( menu, "aomobowA" );
483 }
484
485 // Minimal viable implementations of wxDocument and wxView.
486 class EventTestDocument : public wxDocument
487 {
488 public:
489 EventTestDocument() { }
490
491 wxDECLARE_DYNAMIC_CLASS(EventTestDocument);
492 };
493
494 class EventTestView : public wxView
495 {
496 public:
497 EventTestView() { }
498
499 virtual void OnDraw(wxDC*) { }
500
501 wxDECLARE_DYNAMIC_CLASS(EventTestView);
502 };
503
504 wxIMPLEMENT_DYNAMIC_CLASS(EventTestDocument, wxDocument);
505 wxIMPLEMENT_DYNAMIC_CLASS(EventTestView, wxView);
506
507 void EventPropagationTestCase::DocView()
508 {
509 // Set up the parent frame and its menu bar.
510 wxDocManager docManager;
511
512 wxScopedPtr<wxDocMDIParentFrame>
513 parent(new wxDocMDIParentFrame(&docManager, NULL, wxID_ANY, "Parent"));
514
515 wxMenu* const menu = CreateTestMenu(parent.get());
516
517
518 // Set up the event handlers.
519 TestEvtSink sinkDM('m');
520 docManager.Connect(wxEVT_MENU,
521 wxEventHandler(TestEvtSink::Handle), NULL, &sinkDM);
522
523 TestEvtSink sinkParent('p');
524 parent->Connect(wxEVT_MENU,
525 wxEventHandler(TestEvtSink::Handle), NULL, &sinkParent);
526
527
528 // Check that wxDocManager and wxFrame get the event in order.
529 ASSERT_MENU_EVENT_RESULT( menu, "ampA" );
530
531
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();
539
540 wxScopedPtr<wxFrame>
541 child(new wxDocMDIChildFrame(doc, view, parent.get(), wxID_ANY, "Child"));
542
543 wxMenu* const menuChild = CreateTestMenu(child.get());
544
545 #ifdef __WXGTK__
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".
555 child->Show();
556 parent->Show();
557 wxYield();
558 #endif // __WXGTK__
559
560 TestEvtSink sinkDoc('d');
561 doc->Connect(wxEVT_MENU,
562 wxEventHandler(TestEvtSink::Handle), NULL, &sinkDoc);
563
564 TestEvtSink sinkView('v');
565 view->Connect(wxEVT_MENU,
566 wxEventHandler(TestEvtSink::Handle), NULL, &sinkView);
567
568 TestEvtSink sinkChild('c');
569 child->Connect(wxEVT_MENU,
570 wxEventHandler(TestEvtSink::Handle), NULL, &sinkChild);
571
572 // Check that wxDocument, wxView, wxDocManager, child frame and the parent
573 // get the event in order.
574 ASSERT_MENU_EVENT_RESULT( menuChild, "advmcpA" );
575 }