From 3cbb9df817f7dbd00ced084aaa6f5a782d0d839b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 9 May 2010 14:55:28 +0000 Subject: [PATCH] Refactor the event processing code to add ProcessEventLocally(). This new method can be used to only process the event in this handler or any handlers connected to it (unlike ProcessEventHere() which doesn't follow the chain at all), without propagating the event upwards (unlike ProcessEvent()). Unfortunately implementing this required a field to wxEvent but there doesn't seem to be any other way to do what we need. There should be no user-visible changes after this commit, it just paves the way for the upcoming fixes. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@64261 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/event.h | 62 ++++++++++++++++++++++++++++-- interface/wx/event.h | 37 ++++++++++++++++-- src/common/event.cpp | 91 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 170 insertions(+), 20 deletions(-) diff --git a/include/wx/event.h b/include/wx/event.h index 36f7a8b73e..2ce70e70a5 100644 --- a/include/wx/event.h +++ b/include/wx/event.h @@ -996,6 +996,11 @@ public: return false; } + // This is also used only internally by ProcessEvent() to check if it + // should process the event normally or only restrict the search for the + // event handler to this object itself. + bool ShouldProcessHereOnly() const { return m_processHereOnly; } + protected: wxObject* m_eventObject; wxEventType m_eventType; @@ -1020,6 +1025,11 @@ protected: // once for this event bool m_wasProcessed; + // this flag is used by ProcessEventLocally() to prevent ProcessEvent() + // from doing its usual stuff and force it to just call ProcessEventHere() + // instead, see the comment there explaining why is this needed + bool m_processHereOnly; + protected: wxEvent(const wxEvent&); // for implementing Clone() wxEvent& operator=(const wxEvent&); // for derived classes operator=() @@ -1028,6 +1038,10 @@ private: // it needs to access our m_propagationLevel friend class WXDLLIMPEXP_FWD_BASE wxPropagateOnce; + // and this one needs to access our m_processHereOnly + friend class WXDLLIMPEXP_FWD_BASE wxEventProcessHereOnly; + + DECLARE_ABSTRACT_CLASS(wxEvent) }; @@ -1079,6 +1093,32 @@ private: wxDECLARE_NO_COPY_CLASS(wxPropagateOnce); }; +// A helper used by ProcessEventLocally() to restrict the event processing +// to this handler only. +class WXDLLIMPEXP_BASE wxEventProcessHereOnly +{ +public: + wxEventProcessHereOnly(wxEvent& event) : m_event(event) + { + // This would be unexpected and would also restore the wrong value in + // this class dtor so if even does happen legitimately we'd need to + // store the value in ctor and restore it in dtor. + wxASSERT_MSG( !m_event.m_processHereOnly, + "shouldn't be used twice for the same event" ); + + m_event.m_processHereOnly = true; + } + + ~wxEventProcessHereOnly() + { + m_event.m_processHereOnly = false; + } + +private: + wxEvent& m_event; + + wxDECLARE_NO_COPY_CLASS(wxEventProcessHereOnly); +}; #if wxUSE_GUI @@ -2966,6 +3006,20 @@ public: bool SafelyProcessEvent(wxEvent& event); // NOTE: uses ProcessEvent() + // This method tries to process the event in this event handler, including + // any preprocessing done by TryBefore() and all the handlers chained to + // it, but excluding the post-processing done in TryAfter(). + // + // It is meant to be called from ProcessEvent() only and is not virtual, + // additional event handlers can be hooked into the normal event processing + // logic using TryBefore() and TryAfter() hooks. + // + // You can also call it yourself to forward an event to another handler but + // without propagating it upwards if it's unhandled (this is usually + // unwanted when forwarding as the original handler would already do it if + // needed normally). + bool ProcessEventLocally(wxEvent& event); + // Schedule the given event to be processed later. It takes ownership of // the event pointer, i.e. it will be deleted later. This is safe to call // from multiple threads although you still need to ensure that wxString @@ -3180,9 +3234,8 @@ public: // The method tries to process the event in this event handler. // - // It is meant to be called from ProcessEvent() only and is not virtual, - // additional event handlers can be hooked into the normal event processing - // logic using TryBefore() and TryAfter() hooks. + // It is called from ProcessEventLocally() and normally shouldn't be called + // directly as doing it would ignore any chained event handlers. bool ProcessEventHere(wxEvent& event); @@ -3274,6 +3327,9 @@ private: // pass the event to wxTheApp instance, called from TryAfter() bool DoTryApp(wxEvent& event); + // try to process events in all handlers chained to this one + bool DoTryChain(wxEvent& event); + DECLARE_DYNAMIC_CLASS_NO_COPY(wxEvtHandler) }; diff --git a/interface/wx/event.h b/interface/wx/event.h index b94413f2c1..4a10ad18ce 100644 --- a/interface/wx/event.h +++ b/interface/wx/event.h @@ -489,8 +489,8 @@ public: processed, ProcessEvent() on wxTheApp object is called as the last step. - Notice that steps (3)-(5) are performed in ProcessEventHere() which is - called by this function. + Notice that steps (2)-(6) are performed in ProcessEventLocally() + which is called by this function. @param event Event to process. @@ -502,14 +502,43 @@ public: */ virtual bool ProcessEvent(wxEvent& event); + /** + Try to process the event in this handler and all those chained to it. + + As explained in ProcessEvent() documentation, the event handlers may be + chained in a doubly-linked list. This function tries to process the + event in this handler (including performing any pre-processing done in + TryBefore(), e.g. applying validators) and all those following it in + the chain until the event is processed or the chain is exhausted. + + This function is called from ProcessEvent() and, in turn, calls + ProcessEventHere() for each handler in turn. It is not virtual and so + cannot be overridden but can, and should, be called to forward an event + to another handler instead of ProcessEvent() which would result in a + duplicate call to TryAfter(), e.g. resulting in all unprocessed events + being sent to the application object multiple times. + + @since 2.9.1 + + @param event + Event to process. + @return + @true if this handler of one of those chained to it processed the + event. + */ + bool ProcessEventLocally(wxEvent& event); + /** Try to process the event in this event handler. - This method is called from ProcessEvent(), please see the detailed - description of the event processing logic there. + This method is called from ProcessEventLocally() and thus, + indirectly, from ProcessEvent(), please see the detailed description of + the event processing logic there. It is @em not virtual and so may not be overridden. + @since 2.9.1 + @param event Event to process. @return diff --git a/src/common/event.cpp b/src/common/event.cpp index 5f521211c4..ba5e9c29be 100644 --- a/src/common/event.cpp +++ b/src/common/event.cpp @@ -366,6 +366,7 @@ wxEvent::wxEvent(int theId, wxEventType commandType) m_isCommandEvent = false; m_propagationLevel = wxEVENT_PROPAGATE_NONE; m_wasProcessed = false; + m_processHereOnly = false; } wxEvent::wxEvent(const wxEvent& src) @@ -379,6 +380,7 @@ wxEvent::wxEvent(const wxEvent& src) , m_skipped(src.m_skipped) , m_isCommandEvent(src.m_isCommandEvent) , m_wasProcessed(false) + , m_processHereOnly(false) { } @@ -395,7 +397,7 @@ wxEvent& wxEvent::operator=(const wxEvent& src) m_skipped = src.m_skipped; m_isCommandEvent = src.m_isCommandEvent; - // don't change m_wasProcessed + // don't change m_wasProcessed nor m_processHereOnly return *this; } @@ -1330,6 +1332,18 @@ bool wxEvtHandler::TryBefore(wxEvent& event) bool wxEvtHandler::TryAfter(wxEvent& event) { + // We only want to pass the window to the application object once even if + // there are several chained handlers. Ensure that this is what happens by + // only calling DoTryApp() if there is no next handler (which would do it). + // + // Notice that, unlike simply calling TryAfter() on the last handler in the + // chain only from ProcessEvent(), this also works with wxWindow object in + // the middle of the chain: its overridden TryAfter() will still be called + // and propagate the event upwards the window hierarchy even if it's not + // the last one in the chain (which, admittedly, shouldn't happen often). + if ( GetNextHandler() ) + return GetNextHandler()->TryAfter(event); + #if WXWIN_COMPATIBILITY_2_8 // as above, call the old virtual function for compatibility return TryParent(event); @@ -1340,11 +1354,12 @@ bool wxEvtHandler::TryAfter(wxEvent& event) bool wxEvtHandler::ProcessEvent(wxEvent& event) { - // allow the application to hook into event processing + // The very first thing we do is to allow the application to hook into + // event processing in order to globally pre-process all events. // - // note that we should only do it if we're the first event handler called + // Note that we should only do it if we're the first event handler called // to avoid calling FilterEvent() multiple times as the event goes through - // the event handler chain and possibly upwards the window hierarchy + // the event handler chain and possibly upwards the window hierarchy. if ( !event.WasProcessed() ) { if ( wxTheApp ) @@ -1361,22 +1376,72 @@ bool wxEvtHandler::ProcessEvent(wxEvent& event) } } - // Try the hooks which should be called before our own handlers + // Short circuit the event processing logic if we're requested to process + // this event in this handler only, see DoTryChain() for more details. + if ( event.ShouldProcessHereOnly() ) + return ProcessEventHere(event); + + + // Try to process the event in this handler itself. + if ( ProcessEventLocally(event) ) + return true; + + // If we still didn't find a handler, propagate the event upwards the + // window chain and/or to the application object. + if ( TryAfter(event) ) + return true; + + + // No handler found anywhere, bail out. + return false; +} + +bool wxEvtHandler::ProcessEventLocally(wxEvent& event) +{ + // First try the hooks which should be called before our own handlers if ( TryBefore(event) ) return true; + // Then try this handler itself, notice that we should not call + // ProcessEvent() on this one as we're already called from it, which + // explains why we do it here and not in DoTryChain() if ( ProcessEventHere(event) ) return true; - // pass the event to the next handler, notice that we shouldn't call - // TryAfter() even if it doesn't handle the event as the last handler in - // the chain will do it - if ( GetNextHandler() ) - return GetNextHandler()->ProcessEvent(event); + // Finally try the event handlers chained to this one, + if ( DoTryChain(event) ) + return true; + + // And return false to indicate that we didn't find any handler at this + // level. + return false; +} - // propagate the event upwards the window chain and/or to the application - // object if it wasn't processed at this level - return TryAfter(event); +bool wxEvtHandler::DoTryChain(wxEvent& event) +{ + for ( wxEvtHandler *h = GetNextHandler(); h; h = h->GetNextHandler() ) + { + // We need to process this event at the level of this handler only + // right now, the pre-/post-processing was either already done by + // ProcessEvent() from which we were called or will be done by it when + // we return. + // + // However we must call ProcessEvent() and not ProcessEventHere() + // because the existing code (including some in wxWidgets itself) + // expects the overridden ProcessEvent() in its custom event handlers + // pushed on a window to be called. + // + // So we must call ProcessEvent() but it must not do what it usually + // does. To resolve this paradox we pass a special "process here only" + // flag to ProcessEvent() via the event object itself. This ensures + // that if our own, base class, version is called, it will just call + // ProcessEventHere() and won't do anything else, just as we want it to. + wxEventProcessHereOnly processHereOnly(event); + if ( h->ProcessEvent(event) ) + return true; + } + + return false; } bool wxEvtHandler::ProcessEventHere(wxEvent& event) -- 2.45.2