]> git.saurik.com Git - wxWidgets.git/commitdiff
Fix event handling order in doc/view framework.
authorVadim Zeitlin <vadim@wxwidgets.org>
Sat, 4 May 2013 23:59:56 +0000 (23:59 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Sat, 4 May 2013 23:59:56 +0000 (23:59 +0000)
Ensure that the events are always (provided there is an open document)
processed in the following order:

1. wxDocument
2. wxView
3. wxDocManager
4. wxDocChildFrame
5. wxDocParentFrame
6. wxApp

Do this by forwarding the events from wxDocParentFrame to wxDocChildFrame
first and forward them from there to wxDocManager which -- and this part
remains unchanged -- in turn forwards them to the active wxView which finally
forwards them to wxDocument. This requires another condition in the event
handling code as we still must forward from wxDocParentFrame to wxDocManager
itself if there are no active children at all, but this is the only way to
have the same event order in all cases, whether the event is originally
received by wxDocChildFrame or wxDocParentFrame.

Document this and add a unit test verifying that things indeed work like this.

See #14314.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@73928 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

docs/doxygen/overviews/docview.h
include/wx/docview.h
src/common/docview.cpp
tests/events/propagation.cpp

index 49d2689a55943a116ca1e504183335f532990bad..2c5af959d202ec30bc46250098e0ba74dfcf127b 100644 (file)
@@ -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:
+<ol>
+    <li>wxDocument opened in this frame.</li>
+    <li>wxView shown in this frame.</li>
+    <li>wxDocManager associated with the parent wxDocParentFrame.</li>
+    <li>wxDocChildFrame itself.</li>
+    <li>wxDocParentFrame, as per the usual event bubbling up to parent rules.</li>
+    <li>wxApp, again as the usual fallback for all events.</li>
+</ol>
+
+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
 
index 1c789626c783051d9146f0b7743235621850db46..c8b034721501de66a2b0b7f8e143b9588f8d54ae 100644 (file)
@@ -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:
index 468016ec2dee70aa06896f7a15a40c6672f4e7b2..246d247254fab8b99bcaae31ef178fa20c26c98a 100644 (file)
@@ -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
index 0a9222f7e0fd1a80b0e5b45490e472f17723d996..5bd36a3bd2a349da530abebc81d43916e1cec30d 100644 (file)
@@ -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<wxPaintEvent>
     }
 };
 
+// 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<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<wxFrame>
+        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" );
+}