wxWidgets/tests/events/propagation.cpp
Vadim Zeitlin 5dd5d68e67 Revert "Fix fields initialization in wxCommandEvent copy ctor."
This reverts commit 62763ad541 which seems to
have been completely unnecessary as the fields had been already initialized
and this commit actually broke initialization of the propagation level of the
copied wxCommandEvent objects.

Add a unit test proving that things do work.

Closes #16739.
2016-01-30 05:02:54 +01:00

702 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.
#ifndef __WXOSX__
#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)
{
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))
{
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();
virtual void tearDown();
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 );
CPPUNIT_TEST( PropagationLevel );
CPPUNIT_TEST_SUITE_END();
void OneHandler();
void TwoHandlers();
void WindowWithoutHandler();
void WindowWithHandler();
void ForwardEvent();
void ScrollWindowWithoutHandler();
void ScrollWindowWithHandler();
void MenuEvent();
void DocView();
void ContextMenuEvent();
void PropagationLevel();
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)
{
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 );
// 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