// Purpose: Test events propagation
// Author: Vadim Zeitlin
// Created: 2009-01-16
-// RCS-ID: $Id$
// Copyright: (c) 2009 Vadim Zeitlin <vadim@wxwidgets.org>
///////////////////////////////////////////////////////////////////////////////
#include "wx/window.h"
#endif // WX_PRECOMP
+#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
{
}
};
+struct TestMenuEvtHandler : TestEvtHandlerBase<wxCommandEvent>
+{
+ TestMenuEvtHandler(char tag)
+ : TestEvtHandlerBase<wxCommandEvent>(wxEVT_MENU, tag)
+ {
+ }
+};
+
struct TestPaintEvtHandler : TestEvtHandlerBase<wxPaintEvent>
{
TestPaintEvtHandler(char 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;
+
+ event.Skip();
+ }
+
+ const char m_tag;
+
+ wxDECLARE_NO_COPY_CLASS(TestEvtSink);
+};
+
// a window handling the test event
class TestWindow : public wxWindow
{
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
int DoFilterEvent(wxEvent& event)
{
- if ( event.GetEventType() == TEST_EVT )
+ if ( event.GetEventType() == TEST_EVT ||
+ event.GetEventType() == wxEVT_MENU )
g_str += 'a';
return -1;
bool DoProcessEvent(wxEvent& event)
{
- if ( event.GetEventType() == TEST_EVT )
+ if ( event.GetEventType() == TEST_EVT ||
+ event.GetEventType() == wxEVT_MENU )
g_str += 'A';
return false;
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)
};
// 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()
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');
TestScrollWindow * const win = new TestScrollWindow(parent);
- wxPaintEvent event(win->GetId());
- win->ProcessWindowEvent(event);
+#ifdef CAN_TEST_PAINT_EVENTS
+ win->GeneratePaintEvent();
CPPUNIT_ASSERT_EQUAL( "PD", g_str );
+#endif
g_str.clear();
wxCommandEvent eventCmd(TEST_EVT);
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 );
- wxPaintEvent event(win->GetId());
- win->ProcessWindowEvent(event);
+ win->GeneratePaintEvent();
CPPUNIT_ASSERT_EQUAL( "ohPD", g_str );
+#endif
g_str.clear();
wxCommandEvent eventCmd(TEST_EVT);
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