wxWidgets/tests/events/propagation.cpp
2018-09-22 14:44:07 +02:00

713 lines
20 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: tests/events/propagation.cpp
// Purpose: Test events propagation
// Author: Vadim Zeitlin
// Created: 2009-01-16
// Copyright: (c) 2009 Vadim Zeitlin <vadim@wxwidgets.org>
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "testprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#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/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.
//
// FIXME: Under GTK+ 3 the test is broken because a simple wxYield() is not
// enough to map the frame. It should be also fixed there by waiting for
// it to come up, with some timeout, but for now it always fails, so
// it's useless to run it.
#if !defined(__WXOSX__) && !defined(__WXGTK3__)
#define CAN_TEST_PAINT_EVENTS
#endif
namespace
{
// this string will record the execution of all handlers
wxString g_str;
// a custom event
wxDEFINE_EVENT(TEST_EVT, wxCommandEvent);
// a custom event handler tracing the propagation of the events of the
// specified types
template <class Event>
class TestEvtHandlerBase : public wxEvtHandler
{
public:
TestEvtHandlerBase(wxEventType evtType, char tag)
: m_evtType(evtType),
m_tag(tag)
{
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) wxOVERRIDE
{
if ( event.GetEventType() == m_evtType )
g_str += 'o'; // "o" == "overridden"
return wxEvtHandler::ProcessEvent(event);
}
private:
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;
event.Skip();
}
const char m_tag;
wxDECLARE_NO_COPY_CLASS(TestEvtSink);
};
// a window handling the test event
class TestWindow : public wxWindow
{
public:
TestWindow(wxWindow *parent, char tag)
: wxWindow(parent, wxID_ANY),
m_tag(tag)
{
Connect(TEST_EVT, wxCommandEventHandler(TestWindow::OnTest));
}
private:
void OnTest(wxCommandEvent& event)
{
g_str += m_tag;
event.Skip();
}
const char m_tag;
wxDECLARE_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)) wxOVERRIDE
{
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 ||
event.GetEventType() == wxEVT_MENU )
g_str += 'a';
return -1;
}
bool DoProcessEvent(wxEvent& event)
{
if ( event.GetEventType() == TEST_EVT ||
event.GetEventType() == wxEVT_MENU )
g_str += 'A';
return false;
}
} // anonymous namespace
// --------------------------------------------------------------------------
// test class
// --------------------------------------------------------------------------
class EventPropagationTestCase : public CppUnit::TestCase
{
public:
EventPropagationTestCase() {}
virtual void setUp() wxOVERRIDE;
virtual void tearDown() wxOVERRIDE;
private:
CPPUNIT_TEST_SUITE( EventPropagationTestCase );
CPPUNIT_TEST( OneHandler );
CPPUNIT_TEST( TwoHandlers );
CPPUNIT_TEST( WindowWithoutHandler );
CPPUNIT_TEST( WindowWithHandler );
CPPUNIT_TEST( ForwardEvent );
CPPUNIT_TEST( ScrollWindowWithoutHandler );
CPPUNIT_TEST( ScrollWindowWithHandler );
// for unknown reason, this test will cause the tests segmentation failed
// under x11, disable it for now.
#if !defined (__WXX11__)
CPPUNIT_TEST( MenuEvent );
#endif
CPPUNIT_TEST( DocView );
WXUISIM_TEST( ContextMenuEvent );
WXUISIM_TEST( PropagationLevel );
CPPUNIT_TEST_SUITE_END();
void OneHandler();
void TwoHandlers();
void WindowWithoutHandler();
void WindowWithHandler();
void ForwardEvent();
void ScrollWindowWithoutHandler();
void ScrollWindowWithHandler();
void MenuEvent();
void DocView();
#if wxUSE_UIACTIONSIMULATOR
void ContextMenuEvent();
void PropagationLevel();
#endif
wxDECLARE_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 its own registry so that these tests can be run alone
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EventPropagationTestCase, "EventPropagationTestCase" );
void EventPropagationTestCase::setUp()
{
SetFilterEventFunc(DoFilterEvent);
SetProcessEventFunc(DoProcessEvent);
g_str.clear();
}
void EventPropagationTestCase::tearDown()
{
SetFilterEventFunc(NULL);
SetProcessEventFunc(NULL);
}
void EventPropagationTestCase::OneHandler()
{
wxCommandEvent event(TEST_EVT);
TestEvtHandler h1('1');
h1.ProcessEvent(event);
CPPUNIT_ASSERT_EQUAL( "oa1A", g_str );
}
void EventPropagationTestCase::TwoHandlers()
{
wxCommandEvent event(TEST_EVT);
TestEvtHandler h1('1');
TestEvtHandler h2('2');
h1.SetNextHandler(&h2);
h2.SetPreviousHandler(&h1);
h1.ProcessEvent(event);
CPPUNIT_ASSERT_EQUAL( "oa1o2A", g_str );
}
void EventPropagationTestCase::WindowWithoutHandler()
{
wxCommandEvent event(TEST_EVT);
TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
TestWindow * const child = new TestWindow(parent, 'c');
child->GetEventHandler()->ProcessEvent(event);
CPPUNIT_ASSERT_EQUAL( "acpA", g_str );
}
void EventPropagationTestCase::WindowWithHandler()
{
wxCommandEvent event(TEST_EVT);
TestWindow * const parent = new TestWindow(wxTheApp->GetTopWindow(), 'p');
wxON_BLOCK_EXIT_OBJ0( *parent, wxWindow::Destroy );
TestWindow * const child = new TestWindow(parent, 'c');
TestEvtHandler h1('1');
child->PushEventHandler(&h1);
wxON_BLOCK_EXIT_OBJ1( *child, wxWindow::PopEventHandler, false );
TestEvtHandler h2('2');
child->PushEventHandler(&h2);
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) wxOVERRIDE
{
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.
//
// Note that we trigger the menu event by sending it directly as 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.
#define ASSERT_MENU_EVENT_RESULT_FOR(cmd, menu, result) \
g_str.clear(); \
menu->SendEvent(cmd); \
CHECK( g_str == result )
#define ASSERT_MENU_EVENT_RESULT(menu, result) \
ASSERT_MENU_EVENT_RESULT_FOR(wxID_APPLY, menu, result)
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" );
// Check that a handler can also be attached to a submenu.
wxMenu* const submenu = new wxMenu;
submenu->Append(wxID_ABOUT);
menu->Append(wxID_ANY, "Submenu", submenu);
TestMenuEvtHandler hs('s'); // 's' for "submenu"
submenu->SetNextHandler(&hs);
wxON_BLOCK_EXIT_OBJ1( *submenu,
wxEvtHandler::SetNextHandler, (wxEvtHandler*)NULL );
ASSERT_MENU_EVENT_RESULT_FOR( wxID_ABOUT, submenu, "aosomA" );
// 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*) wxOVERRIDE { }
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 );
// For some unfathomable reason the test below sporadically fails in wxGTK
// buildbot builds, so disable it there to avoid spurious failure reports.
#ifdef __WXGTK__
if ( IsAutomaticTest() )
return;
#endif // __WXGTK__
// 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 );
}
// Helper function: get the event propagation level.
int GetPropagationLevel(wxEvent& e)
{
const int level = e.StopPropagation();
e.ResumePropagation(level);
return level;
}
void EventPropagationTestCase::PropagationLevel()
{
wxSizeEvent se;
CPPUNIT_ASSERT_EQUAL( GetPropagationLevel(se), (int)wxEVENT_PROPAGATE_NONE );
wxCommandEvent ce;
CPPUNIT_ASSERT_EQUAL( GetPropagationLevel(ce), (int)wxEVENT_PROPAGATE_MAX );
wxCommandEvent ce2(ce);
CPPUNIT_ASSERT_EQUAL( GetPropagationLevel(ce2), (int)wxEVENT_PROPAGATE_MAX );
wxCommandEvent ce3;
ce3.ResumePropagation(17);
CPPUNIT_ASSERT_EQUAL( GetPropagationLevel(ce3), 17 );
wxCommandEvent ce4(ce3);
CPPUNIT_ASSERT_EQUAL( GetPropagationLevel(ce4), 17 );
}
#endif // wxUSE_UIACTIONSIMULATOR