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; - // propagate the event upwards the window chain and/or to the application - // object if it wasn't processed at this level - return TryAfter(event); + // And return false to indicate that we didn't find any handler at this + // level. + return false; +} + +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)