X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/a79fe526de101dbb890535633d9c7b0c950e71a9..cd15bcaf50ead53ccf9d50965312f0dc754affb4:/tests/events/propagation.cpp diff --git a/tests/events/propagation.cpp b/tests/events/propagation.cpp index 311fb171ef..5cd90ae072 100644 --- a/tests/events/propagation.cpp +++ b/tests/events/propagation.cpp @@ -18,12 +18,28 @@ #endif #ifndef WX_PRECOMP + #include "wx/app.h" + #include "wx/event.h" + #include "wx/scrolwin.h" + #include "wx/window.h" #endif // WX_PRECOMP -#include "wx/event.h" -#include "wx/window.h" +#include "wx/docmdi.h" +#include "wx/frame.h" +#include "wx/menu.h" +#include "wx/scopedptr.h" #include "wx/scopeguard.h" +// FIXME: Currently under OS X testing paint event doesn't work because neither +// calling Refresh()+Update() nor even sending wxPaintEvent directly to +// the window doesn't result in calls to its event handlers, so disable +// some tests there. But this should be fixed and the tests reenabled +// because wxPaintEvent propagation in wxScrolledWindow is a perfect +// example of fragile code that could be broken under OS X. +#ifndef __WXOSX__ + #define CAN_TEST_PAINT_EVENTS +#endif + namespace { @@ -33,28 +49,77 @@ wxString g_str; // a custom event wxDEFINE_EVENT(TEST_EVT, wxCommandEvent); -// a custom event handler -class TestEvtHandler : public wxEvtHandler +// a custom event handler tracing the propagation of the events of the +// specified types +template +class TestEvtHandlerBase : public wxEvtHandler { public: - TestEvtHandler(char tag) - : m_tag(tag) + TestEvtHandlerBase(wxEventType evtType, char tag) + : m_evtType(evtType), + m_tag(tag) { - Connect(TEST_EVT, wxCommandEventHandler(TestEvtHandler::OnTest)); + Connect(evtType, + static_cast(&TestEvtHandlerBase::OnTest)); } // override ProcessEvent() to confirm that it is called for all event // handlers in the chain virtual bool ProcessEvent(wxEvent& event) { - if ( event.GetEventType() == TEST_EVT ) + if ( event.GetEventType() == m_evtType ) g_str += 'o'; // "o" == "overridden" return wxEvtHandler::ProcessEvent(event); } private: - void OnTest(wxCommandEvent& event) + void OnTest(wxEvent& event) + { + g_str += m_tag; + + event.Skip(); + } + + const wxEventType m_evtType; + const char m_tag; + + wxDECLARE_NO_COPY_TEMPLATE_CLASS(TestEvtHandlerBase, Event); +}; + +struct TestEvtHandler : TestEvtHandlerBase +{ + TestEvtHandler(char tag) + : TestEvtHandlerBase(TEST_EVT, tag) + { + } +}; + +struct TestMenuEvtHandler : TestEvtHandlerBase +{ + TestMenuEvtHandler(char tag) + : TestEvtHandlerBase(wxEVT_MENU, tag) + { + } +}; + +struct TestPaintEvtHandler : TestEvtHandlerBase +{ + TestPaintEvtHandler(char tag) + : TestEvtHandlerBase(wxEVT_PAINT, tag) + { + } +}; + +// Another custom event handler, suitable for use with Connect(). +struct TestEvtSink : wxEvtHandler +{ + TestEvtSink(char tag) + : m_tag(tag) + { + } + + void Handle(wxEvent& event) { g_str += m_tag; @@ -63,7 +128,7 @@ private: const char m_tag; - DECLARE_NO_COPY_CLASS(TestEvtHandler) + wxDECLARE_NO_COPY_CLASS(TestEvtSink); }; // a window handling the test event @@ -90,9 +155,54 @@ private: DECLARE_NO_COPY_CLASS(TestWindow) }; +// a scroll window handling paint event: we want to have a special test case +// for this because the event propagation is complicated even further than +// usual here by the presence of wxScrollHelperEvtHandler in the event handlers +// chain and the fact that OnDraw() virtual method must be called if EVT_PAINT +// is not handled +class TestScrollWindow : public wxScrolledWindow +{ +public: + TestScrollWindow(wxWindow *parent) + : wxScrolledWindow(parent, wxID_ANY) + { + Connect(wxEVT_PAINT, wxPaintEventHandler(TestScrollWindow::OnPaint)); + } + + void GeneratePaintEvent() + { +#ifdef __WXGTK__ + // We need to map the window, otherwise we're not going to get any + // paint events for it. + wxYield(); + + // Ignore events generated during the initial mapping. + g_str.clear(); +#endif // __WXGTK__ + + Refresh(); + Update(); + } + + virtual void OnDraw(wxDC& WXUNUSED(dc)) + { + g_str += 'D'; // draw + } + +private: + void OnPaint(wxPaintEvent& event) + { + g_str += 'P'; // paint + event.Skip(); + } + + wxDECLARE_NO_COPY_CLASS(TestScrollWindow); +}; + int DoFilterEvent(wxEvent& event) { - if ( event.GetEventType() == TEST_EVT ) + if ( event.GetEventType() == TEST_EVT || + event.GetEventType() == wxEVT_MENU ) g_str += 'a'; return -1; @@ -100,7 +210,8 @@ int DoFilterEvent(wxEvent& event) bool DoProcessEvent(wxEvent& event) { - if ( event.GetEventType() == TEST_EVT ) + if ( event.GetEventType() == TEST_EVT || + event.GetEventType() == wxEVT_MENU ) g_str += 'A'; return false; @@ -126,12 +237,22 @@ private: CPPUNIT_TEST( TwoHandlers ); CPPUNIT_TEST( WindowWithoutHandler ); CPPUNIT_TEST( WindowWithHandler ); + CPPUNIT_TEST( ForwardEvent ); + CPPUNIT_TEST( ScrollWindowWithoutHandler ); + CPPUNIT_TEST( ScrollWindowWithHandler ); + CPPUNIT_TEST( MenuEvent ); + CPPUNIT_TEST( DocView ); CPPUNIT_TEST_SUITE_END(); void OneHandler(); void TwoHandlers(); void WindowWithoutHandler(); void WindowWithHandler(); + void ForwardEvent(); + void ScrollWindowWithoutHandler(); + void ScrollWindowWithHandler(); + void MenuEvent(); + void DocView(); DECLARE_NO_COPY_CLASS(EventPropagationTestCase) }; @@ -139,7 +260,7 @@ private: // register in the unnamed registry so that these tests are run by default CPPUNIT_TEST_SUITE_REGISTRATION( EventPropagationTestCase ); -// also include in it's own registry so that these tests can be run alone +// also include in its own registry so that these tests can be run alone CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EventPropagationTestCase, "EventPropagationTestCase" ); void EventPropagationTestCase::setUp() @@ -183,7 +304,7 @@ void EventPropagationTestCase::WindowWithoutHandler() TestWindow * const child = new TestWindow(parent, 'c'); - child->ProcessEvent(event); + child->GetEventHandler()->ProcessEvent(event); CPPUNIT_ASSERT_EQUAL( "acpA", g_str ); } @@ -197,12 +318,258 @@ void EventPropagationTestCase::WindowWithHandler() TestEvtHandler h1('1'); child->PushEventHandler(&h1); - wxON_BLOCK_EXIT_OBJ0( *child, wxWindow::PopEventHandler ); + wxON_BLOCK_EXIT_OBJ1( *child, wxWindow::PopEventHandler, false ); TestEvtHandler h2('2'); child->PushEventHandler(&h2); - wxON_BLOCK_EXIT_OBJ0( *child, wxWindow::PopEventHandler ); + wxON_BLOCK_EXIT_OBJ1( *child, wxWindow::PopEventHandler, false ); child->HandleWindowEvent(event); CPPUNIT_ASSERT_EQUAL( "oa2o1cpA", g_str ); } +void EventPropagationTestCase::ForwardEvent() +{ + // The idea of this test is to check that the events explicitly forwarded + // to another event handler still get pre/post-processed as usual as this + // used to be broken by the fixes trying to avoid duplicate processing. + TestWindow * const win = new TestWindow(wxTheApp->GetTopWindow(), 'w'); + wxON_BLOCK_EXIT_OBJ0( *win, wxWindow::Destroy ); + + TestEvtHandler h1('1'); + win->PushEventHandler(&h1); + wxON_BLOCK_EXIT_OBJ1( *win, wxWindow::PopEventHandler, false ); + + class ForwardEvtHandler : public wxEvtHandler + { + public: + ForwardEvtHandler(wxEvtHandler& h) : m_h(&h) { } + + virtual bool ProcessEvent(wxEvent& event) + { + g_str += 'f'; + + return m_h->ProcessEvent(event); + } + + private: + wxEvtHandler *m_h; + } f(h1); + + // First send the event directly to f. + wxCommandEvent event1(TEST_EVT); + f.ProcessEvent(event1); + CPPUNIT_ASSERT_EQUAL( "foa1wA", g_str ); + g_str.clear(); + + // And then also test sending it to f indirectly. + wxCommandEvent event2(TEST_EVT); + TestEvtHandler h2('2'); + h2.SetNextHandler(&f); + h2.ProcessEvent(event2); + CPPUNIT_ASSERT_EQUAL( "oa2fo1wAA", g_str ); +} + +void EventPropagationTestCase::ScrollWindowWithoutHandler() +{ + TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p'); + wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy ); + + TestScrollWindow * const win = new TestScrollWindow(parent); + +#ifdef CAN_TEST_PAINT_EVENTS + win->GeneratePaintEvent(); + CPPUNIT_ASSERT_EQUAL( "PD", g_str ); +#endif + + g_str.clear(); + wxCommandEvent eventCmd(TEST_EVT); + win->HandleWindowEvent(eventCmd); + CPPUNIT_ASSERT_EQUAL( "apA", g_str ); +} + +void EventPropagationTestCase::ScrollWindowWithHandler() +{ + TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p'); + wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy ); + + TestScrollWindow * const win = new TestScrollWindow(parent); + +#ifdef CAN_TEST_PAINT_EVENTS + TestPaintEvtHandler h('h'); + win->PushEventHandler(&h); + wxON_BLOCK_EXIT_OBJ1( *win, wxWindow::PopEventHandler, false ); + + win->GeneratePaintEvent(); + CPPUNIT_ASSERT_EQUAL( "ohPD", g_str ); +#endif + + g_str.clear(); + wxCommandEvent eventCmd(TEST_EVT); + win->HandleWindowEvent(eventCmd); + CPPUNIT_ASSERT_EQUAL( "apA", g_str ); +} + +// Create a menu bar with a single menu containing wxID_APPLY menu item and +// attach it to the specified frame. +wxMenu* CreateTestMenu(wxFrame* frame) +{ + wxMenu* const menu = new wxMenu; + menu->Append(wxID_APPLY); + wxMenuBar* const mb = new wxMenuBar; + mb->Append(menu, "&Menu"); + frame->SetMenuBar(mb); + + return menu; +} + +// Helper for checking that the menu event processing resulted in the expected +// output from the handlers. +// +// Notice that this is supposed to be used with ASSERT_MENU_EVENT_RESULT() +// macro to make the file name and line number of the caller appear in the +// failure messages. +void +CheckMenuEvent(wxMenu* menu, const char* result, CppUnit::SourceLine sourceLine) +{ + g_str.clear(); + + // Trigger the menu event: this is more reliable than using + // wxUIActionSimulator and currently works in all ports as they all call + // wxMenuBase::SendEvent() from their respective menu event handlers. + menu->SendEvent(wxID_APPLY); + + CPPUNIT_NS::assertEquals( result, g_str, sourceLine, "" ); +} + +#define ASSERT_MENU_EVENT_RESULT(menu, result) \ + CheckMenuEvent((menu), (result), CPPUNIT_SOURCELINE()) + +void EventPropagationTestCase::MenuEvent() +{ + wxFrame* const frame = static_cast(wxTheApp->GetTopWindow()); + + // Create a minimal menu bar. + wxMenu* const menu = CreateTestMenu(frame); + wxMenuBar* const mb = menu->GetMenuBar(); + wxScopedPtr ensureMenuBarDestruction(mb); + wxON_BLOCK_EXIT_OBJ1( *frame, wxFrame::SetMenuBar, (wxMenuBar*)NULL ); + + // Check that wxApp gets the event exactly once. + ASSERT_MENU_EVENT_RESULT( menu, "aA" ); + + + // Check that the menu event handler is called. + TestMenuEvtHandler hm('m'); // 'm' for "menu" + menu->SetNextHandler(&hm); + wxON_BLOCK_EXIT_OBJ1( *menu, + wxEvtHandler::SetNextHandler, (wxEvtHandler*)NULL ); + ASSERT_MENU_EVENT_RESULT( menu, "aomA" ); + + + // Test that the event handler associated with the menu bar gets the event. + TestMenuEvtHandler hb('b'); // 'b' for "menu Bar" + mb->PushEventHandler(&hb); + wxON_BLOCK_EXIT_OBJ1( *mb, wxWindow::PopEventHandler, false ); + + ASSERT_MENU_EVENT_RESULT( menu, "aomobA" ); + + + // Also test that the window to which the menu belongs gets the event. + TestMenuEvtHandler hw('w'); // 'w' for "Window" + frame->PushEventHandler(&hw); + wxON_BLOCK_EXIT_OBJ1( *frame, wxWindow::PopEventHandler, false ); + + ASSERT_MENU_EVENT_RESULT( menu, "aomobowA" ); +} + +// Minimal viable implementations of wxDocument and wxView. +class EventTestDocument : public wxDocument +{ +public: + EventTestDocument() { } + + wxDECLARE_DYNAMIC_CLASS(EventTestDocument); +}; + +class EventTestView : public wxView +{ +public: + EventTestView() { } + + virtual void OnDraw(wxDC*) { } + + wxDECLARE_DYNAMIC_CLASS(EventTestView); +}; + +wxIMPLEMENT_DYNAMIC_CLASS(EventTestDocument, wxDocument); +wxIMPLEMENT_DYNAMIC_CLASS(EventTestView, wxView); + +void EventPropagationTestCase::DocView() +{ + // Set up the parent frame and its menu bar. + wxDocManager docManager; + + wxScopedPtr + parent(new wxDocMDIParentFrame(&docManager, NULL, wxID_ANY, "Parent")); + + wxMenu* const menu = CreateTestMenu(parent.get()); + + + // Set up the event handlers. + TestEvtSink sinkDM('m'); + docManager.Connect(wxEVT_MENU, + wxEventHandler(TestEvtSink::Handle), NULL, &sinkDM); + + TestEvtSink sinkParent('p'); + parent->Connect(wxEVT_MENU, + wxEventHandler(TestEvtSink::Handle), NULL, &sinkParent); + + + // Check that wxDocManager and wxFrame get the event in order. + ASSERT_MENU_EVENT_RESULT( menu, "ampA" ); + + + // Now check what happens if we have an active document. + wxDocTemplate docTemplate(&docManager, "Test", "", "", "", + "Test Document", "Test View", + wxCLASSINFO(EventTestDocument), + wxCLASSINFO(EventTestView)); + wxDocument* const doc = docTemplate.CreateDocument(""); + wxView* const view = doc->GetFirstView(); + + wxScopedPtr + child(new wxDocMDIChildFrame(doc, view, parent.get(), wxID_ANY, "Child")); + + wxMenu* const menuChild = CreateTestMenu(child.get()); + +#ifdef __WXGTK__ + // There are a lot of hacks related to child frame menu bar handling in + // wxGTK and, in particular, the code in src/gtk/mdi.cpp relies on getting + // idle events to really put everything in place. Moreover, as wxGTK uses + // GtkNotebook as its MDI pages container, the frame must be shown for all + // this to work as gtk_notebook_set_current_page() doesn't do anything if + // called for a hidden window (this incredible fact cost me quite some time + // to find empirically -- only to notice its confirmation in GTK+ + // documentation immediately afterwards). So just do whatever it takes to + // make things work "as usual". + child->Show(); + parent->Show(); + wxYield(); +#endif // __WXGTK__ + + TestEvtSink sinkDoc('d'); + doc->Connect(wxEVT_MENU, + wxEventHandler(TestEvtSink::Handle), NULL, &sinkDoc); + + TestEvtSink sinkView('v'); + view->Connect(wxEVT_MENU, + wxEventHandler(TestEvtSink::Handle), NULL, &sinkView); + + TestEvtSink sinkChild('c'); + child->Connect(wxEVT_MENU, + wxEventHandler(TestEvtSink::Handle), NULL, &sinkChild); + + // Check that wxDocument, wxView, wxDocManager, child frame and the parent + // get the event in order. + ASSERT_MENU_EVENT_RESULT( menuChild, "advmcpA" ); +}