]> git.saurik.com Git - wxWidgets.git/blobdiff - tests/events/propagation.cpp
Allow retrieving the descent and external leading of empty strings.
[wxWidgets.git] / tests / events / propagation.cpp
index 311fb171ef7712502374cb56396516234af8feb2..21ece32df29f21d5a6c618ca1ecc91fcb60ac58e 100644 (file)
 #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"
+#include "wx/toolbar.h"
+#include "wx/uiaction.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 +51,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 Event>
+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<wxEventFunction>(&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<wxCommandEvent>
+{
+    TestEvtHandler(char tag)
+        : TestEvtHandlerBase<wxCommandEvent>(TEST_EVT, tag)
+    {
+    }
+};
+
+struct TestMenuEvtHandler : TestEvtHandlerBase<wxCommandEvent>
+{
+    TestMenuEvtHandler(char tag)
+        : TestEvtHandlerBase<wxCommandEvent>(wxEVT_MENU, tag)
+    {
+    }
+};
+
+struct TestPaintEvtHandler : TestEvtHandlerBase<wxPaintEvent>
+{
+    TestPaintEvtHandler(char tag)
+        : TestEvtHandlerBase<wxPaintEvent>(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 +130,7 @@ private:
 
     const char m_tag;
 
-    DECLARE_NO_COPY_CLASS(TestEvtHandler)
+    wxDECLARE_NO_COPY_CLASS(TestEvtSink);
 };
 
 // a window handling the test event
@@ -90,9 +157,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 +212,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 +239,24 @@ 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 );
+        WXUISIM_TEST( ContextMenuEvent );
     CPPUNIT_TEST_SUITE_END();
 
     void OneHandler();
     void TwoHandlers();
     void WindowWithoutHandler();
     void WindowWithHandler();
+    void ForwardEvent();
+    void ScrollWindowWithoutHandler();
+    void ScrollWindowWithHandler();
+    void MenuEvent();
+    void DocView();
+    void ContextMenuEvent();
 
     DECLARE_NO_COPY_CLASS(EventPropagationTestCase)
 };
@@ -139,7 +264,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 +308,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 +322,341 @@ 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<wxFrame*>(wxTheApp->GetTopWindow());
+
+    // Create a minimal menu bar.
+    wxMenu* const menu = CreateTestMenu(frame);
+    wxMenuBar* const mb = menu->GetMenuBar();
+    wxScopedPtr<wxMenuBar> 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<wxDocMDIParentFrame>
+        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<wxMDIChildFrame>
+        child(new wxDocMDIChildFrame(doc, view, parent.get(), wxID_ANY, "Child"));
+
+    wxMenu* const menuChild = CreateTestMenu(child.get());
+
+    // Ensure that the child that we've just created is the active one.
+    child->Activate();
+
+#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" );
+
+
+#if wxUSE_TOOLBAR
+    // Also check that toolbar events get forwarded to the active child.
+    wxToolBar* const tb = parent->CreateToolBar(wxTB_NOICONS);
+    tb->AddTool(wxID_APPLY, "Apply", wxNullBitmap);
+    tb->Realize();
+
+    // As in CheckMenuEvent(), use toolbar method actually sending the event
+    // instead of bothering with wxUIActionSimulator which would have been
+    // trickier.
+    g_str.clear();
+    tb->OnLeftClick(wxID_APPLY, true /* doesn't matter */);
+
+    CPPUNIT_ASSERT_EQUAL( "advmcpA", g_str );
+#endif // wxUSE_TOOLBAR
+}
+
+#if wxUSE_UIACTIONSIMULATOR
+
+class ContextMenuTestWindow : public wxWindow
+{
+public:
+    ContextMenuTestWindow(wxWindow *parent, char tag)
+        : wxWindow(parent, wxID_ANY),
+          m_tag(tag)
+    {
+        Connect(wxEVT_CONTEXT_MENU,
+                wxContextMenuEventHandler(ContextMenuTestWindow::OnMenu));
+    }
+
+private:
+    void OnMenu(wxContextMenuEvent& event)
+    {
+        g_str += m_tag;
+
+        event.Skip();
+    }
+
+    const char m_tag;
+
+    wxDECLARE_NO_COPY_CLASS(ContextMenuTestWindow);
+};
+
+void EventPropagationTestCase::ContextMenuEvent()
+{
+    ContextMenuTestWindow * const
+        parent = new ContextMenuTestWindow(wxTheApp->GetTopWindow(), 'p');
+    wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
+
+    ContextMenuTestWindow * const
+        child = new ContextMenuTestWindow(parent, 'c');
+    parent->SetSize(100, 100);
+    child->SetSize(0, 0, 50, 50);
+    child->SetFocus();
+
+    wxUIActionSimulator sim;
+    const wxPoint origin = parent->ClientToScreen(wxPoint(0, 0));
+
+    // Right clicking in the child should generate an event for it and the
+    // parent.
+    g_str.clear();
+    sim.MouseMove(origin + wxPoint(10, 10));
+    sim.MouseClick(wxMOUSE_BTN_RIGHT);
+
+    // At least with MSW, for WM_CONTEXTMENU to be synthesized by the system
+    // from the right mouse click event, we must dispatch the mouse messages.
+    wxYield();
+
+    CPPUNIT_ASSERT_EQUAL( "cp", g_str );
+
+    // Right clicking outside the child should generate the event just in the
+    // parent.
+    g_str.clear();
+    sim.MouseMove(origin + wxPoint(60, 60));
+    sim.MouseClick(wxMOUSE_BTN_RIGHT);
+    wxYield();
+    CPPUNIT_ASSERT_EQUAL( "p", g_str );
+}
+
+#endif // wxUSE_UIACTIONSIMULATOR