diff --git a/docs/doxygen/overviews/docview.h b/docs/doxygen/overviews/docview.h index 49d2689a55..2c5af959d2 100644 --- a/docs/doxygen/overviews/docview.h +++ b/docs/doxygen/overviews/docview.h @@ -214,6 +214,30 @@ There may be multiple wxDocManager instances in an application. See the example application in @c samples/docview. +@section overview_docview_events Event Propagation in Document/View framework + +While wxDocument, wxDocManager and wxView are abstract objects, with which the +user can't interact directly, all of them derive from wxEvtHandler class and +can handle events arising in the windows showing the document with which the +user does interact. This is implemented by adding additional steps to the event +handling process described in @ref overview_events_processing, so the full list +of the handlers searched for an event occurring directly in wxDocChildFrame is: +
    +
  1. wxDocument opened in this frame.
  2. +
  3. wxView shown in this frame.
  4. +
  5. wxDocManager associated with the parent wxDocParentFrame.
  6. +
  7. wxDocChildFrame itself.
  8. +
  9. wxDocParentFrame, as per the usual event bubbling up to parent rules.
  10. +
  11. wxApp, again as the usual fallback for all events.
  12. +
+ +This is mostly useful to define handlers for some menu commands directly in +wxDocument or wxView and is also used by the framework itself to define the +handlers for several standard commands, such as wxID_NEW or wxID_SAVE, in +wxDocManager itself. Notice that due to the order of the event handler search +detailed above, the handling of these commands can @e not be overridden at +wxDocParentFrame level but must be done at the level of wxDocManager itself. + @section overview_docview_wxcommand wxCommand Overview diff --git a/include/wx/docview.h b/include/wx/docview.h index 1c789626c7..c8b0347215 100644 --- a/include/wx/docview.h +++ b/include/wx/docview.h @@ -643,10 +643,7 @@ protected: // we're not a wxEvtHandler but we provide this wxEvtHandler-like function // which is called from TryBefore() of the derived classes to give our view // a chance to process the message before the frame event handlers are used - bool TryProcessEvent(wxEvent& event) - { - return m_childView && m_childView->ProcessEventLocally(event); - } + bool TryProcessEvent(wxEvent& event); // called from EVT_CLOSE handler in the frame: check if we can close and do // cleanup if so; veto the event otherwise @@ -830,11 +827,22 @@ private: class WXDLLIMPEXP_CORE wxDocParentFrameAnyBase { public: - wxDocParentFrameAnyBase() { m_docManager = NULL; } + wxDocParentFrameAnyBase(wxWindow* frame) + : m_frame(frame) + { + m_docManager = NULL; + } wxDocManager *GetDocumentManager() const { return m_docManager; } protected: + // This is similar to wxDocChildFrameAnyBase method with the same name: + // while we're not an event handler ourselves and so can't override + // TryBefore(), we provide a helper that the derived template class can use + // from its TryBefore() implementation. + bool TryProcessEvent(wxEvent& event); + + wxWindow* const m_frame; wxDocManager *m_docManager; wxDECLARE_NO_COPY_CLASS(wxDocParentFrameAnyBase); @@ -847,7 +855,7 @@ class WXDLLIMPEXP_CORE wxDocParentFrameAny : public BaseFrame, public wxDocParentFrameAnyBase { public: - wxDocParentFrameAny() { } + wxDocParentFrameAny() : wxDocParentFrameAnyBase(this) { } wxDocParentFrameAny(wxDocManager *manager, wxFrame *frame, wxWindowID id, @@ -856,6 +864,7 @@ public: const wxSize& size = wxDefaultSize, long style = wxDEFAULT_FRAME_STYLE, const wxString& name = wxFrameNameStr) + : wxDocParentFrameAnyBase(this) { Create(manager, frame, id, title, pos, size, style, name); } @@ -886,10 +895,7 @@ protected: // hook the document manager into event handling chain here virtual bool TryBefore(wxEvent& event) { - if ( m_docManager && m_docManager->ProcessEventLocally(event) ) - return true; - - return BaseFrame::TryBefore(event); + return TryProcessEvent(event) || BaseFrame::TryBefore(event); } private: diff --git a/src/common/docview.cpp b/src/common/docview.cpp index 468016ec2d..246d247254 100644 --- a/src/common/docview.cpp +++ b/src/common/docview.cpp @@ -2014,6 +2014,26 @@ void wxDocManager::ActivateView(wxView *view, bool activate) // wxDocChildFrameAnyBase // ---------------------------------------------------------------------------- +bool wxDocChildFrameAnyBase::TryProcessEvent(wxEvent& event) +{ + if ( !m_childView ) + { + // We must be being destroyed, don't forward events anywhere as + // m_childDocument could be invalid by now. + return false; + } + + // Forward the event to the document manager which will, in turn, forward + // it to its active view which must be our m_childView. + // + // Notice that we do things in this roundabout way to guarantee the correct + // event handlers call order: first the document, then the new and then the + // document manager itself. And if we forwarded the event directly to the + // view, then the document manager would do it once again when we forwarded + // it to it. + return m_childDocument->GetDocumentManager()->ProcessEventLocally(event); +} + bool wxDocChildFrameAnyBase::CloseView(wxCloseEvent& event) { if ( m_childView ) @@ -2046,6 +2066,35 @@ bool wxDocChildFrameAnyBase::CloseView(wxCloseEvent& event) // wxDocParentFrameAnyBase // ---------------------------------------------------------------------------- +bool wxDocParentFrameAnyBase::TryProcessEvent(wxEvent& event) +{ + if ( !m_docManager ) + return false; + + // If we have an active view, its associated child frame may have + // already forwarded the event to wxDocManager, check for this: + if ( wxView* const view = m_docManager->GetAnyUsableView() ) + { + // Notice that we intentionally don't use wxGetTopLevelParent() here + // because we want to check both for the case of a child "frame" (e.g. + // MDI child frame or notebook page) inside this TLW and a separate + // child TLW frame (as used in the SDI mode) here. + for ( wxWindow* win = view->GetFrame(); win; win = win->GetParent() ) + { + if ( win == m_frame ) + return false; + } + } + + // But forward the event to wxDocManager ourselves if there are no views at + // all or if we are the frame's view ourselves. + return m_docManager->ProcessEventLocally(event); +} + +// ---------------------------------------------------------------------------- +// Printing support +// ---------------------------------------------------------------------------- + #if wxUSE_PRINTING_ARCHITECTURE namespace diff --git a/tests/events/propagation.cpp b/tests/events/propagation.cpp index 0a9222f7e0..5bd36a3bd2 100644 --- a/tests/events/propagation.cpp +++ b/tests/events/propagation.cpp @@ -24,6 +24,7 @@ #include "wx/window.h" #endif // WX_PRECOMP +#include "wx/docmdi.h" #include "wx/frame.h" #include "wx/menu.h" #include "wx/scopedptr.h" @@ -100,6 +101,26 @@ struct TestPaintEvtHandler : TestEvtHandlerBase } }; +// 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 { @@ -195,6 +216,7 @@ private: CPPUNIT_TEST( ScrollWindowWithoutHandler ); CPPUNIT_TEST( ScrollWindowWithHandler ); CPPUNIT_TEST( MenuEvent ); + CPPUNIT_TEST( DocView ); CPPUNIT_TEST_SUITE_END(); void OneHandler(); @@ -205,6 +227,7 @@ private: void ScrollWindowWithoutHandler(); void ScrollWindowWithHandler(); void MenuEvent(); + void DocView(); DECLARE_NO_COPY_CLASS(EventPropagationTestCase) }; @@ -434,3 +457,95 @@ void EventPropagationTestCase::MenuEvent() 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" ); +}