From d48b06bd9039597545de9a862501d57c5f9ec1ea Mon Sep 17 00:00:00 2001 From: Francesco Montorsi Date: Wed, 4 Feb 2009 17:42:28 +0000 Subject: [PATCH] check in the 'selective yield' patch (see ticket #10320): - implements YieldFor() with event filtering for wxMSW and wxGTK, adds TODO markers in other ports; - replaces wxYield() in GTK's clipboard code with a wxTheApp->YieldFor() call, thus fixing possible reentrancies (and modifies clipboard sample to test synchronous IsSupported calls) - replaces wxYieldIfNeeded() calls in wxProgressDialog with wxTheApp->YieldFor() calls, so that it processes only UI/user-input events, thus fixing the race condition visible in the "thread" sample - documents the new functions git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@58654 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/app.h | 73 ++++++++++++++----- include/wx/cocoa/app.h | 6 +- include/wx/dfb/app.h | 3 +- include/wx/event.h | 124 ++++++++++++++++++++++++++++++-- include/wx/gtk/app.h | 7 +- include/wx/gtk1/app.h | 3 +- include/wx/list.h | 11 +++ include/wx/mgl/app.h | 3 +- include/wx/motif/app.h | 2 +- include/wx/msw/app.h | 3 +- include/wx/os2/app.h | 3 +- include/wx/osx/app.h | 4 +- include/wx/palmos/app.h | 3 +- include/wx/socket.h | 1 + include/wx/timer.h | 1 + include/wx/x11/app.h | 2 +- interface/wx/app.h | 52 +++++++++++--- interface/wx/event.h | 104 +++++++++++++++++++++++++-- samples/clipboard/clipboard.cpp | 17 ++--- samples/thread/thread.cpp | 33 ++++++--- src/cocoa/app.mm | 5 +- src/common/appbase.cpp | 33 ++++++++- src/common/appcmn.cpp | 21 ++++++ src/common/event.cpp | 80 +++++++++++++++++++-- src/common/init.cpp | 1 + src/dfb/app.cpp | 4 +- src/dfb/evtloop.cpp | 2 + src/generic/progdlgg.cpp | 6 +- src/gtk/app.cpp | 122 ++++++++++++++++++++++++++++--- src/gtk/clipbrd.cpp | 18 ++--- src/gtk1/app.cpp | 4 +- src/mgl/app.cpp | 5 +- src/motif/app.cpp | 4 +- src/msw/app.cpp | 109 ++++++++++++++++++++++++++-- src/msw/evtloop.cpp | 5 ++ src/os2/app.cpp | 5 +- src/osx/carbon/app.cpp | 3 +- src/palmos/app.cpp | 2 +- src/x11/app.cpp | 4 +- 39 files changed, 777 insertions(+), 111 deletions(-) diff --git a/include/wx/app.h b/include/wx/app.h index d4ff103779..9113fbd6e1 100644 --- a/include/wx/app.h +++ b/include/wx/app.h @@ -32,6 +32,7 @@ class WXDLLIMPEXP_FWD_BASE wxMessageOutput; #if wxUSE_GUI struct WXDLLIMPEXP_FWD_CORE wxVideoMode; + class WXDLLIMPEXP_FWD_CORE wxWindow; #endif // ---------------------------------------------------------------------------- @@ -254,6 +255,13 @@ public: // (already) be dispatched static bool IsMainLoopRunning(); + // temporary suspends processing of the pending events + virtual void SuspendProcessingOfPendingEvents(); + + // resume processing of the pending events previously stopped because of a + // call to SuspendProcessingOfPendingEvents() + virtual void ResumeProcessingOfPendingEvents(); + // process all events in the wxHandlersWithPendingEvents list -- it is necessary // to call this function to process posted events. This happens during each // event loop iteration in GUI mode but if there is no main loop, it may be @@ -263,9 +271,6 @@ public: // check if there are pending events on global pending event list bool HasPendingEvents() const; - // doesn't do anything in this class, just a hook for GUI wxApp - virtual bool Yield(bool WXUNUSED(onlyIfNeeded) = false) { return true; } - // make sure that idle events are sent again virtual void WakeUpIdle(); @@ -300,6 +305,30 @@ public: virtual bool OnExceptionInMainLoop(); #endif // wxUSE_EXCEPTIONS + // Yield-related hooks + // ------------------- + + // process all currently pending events right now + // + // it is an error to call Yield() recursively unless the value of + // onlyIfNeeded is true + // + // WARNING: this function is dangerous as it can lead to unexpected + // reentrancies (i.e. when called from an event handler it + // may result in calling the same event handler again), use + // with _extreme_ care or, better, don't use at all! + // NOTE: in wxConsoleBase it doesn't do anything, just a hook for GUI wxApp + bool Yield(bool onlyIfNeeded = false) + { return DoYield(onlyIfNeeded, wxEVT_CATEGORY_ALL); } + bool YieldFor(long eventsToProcess) + { return DoYield(true, eventsToProcess); } + virtual bool IsYielding() const + { return false; } + virtual bool IsEventAllowedInsideYield(wxEventCategory WXUNUSED(cat)) const + { return true; } + // no SafeYield hooks since it uses wxWindow which is not available when wxUSE_GUI=0 + + // debugging support // ----------------- @@ -366,6 +395,9 @@ protected: // for the first time virtual wxAppTraits *CreateTraits(); + // the real yield function hook: + virtual bool DoYield(bool WXUNUSED(onlyIfNeeded), long WXUNUSED(eventsToProcess)) + { return true; } // function used for dynamic wxApp creation static wxAppInitializerFunction ms_appInitFn; @@ -392,6 +424,15 @@ protected: // been started yet or has already terminated) wxEventLoopBase *m_mainLoop; + // the array of the handlers with pending events which needs to be processed + // inside ProcessPendingEvents() + // wxEvtHandlerArray m_handlersWithPendingEvents; FIXME: enable this and remove global lists + + // helper array used by ProcessPendingEvents() + // wxEvtHandlerArray m_handlersWithPendingDelayedEvents; FIXME: enable this and remove global lists + + friend class WXDLLIMPEXP_BASE wxEvtHandler; + // the application object is a singleton anyhow, there is no sense in // copying it DECLARE_NO_COPY_CLASS(wxAppConsoleBase) @@ -451,23 +492,19 @@ public: // the worker functions - usually not used directly by the user code // ----------------------------------------------------------------- - - - // process all currently pending events right now - // - // it is an error to call Yield() recursively unless the value of - // onlyIfNeeded is true - // - // WARNING: this function is dangerous as it can lead to unexpected - // reentrancies (i.e. when called from an event handler it - // may result in calling the same event handler again), use - // with _extreme_ care or, better, don't use at all! - virtual bool Yield(bool onlyIfNeeded = false) = 0; + // safer alternatives to Yield(), using wxWindowDisabler + virtual bool SafeYield(wxWindow *win, bool onlyIfNeeded); + virtual bool SafeYieldFor(wxWindow *win, long eventsToProcess); // returns true if the main thread is inside a Yield() call - bool IsYielding() const + virtual bool IsYielding() const { return m_isInsideYield; } + // returns true if events of the given event category should be immediately + // processed inside a wxApp::Yield() call or rather should be queued for + // later processing by the main event loop + virtual bool IsEventAllowedInsideYield(wxEventCategory cat) const; + // this virtual function is called in the GUI mode when the application // becomes idle and normally just sends wxIdleEvent to all interested // parties @@ -590,7 +627,9 @@ protected: // does any of our windows have focus? bool m_isActive; + // Yield() helpers: bool m_isInsideYield; + long m_eventsToProcessInsideYield; DECLARE_NO_COPY_CLASS(wxAppBase) }; @@ -660,7 +699,7 @@ protected: // Force an exit from main loop WXDLLIMPEXP_BASE void wxExit(); -// avoid redeclaring this function here if it had been already declated by +// avoid redeclaring this function here if it had been already declared by // wx/utils.h, this results in warnings from g++ with -Wredundant-decls #ifndef wx_YIELD_DECLARED #define wx_YIELD_DECLARED diff --git a/include/wx/cocoa/app.h b/include/wx/cocoa/app.h index bed4a07c7f..7285f71e65 100644 --- a/include/wx/cocoa/app.h +++ b/include/wx/cocoa/app.h @@ -57,14 +57,14 @@ public: // Implement wxAppBase pure virtuals virtual void Exit(); - virtual bool Yield(bool onlyIfNeeded = FALSE); + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); virtual void WakeUpIdle(); - + virtual bool Initialize(int& argc, wxChar **argv); virtual void CleanUp(); virtual bool CallOnInit(); - + virtual bool OnInit(); virtual bool OnInitGui(); diff --git a/include/wx/dfb/app.h b/include/wx/dfb/app.h index 80e7dfcb08..b76af79ce3 100644 --- a/include/wx/dfb/app.h +++ b/include/wx/dfb/app.h @@ -32,7 +32,6 @@ public: virtual void CleanUp(); virtual void WakeUpIdle(); - virtual bool Yield(bool onlyIfNeeded = false); virtual wxVideoMode GetDisplayMode() const; virtual bool SetDisplayMode(const wxVideoMode& mode); @@ -40,6 +39,8 @@ public: private: wxVideoMode m_videoMode; + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); + DECLARE_DYNAMIC_CLASS(wxApp) }; diff --git a/include/wx/event.h b/include/wx/event.h index 6b0f093f30..b92e213cd6 100644 --- a/include/wx/event.h +++ b/include/wx/event.h @@ -574,6 +574,9 @@ wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_COMMAND_TOOL_RCLICKED, wxComman wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_COMMAND_TOOL_DROPDOWN_CLICKED, wxCommandEvent) wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_COMMAND_TOOL_ENTER, wxCommandEvent) + // Thread events +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_COMMAND_THREAD, wxCommandEvent) + // Mouse event types wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_LEFT_DOWN, wxMouseEvent) wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_LEFT_UP, wxMouseEvent) @@ -718,9 +721,14 @@ wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_DETAILED_HELP, wxHelpEvent) // still, any new code using it should include wx/textctrl.h explicitly wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEvent) + +// ---------------------------------------------------------------------------- +// wxEvent(-derived) classes +// ---------------------------------------------------------------------------- + // the predefined constants for the number of times we propagate event // upwards window child-parent chain -enum Propagation_state +enum wxEventPropagation { // don't propagate it at all wxEVENT_PROPAGATE_NONE = 0, @@ -729,6 +737,58 @@ enum Propagation_state wxEVENT_PROPAGATE_MAX = INT_MAX }; +// The different categories for a wxEvent; see wxEvent::GetEventCategory. +// NOTE: they are used as OR-combinable flags by wxApp::Yield +enum wxEventCategory +{ + // this is the category for those events which are generated to update + // the appearance of the GUI but which (usually) do not comport data + // processing, i.e. which do not provide input or output data + // (e.g. size events, scroll events, etc). + // They are events NOT directly generated by the user's input devices. + wxEVT_CATEGORY_UI = 1, + + // this category groups those events which are generated directly from the + // user through input devices like mouse and keyboard and usually result in + // data to be processed from the application. + // (e.g. mouse clicks, key presses, etc) + wxEVT_CATEGORY_USER_INPUT = 2, + + // this category is for wxSocketEvent + wxEVT_CATEGORY_SOCKET = 4, + + // this category is for wxTimerEvent + wxEVT_CATEGORY_TIMER = 8, + + // this category is for any event used to send notifications from the + // secondary threads to the main one or in general for notifications among + // different threads (which may or may not be user-generated) + wxEVT_CATEGORY_THREAD = 16, + + + // implementation only + + // used in the implementations of DoYield() + wxEVT_CATEGORY_UNKNOWN = 32, + + // a special category used as an argument to wxApp::Yield() to indicate that + // Yield() should leave all wxEvents on the queue while emptying the native event queue + // (native events will be processed but the wxEvents they generate will be queued) + wxEVT_CATEGORY_CLIPBOARD = 64, + + + // shortcut masks + + // this category groups those events which are emitted in response to + // events of the native toolkit and which typically are not-"delayable". + wxEVT_CATEGORY_NATIVE_EVENTS = wxEVT_CATEGORY_UI|wxEVT_CATEGORY_USER_INPUT, + + // used in wxApp::Yield to specify all event categories should be processed: + wxEVT_CATEGORY_ALL = + wxEVT_CATEGORY_UI|wxEVT_CATEGORY_USER_INPUT|wxEVT_CATEGORY_SOCKET| \ + wxEVT_CATEGORY_TIMER|wxEVT_CATEGORY_THREAD +}; + /* * wxWidgets events, covering all interesting things that might happen * (button clicking, resizing, setting text in widgets, etc.). @@ -748,10 +808,13 @@ public: void SetEventType(wxEventType typ) { m_eventType = typ; } wxEventType GetEventType() const { return m_eventType; } + wxObject *GetEventObject() const { return m_eventObject; } void SetEventObject(wxObject *obj) { m_eventObject = obj; } + long GetTimestamp() const { return m_timeStamp; } void SetTimestamp(long ts = 0) { m_timeStamp = ts; } + int GetId() const { return m_id; } void SetId(int Id) { m_id = Id; } @@ -767,6 +830,12 @@ public: // for them wouldn't work (it needs to do a copy of the event) virtual wxEvent *Clone() const = 0; + // this function is used to selectively process events in wxApp::YieldFor + // NOTE: by default it returns wxEVT_CATEGORY_UI just because the major + // part of wxWidgets events belong to that category. + virtual wxEventCategory GetEventCategory() const + { return wxEVT_CATEGORY_UI; } + // Implementation only: this test is explicitly anti OO and this function // exists only for optimization purposes. bool IsCommandEvent() const { return m_isCommandEvent; } @@ -818,9 +887,6 @@ public: protected: // the propagation level: while it is positive, we propagate the event to // the parent window (if any) - // - // this one doesn't have to be public, we don't have to worry about - // backwards compatibility as it is new int m_propagationLevel; bool m_skipped; @@ -956,6 +1022,7 @@ public: int GetInt() const { return m_commandInt; } virtual wxEvent *Clone() const { return new wxCommandEvent(*this); } + virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_USER_INPUT; } protected: wxString m_cmdString; // String event argument @@ -999,6 +1066,36 @@ private: DECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxNotifyEvent) }; + +// Thread event + +class WXDLLIMPEXP_BASE wxThreadEvent : public wxCommandEvent +{ +public: + wxThreadEvent(int id = wxID_ANY) + : wxCommandEvent(wxEVT_COMMAND_THREAD, id) + { } + + virtual wxEvent *Clone() const + { + // make sure our string member (which uses COW aka refcounting) is not + // shared by other string instances: + const_cast(this)->SetString(GetString().c_str()); + + return new wxThreadEvent(*this); + } + + // this is important to avoid that calling wxApp::Yield() thread events + // gets processed when this is unwanted: + virtual wxEventCategory GetEventCategory() const + { return wxEVT_CATEGORY_THREAD; } + +private: + DECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxThreadEvent) +}; + + + // Scroll event class, derived form wxCommandEvent. wxScrollEvents are // sent by wxSlider and wxScrollBar. /* @@ -1067,6 +1164,8 @@ private: DECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxScrollWinEvent) }; + + // Mouse event class /* @@ -1247,6 +1346,7 @@ public: bool IsPageScroll() const { return ((unsigned int)m_linesPerAction == UINT_MAX); } virtual wxEvent *Clone() const { return new wxMouseEvent(*this); } + virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_USER_INPUT; } wxMouseEvent& operator=(const wxMouseEvent& event) { @@ -1370,6 +1470,7 @@ public: wxCoord GetY() const { return m_y; } virtual wxEvent *Clone() const { return new wxKeyEvent(*this); } + virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_USER_INPUT; } // we do need to copy wxKeyEvent sometimes (in wxTreeCtrl code, for // example) @@ -2537,6 +2638,7 @@ private: */ + // ============================================================================ // event handler and related classes // ============================================================================ @@ -3217,6 +3319,8 @@ private: DECLARE_DYNAMIC_CLASS_NO_COPY(wxEvtHandler) }; +WX_DEFINE_EXPORTED_ARRAY_PTR(wxEvtHandler*, wxEvtHandlerArray); + // ---------------------------------------------------------------------------- // wxEventConnectionRef represents all connections between two event handlers // and enables automatic disconnect when an event handler sink goes out of @@ -3322,6 +3426,7 @@ protected: }; typedef void (wxEvtHandler::*wxCommandEventFunction)(wxCommandEvent&); +typedef void (wxEvtHandler::*wxThreadEventFunction)(wxThreadEvent&); typedef void (wxEvtHandler::*wxScrollEventFunction)(wxScrollEvent&); typedef void (wxEvtHandler::*wxScrollWinEventFunction)(wxScrollWinEvent&); typedef void (wxEvtHandler::*wxSizeEventFunction)(wxSizeEvent&); @@ -3361,6 +3466,8 @@ typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent& #define wxCommandEventHandler(func) \ wxEVENT_HANDLER_CAST(wxCommandEventFunction, func) +#define wxThreadEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxThreadEventFunction, func) #define wxScrollEventHandler(func) \ wxEVENT_HANDLER_CAST(wxScrollEventFunction, func) #define wxScrollWinEventHandler(func) \ @@ -3840,6 +3947,9 @@ typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent& #define EVT_TEXT_COPY(winid, func) wx__DECLARE_EVT1(wxEVT_COMMAND_TEXT_COPY, winid, wxClipboardTextEventHandler(func)) #define EVT_TEXT_PASTE(winid, func) wx__DECLARE_EVT1(wxEVT_COMMAND_TEXT_PASTE, winid, wxClipboardTextEventHandler(func)) +// Thread events +#define EVT_THREAD(id, func) wx__DECLARE_EVT1(wxEVT_COMMAND_THREAD, id, wxThreadEventHandler(func)) + // ---------------------------------------------------------------------------- // Global data // ---------------------------------------------------------------------------- @@ -3848,10 +3958,16 @@ typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent& // // notice that each event handler should occur at most once in this list extern WXDLLIMPEXP_BASE wxList *wxHandlersWithPendingEvents; +extern WXDLLIMPEXP_BASE wxList *wxHandlersWithPendingDelayedEvents; #if wxUSE_THREADS + // this critical section protectes both the lists above extern WXDLLIMPEXP_BASE wxCriticalSection *wxHandlersWithPendingEventsLocker; #endif +// old list names: +#define wxPendingEvents wxHandlersWithPendingEvents +#define wxPendingEventsLocker wxHandlersWithPendingEventsLocker + // ---------------------------------------------------------------------------- // Helper functions // ---------------------------------------------------------------------------- diff --git a/include/wx/gtk/app.h b/include/wx/gtk/app.h index ce6da5421e..87a2ff8c43 100644 --- a/include/wx/gtk/app.h +++ b/include/wx/gtk/app.h @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// // Name: wx/gtk/app.h -// Purpose: +// Purpose: wxApp definition for wxGTK // Author: Robert Roebling // Id: $Id$ // Copyright: (c) 1998 Robert Roebling, Julian Smart @@ -40,7 +40,6 @@ public: virtual bool OnInitGui(); // override base class (pure) virtuals - virtual bool Yield(bool onlyIfNeeded = FALSE); virtual void WakeUpIdle(); virtual bool Initialize(int& argc, wxChar **argv); @@ -79,6 +78,10 @@ public: bool EventsPending(); bool DoIdle(); +protected: + + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); + private: // true if we're inside an assert modal dialog #ifdef __WXDEBUG__ diff --git a/include/wx/gtk1/app.h b/include/wx/gtk1/app.h index db2d753e3d..32ef7416a0 100644 --- a/include/wx/gtk1/app.h +++ b/include/wx/gtk1/app.h @@ -38,7 +38,6 @@ public: virtual bool OnInitGui(); // override base class (pure) virtuals - virtual bool Yield(bool onlyIfNeeded = FALSE); virtual void WakeUpIdle(); virtual bool Initialize(int& argc, wxChar **argv); @@ -71,6 +70,8 @@ private: bool m_isInAssert; #endif // __WXDEBUG__ + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); + DECLARE_DYNAMIC_CLASS(wxApp) }; diff --git a/include/wx/list.h b/include/wx/list.h index 950156d4f8..4a55c64134 100644 --- a/include/wx/list.h +++ b/include/wx/list.h @@ -1277,4 +1277,15 @@ public: (list).clear(); \ } +// append all element of one list to another one +#define WX_APPEND_LIST(list, other) \ + { \ + wxList::compatibility_iterator node = other->GetFirst(); \ + while ( node ) \ + { \ + (list)->push_back(node->GetData()); \ + node = node->GetNext(); \ + } \ + } + #endif // _WX_LISTH__ diff --git a/include/wx/mgl/app.h b/include/wx/mgl/app.h index 686137a5a2..d771014e8d 100644 --- a/include/wx/mgl/app.h +++ b/include/wx/mgl/app.h @@ -44,7 +44,6 @@ public: virtual void Exit(); virtual void WakeUpIdle(); - virtual bool Yield(bool onlyIfNeeded = FALSE); virtual wxVideoMode GetDisplayMode() const { return m_displayMode; } virtual bool SetDisplayMode(const wxVideoMode& mode); @@ -52,6 +51,8 @@ public: private: DECLARE_DYNAMIC_CLASS(wxApp) + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); + wxVideoMode m_displayMode; }; diff --git a/include/wx/motif/app.h b/include/wx/motif/app.h index eef7c9e054..8879381460 100644 --- a/include/wx/motif/app.h +++ b/include/wx/motif/app.h @@ -53,7 +53,6 @@ public: virtual void Exit(); - virtual bool Yield(bool onlyIfNeeded = false); virtual void WakeUpIdle(); // implemented in motif/evtloop.cpp // implementation from now on @@ -66,6 +65,7 @@ public: // Implementation virtual bool Initialize(int& argc, wxChar **argv); virtual void CleanUp(); + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); // Motif-specific WXAppContext GetAppContext() const { return m_appContext; } diff --git a/include/wx/msw/app.h b/include/wx/msw/app.h index 62c6668f1a..d7175118e1 100644 --- a/include/wx/msw/app.h +++ b/include/wx/msw/app.h @@ -33,7 +33,6 @@ public: virtual bool Initialize(int& argc, wxChar **argv); virtual void CleanUp(); - virtual bool Yield(bool onlyIfNeeded = false); virtual void WakeUpIdle(); virtual void SetPrintMode(int mode) { m_printMode = mode; } @@ -79,6 +78,8 @@ public: protected: int m_printMode; // wxPRINT_WINDOWS, wxPRINT_POSTSCRIPT + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); + public: // unregister any window classes registered by GetRegisteredClassName() static void UnregisterWindowClasses(); diff --git a/include/wx/os2/app.h b/include/wx/os2/app.h index 6c811e3a1f..26716011af 100644 --- a/include/wx/os2/app.h +++ b/include/wx/os2/app.h @@ -75,7 +75,6 @@ public: virtual bool OnInitGui(void); - virtual bool Yield(bool onlyIfNeeded = false); virtual void WakeUpIdle(void); virtual void SetPrintMode(int mode) { m_nPrintMode = mode; } @@ -111,6 +110,8 @@ public: // Implementation static bool RegisterWindowClasses(HAB vHab); + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); + public: int m_nCmdShow; HMQ m_hMq; diff --git a/include/wx/osx/app.h b/include/wx/osx/app.h index 2ebb72cc1a..ad80d008a5 100644 --- a/include/wx/osx/app.h +++ b/include/wx/osx/app.h @@ -38,7 +38,6 @@ class WXDLLIMPEXP_CORE wxApp: public wxAppBase wxApp(); virtual ~wxApp() {} - virtual bool Yield(bool onlyIfNeeded = FALSE); virtual void WakeUpIdle(); virtual void SetPrintMode(int mode) { m_printMode = mode; } @@ -67,6 +66,7 @@ public: // Implementation virtual bool Initialize(int& argc, wxChar **argv); virtual void CleanUp(); + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); // the installed application event handler WXEVENTHANDLERREF MacGetEventHandler() { return m_macEventHandler ; } @@ -88,7 +88,7 @@ public: private: // mac specifics virtual bool DoInitGui(); - virtual void DoCleanUp(); + virtual void DoCleanUp(); WXEVENTHANDLERREF m_macEventHandler ; WXEVENTHANDLERCALLREF m_macCurrentEventHandlerCallRef ; diff --git a/include/wx/palmos/app.h b/include/wx/palmos/app.h index c2f4fa6e93..84d740ea64 100644 --- a/include/wx/palmos/app.h +++ b/include/wx/palmos/app.h @@ -35,7 +35,6 @@ public: virtual bool Initialize(int& argc, wxChar **argv); virtual void CleanUp(); - virtual bool Yield(bool onlyIfNeeded = false); virtual void WakeUpIdle(); virtual void SetPrintMode(int mode) { m_printMode = mode; } @@ -52,6 +51,8 @@ public: protected: int m_printMode; // wxPRINT_WINDOWS, wxPRINT_POSTSCRIPT + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); + public: // Implementation static bool RegisterWindowClasses(); diff --git a/include/wx/socket.h b/include/wx/socket.h index 628605f606..94818bed03 100644 --- a/include/wx/socket.h +++ b/include/wx/socket.h @@ -382,6 +382,7 @@ public: void *GetClientData() const { return m_clientData; } virtual wxEvent *Clone() const { return new wxSocketEvent(*this); } + virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_SOCKET; } public: wxSocketNotify m_event; diff --git a/include/wx/timer.h b/include/wx/timer.h index 87ac6b9e06..2677cab684 100644 --- a/include/wx/timer.h +++ b/include/wx/timer.h @@ -172,6 +172,7 @@ public: // implement the base class pure virtual virtual wxEvent *Clone() const { return new wxTimerEvent(*this); } + virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_TIMER; } private: wxTimer* m_timer; diff --git a/include/wx/x11/app.h b/include/wx/x11/app.h index 02bea53fff..588d62242d 100644 --- a/include/wx/x11/app.h +++ b/include/wx/x11/app.h @@ -45,7 +45,6 @@ public: virtual void Exit(); - virtual bool Yield(bool onlyIfNeeded = FALSE); virtual void WakeUpIdle(); virtual bool OnInitGui(); @@ -64,6 +63,7 @@ public: // Implementation virtual bool Initialize(int& argc, wxChar **argv); virtual void CleanUp(); + virtual bool DoYield(bool onlyIfNeeded, long eventsToProcess); WXWindow GetTopLevelWidget() const { return m_topLevelWidget; } WXColormap GetMainColormap(WXDisplay* display); diff --git a/interface/wx/app.h b/interface/wx/app.h index 893ee20c6c..ad5ef7b621 100644 --- a/interface/wx/app.h +++ b/interface/wx/app.h @@ -162,7 +162,26 @@ public: @a onlyIfNeeded parameter is @true, the method will just silently return @false instead. */ - virtual bool Yield(bool onlyIfNeeded = false); + bool Yield(bool onlyIfNeeded = false); + + /** + Works like Yield() with @e onlyIfNeeded == @true, except that it allows + the caller to specify a mask of the ::wxEventCategory values which + indicates which events should be processed and which should instead + be "delayed" (i.e. processed by the main loop later). + + Note that this is a safer alternative to Yield() since it ensures that + only the events you're interested to are processed; i.e. helps to avoid + unwanted reentrancies. + */ + bool YieldFor(long eventsToProcess); + + /** + Returns @true if the given event category is allowed inside + a YieldFor() call (i.e. compares the given category against the + last mask passed to YieldFor()). + */ + virtual bool IsEventAllowedInsideYield(wxEventCategory cat) const; //@} @@ -180,7 +199,7 @@ public: /** Returns the one and only global application object. - Usually wxTheApp is used instead. + Usually ::wxTheApp is used instead. @see SetInstance() */ @@ -621,6 +640,25 @@ public: */ virtual bool IsActive() const; + /** + This function is similar to wxYield(), except that it disables the user + input to all program windows before calling wxAppConsole::Yield and re-enables it + again afterwards. If @a win is not @NULL, this window will remain enabled, + allowing the implementation of some limited user interaction. + Returns the result of the call to wxAppConsole::Yield. + + @see wxSafeYield + */ + virtual bool SafeYield(wxWindow *win, bool onlyIfNeeded); + + /** + Works like SafeYield() with @e onlyIfNeeded == @true except that + it allows the caller to specify a mask of events to be processed. + + See wxAppConsole::YieldFor for more info. + */ + virtual bool SafeYieldFor(wxWindow *win, long eventsToProcess); + /** Windows-only function for processing a message. This function is called from the main message loop, checking for windows that may wish to process it. @@ -857,22 +895,18 @@ void wxUninitialize(); void wxWakeUpIdle(); /** - Calls wxApp::Yield. + Calls wxAppConsole::Yield. @deprecated This function is kept only for backwards compatibility. Please use - the wxApp::Yield method instead in any new code. + the wxAppConsole::Yield method instead in any new code. @header{wx/app.h} */ bool wxYield(); /** - This function is similar to wxYield(), except that it disables the user - input to all program windows before calling wxYield() and re-enables it - again afterwards. If @a win is not @NULL, this window will remain enabled, - allowing the implementation of some limited user interaction. - Returns the result of the call to ::wxYield. + Calls wxApp::SafeYield. @header{wx/app.h} */ diff --git a/interface/wx/event.h b/interface/wx/event.h index 638b185ee2..f4ff7a77c5 100644 --- a/interface/wx/event.h +++ b/interface/wx/event.h @@ -7,6 +7,64 @@ // Licence: wxWindows license ///////////////////////////////////////////////////////////////////////////// +/** + The predefined constants for the number of times we propagate event + upwards window child-parent chain. +*/ +enum wxEventPropagation +{ + /// don't propagate it at all + wxEVENT_PROPAGATE_NONE = 0, + + /// propagate it until it is processed + wxEVENT_PROPAGATE_MAX = INT_MAX +}; + +/** + The different categories for a wxEvent; see wxEvent::GetEventCategory. + + @note They are used as OR-combinable flags by wxApp::Yield. +*/ +enum wxEventCategory +{ + /** + This is the category for those events which are generated to update + the appearance of the GUI but which (usually) do not comport data + processing, i.e. which do not provide input or output data + (e.g. size events, scroll events, etc). + They are events NOT directly generated by the user's input devices. + */ + wxEVT_CATEGORY_UI = 1, + + /** + This category groups those events which are generated directly from the + user through input devices like mouse and keyboard and usually result in + data to be processed from the application + (e.g. mouse clicks, key presses, etc). + */ + wxEVT_CATEGORY_USER_INPUT = 2, + + /// This category is for wxSocketEvent + wxEVT_CATEGORY_SOCKET = 4, + + /// This category is for wxTimerEvent + wxEVT_CATEGORY_TIMER = 8, + + /** + This category is for any event used to send notifications from the + secondary threads to the main one or in general for notifications among + different threads (which may or may not be user-generated). + */ + wxEVT_CATEGORY_THREAD = 16, + + /** + This mask is used in wxApp::Yield to specify that all event categories should + be processed. + */ + wxEVT_CATEGORY_ALL = + wxEVT_CATEGORY_UI|wxEVT_CATEGORY_USER_INPUT|wxEVT_CATEGORY_SOCKET| \ + wxEVT_CATEGORY_TIMER|wxEVT_CATEGORY_THREAD +}; /** @class wxEvent @@ -87,6 +145,13 @@ public: */ wxEventType GetEventType() const; + /** + Returns a generic category for this event. + + This function is used to selectively process events in wxApp::Yield. + */ + virtual wxEventCategory GetEventCategory() const; + /** Returns the identifier associated with this event, such as a button command id. */ @@ -2463,18 +2528,45 @@ public: }; +/** + @class wxThreadEvent + This class adds some simple functionalities to wxCommandEvent coinceived + for inter-threads communications. -enum wxHelpEventOrigin + @library{wxcore} + @category{events} + + @see @ref overview_thread, wxApp::YieldFor +*/ +class wxThreadEvent : public wxCommandEvent { - wxHE_ORIGIN_UNKNOWN = -1, - wxHE_ORIGIN_KEYBOARD, +public: + /** + Constructor. + + Initializes the event type to @c wxEVT_THREAD (but you can change it + using wxEvent::SetEventType. + */ + wxThreadEvent(int id = wxID_ANY); - /** event generated by wxContextHelp or from the [?] button on - the title bar (Windows). */ - wxHE_ORIGIN_HELPBUTTON + /** + Clones this event making sure that all internal members which use + COW (only @c m_commandString for now; see @ref overview_refcount) + are unshared (see wxObject::UnShare). + */ + virtual wxEvent *Clone() const; + + /** + Returns @c wxEVT_CATEGORY_THREAD. + + This is important to avoid that calling wxApp::Yield() thread events + gets processed when this is unwanted: + */ + virtual wxEventCategory GetEventCategory() const; }; + /** @class wxHelpEvent diff --git a/samples/clipboard/clipboard.cpp b/samples/clipboard/clipboard.cpp index 3843e7ab2c..be9b0f9433 100644 --- a/samples/clipboard/clipboard.cpp +++ b/samples/clipboard/clipboard.cpp @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// // Name: clipboard.cpp -// Purpose: clipbaord wxWidgets sample +// Purpose: clipboard wxWidgets sample // Author: Robert Roebling // RCS-ID: $Id: minimal.cpp 53461 2008-05-05 23:30:33Z VZ $ // Copyright: (c) Robert Roebling @@ -9,7 +9,7 @@ // For compilers that support precompilation, includes "wx/wx.h". #include "wx/wxprec.h" - + #ifdef __BORLANDC__ #pragma hdrstop #endif @@ -27,7 +27,7 @@ #endif -#define USE_ASYNCHRONOUS_CLIPBOARD_REQUEST 1 +#define USE_ASYNCHRONOUS_CLIPBOARD_REQUEST 0 class MyApp : public wxApp { @@ -94,7 +94,7 @@ bool MyApp::OnInit() MyFrame *frame = new MyFrame("wxClipboard sample"); frame->Show(true); - + return true; } @@ -103,7 +103,7 @@ MyFrame::MyFrame(const wxString& title) { // set the frame icon SetIcon(wxICON(sample)); - + #if USE_ASYNCHRONOUS_CLIPBOARD_REQUEST m_request = Idle; m_clipboardSupportsText = false; @@ -129,7 +129,7 @@ MyFrame::MyFrame(const wxString& title) #endif // wxUSE_MENUS wxPanel *panel = new wxPanel( this, -1 ); - + wxBoxSizer *main_sizer = new wxBoxSizer( wxVERTICAL ); main_sizer->Add( new wxButton( panel, ID_Write, "Get clipboard text" ) ); m_textctrl = new wxTextCtrl( panel, ID_Text, "", wxDefaultPosition, @@ -148,7 +148,7 @@ void MyFrame::OnWriteClipboardContents(wxCommandEvent& WXUNUSED(event)) wxTheClipboard->GetData( data ); m_textctrl->Clear(); m_textctrl->SetValue( data.GetText() ); - + } wxTheClipboard->Close(); } @@ -175,7 +175,7 @@ void MyFrame::OnUpdateUI(wxUpdateUIEvent&event) } m_request = Waiting; event.Enable( m_clipboardSupportsText ); // not yet known, assume last value - } + } else if (m_request == Waiting) { event.Enable( m_clipboardSupportsText ); // not yet known, assume last value @@ -198,6 +198,7 @@ void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event)) { + wxMessageBox("Clipboard sample", "About clipboard", wxOK|wxICON_INFORMATION, this); } diff --git a/samples/thread/thread.cpp b/samples/thread/thread.cpp index ad2ca59220..2bcdbb87ea 100644 --- a/samples/thread/thread.cpp +++ b/samples/thread/thread.cpp @@ -105,7 +105,7 @@ private: void OnResumeThread(wxCommandEvent& event); void OnStartWorker(wxCommandEvent& event); - void OnWorkerEvent(wxCommandEvent& event); + void OnWorkerEvent(wxThreadEvent& event); void OnUpdateWorker(wxUpdateUIEvent& event); void OnExecMain(wxCommandEvent& event); @@ -169,7 +169,7 @@ enum THREAD_SHOWCPUS, - WORKER_EVENT // this one gets sent from the worker thread + WORKER_EVENT = wxID_HIGHEST+1 // this one gets sent from the worker thread }; // ---------------------------------------------------------------------------- @@ -292,8 +292,22 @@ void MyWorkerThread::OnExit() { } +#define TEST_YIELD_RACE_CONDITION 1 + void *MyWorkerThread::Entry() { +#if TEST_YIELD_RACE_CONDITION + if ( TestDestroy() ) + return NULL; + + wxThreadEvent event( WORKER_EVENT ); + + event.SetInt( 50 ); + wxQueueEvent( m_frame, new wxThreadEvent(event) ); + + event.SetInt(-1); + wxQueueEvent( m_frame, new wxThreadEvent(event) ); +#else for ( m_count = 0; !m_frame->Cancelled() && (m_count < 100); m_count++ ) { // check if we were asked to exit @@ -301,18 +315,19 @@ void *MyWorkerThread::Entry() break; // create any type of command event here - wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT ); + wxThreadEvent event( WORKER_EVENT ); event.SetInt( m_count ); // send in a thread-safe way - wxQueueEvent( m_frame, new wxCommandEvent(event) ); + wxQueueEvent( m_frame, new wxThreadEvent(event) ); wxMilliSleep(200); } - wxCommandEvent event( wxEVT_COMMAND_MENU_SELECTED, WORKER_EVENT ); + wxThreadEvent event( WORKER_EVENT ); event.SetInt(-1); // that's all - wxQueueEvent( m_frame, new wxCommandEvent(event) ); + wxQueueEvent( m_frame, new wxThreadEvent(event) ); +#endif return NULL; } @@ -360,7 +375,8 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_UPDATE_UI(THREAD_START_WORKER, MyFrame::OnUpdateWorker) EVT_MENU(THREAD_START_WORKER, MyFrame::OnStartWorker) - EVT_MENU(WORKER_EVENT, MyFrame::OnWorkerEvent) + + EVT_THREAD(WORKER_EVENT, MyFrame::OnWorkerEvent) EVT_IDLE(MyFrame::OnIdle) END_EVENT_TABLE() @@ -442,7 +458,6 @@ MyFrame::MyFrame(wxFrame *frame, const wxString& title, m_txtctrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxPoint(0, 0), wxSize(0, 0), wxTE_MULTILINE | wxTE_READONLY); - } MyFrame::~MyFrame() @@ -758,7 +773,7 @@ void MyFrame::OnStartWorker(wxCommandEvent& WXUNUSED(event)) thread->Run(); } -void MyFrame::OnWorkerEvent(wxCommandEvent& event) +void MyFrame::OnWorkerEvent(wxThreadEvent& event) { int n = event.GetInt(); if ( n == -1 ) diff --git a/src/cocoa/app.mm b/src/cocoa/app.mm index 662859f091..7cde079e14 100644 --- a/src/cocoa/app.mm +++ b/src/cocoa/app.mm @@ -295,7 +295,7 @@ void wxApp::Exit() } // Yield to other processes -bool wxApp::Yield(bool onlyIfNeeded) +bool wxApp::DoYield(bool onlyIfNeeded, long eventsToProcess) { #if wxUSE_LOG // disable log flushing from here because a call to wxYield() shouldn't @@ -314,10 +314,13 @@ bool wxApp::Yield(bool onlyIfNeeded) } m_isInsideYield = true; + m_eventsToProcessInsideYield = eventsToProcess; // Run the event loop until it is out of events while(1) { + // TODO: implement event filtering using the eventsToProcess mask + wxAutoNSAutoreleasePool pool; /* NOTE: It may be better to use something like NSEventTrackingRunLoopMode since we don't necessarily want all diff --git a/src/common/appbase.cpp b/src/common/appbase.cpp index 30c7f093b2..d159c9bdd8 100644 --- a/src/common/appbase.cpp +++ b/src/common/appbase.cpp @@ -164,6 +164,7 @@ bool wxAppConsoleBase::Initialize(int& WXUNUSED(argc), wxChar **argv) #if wxUSE_THREADS wxHandlersWithPendingEventsLocker = new wxCriticalSection; + wxHandlersWithPendingDelayedEvents = new wxList; #endif #ifndef __WXPALMOS__ @@ -193,6 +194,9 @@ void wxAppConsoleBase::CleanUp() delete wxHandlersWithPendingEvents; wxHandlersWithPendingEvents = NULL; + delete wxHandlersWithPendingDelayedEvents; + wxHandlersWithPendingDelayedEvents = NULL; + #if wxUSE_THREADS delete wxHandlersWithPendingEventsLocker; wxHandlersWithPendingEventsLocker = NULL; @@ -336,6 +340,16 @@ bool wxAppConsoleBase::HasPendingEvents() const return has; } +void wxAppConsoleBase::SuspendProcessingOfPendingEvents() +{ + wxENTER_CRIT_SECT( *wxHandlersWithPendingEventsLocker ); +} + +void wxAppConsoleBase::ResumeProcessingOfPendingEvents() +{ + wxLEAVE_CRIT_SECT( *wxHandlersWithPendingEventsLocker ); +} + /* static */ bool wxAppConsoleBase::IsMainLoopRunning() { @@ -353,6 +367,9 @@ void wxAppConsoleBase::ProcessPendingEvents() wxENTER_CRIT_SECT( *wxHandlersWithPendingEventsLocker ); + wxCHECK_RET( wxHandlersWithPendingDelayedEvents->IsEmpty(), + "this helper list should be empty" ); + if (wxHandlersWithPendingEvents) { // iterate until the list becomes empty: the handlers remove themselves @@ -360,7 +377,7 @@ void wxAppConsoleBase::ProcessPendingEvents() wxList::compatibility_iterator node = wxHandlersWithPendingEvents->GetFirst(); while (node) { - // In ProcessPendingEvents(), new handlers might be add + // In ProcessPendingEvents(), new handlers might be added // and we can safely leave the critical section here. wxLEAVE_CRIT_SECT( *wxHandlersWithPendingEventsLocker ); @@ -374,6 +391,20 @@ void wxAppConsoleBase::ProcessPendingEvents() } } + // now the wxHandlersWithPendingEvents is surely empty; however some event + // handlers may have moved themselves into wxHandlersWithPendingDelayedEvents + // because of a selective wxYield call in progress. + // Now we need to move them back to wxHandlersWithPendingEvents so the next + // call to this function has the chance of processing them: + if (!wxHandlersWithPendingDelayedEvents->IsEmpty()) + { + if (!wxHandlersWithPendingEvents) + wxHandlersWithPendingEvents = new wxList; + + WX_APPEND_LIST(wxHandlersWithPendingEvents, wxHandlersWithPendingDelayedEvents); + wxHandlersWithPendingDelayedEvents->Clear(); + } + wxLEAVE_CRIT_SECT( *wxHandlersWithPendingEventsLocker ); } diff --git a/src/common/appcmn.cpp b/src/common/appcmn.cpp index afb51d63c8..95da92def7 100644 --- a/src/common/appcmn.cpp +++ b/src/common/appcmn.cpp @@ -79,6 +79,7 @@ wxAppBase::wxAppBase() m_isActive = true; m_isInsideYield = false; + m_eventsToProcessInsideYield = wxEVT_CATEGORY_ALL; // We don't want to exit the app if the user code shows a dialog from its // OnInit() -- but this is what would happen if we set m_exitOnFrameDelete @@ -325,6 +326,26 @@ void wxAppBase::SetActive(bool active, wxWindow * WXUNUSED(lastFocus)) (void)ProcessEvent(event); } +bool wxAppBase::IsEventAllowedInsideYield(wxEventCategory cat) const +{ + return m_eventsToProcessInsideYield & cat; +} + +bool wxAppBase::SafeYield(wxWindow *win, bool onlyIfNeeded) +{ + wxWindowDisabler wd(win); + + return Yield(onlyIfNeeded); +} + +bool wxAppBase::SafeYieldFor(wxWindow *win, long eventsToProcess) +{ + wxWindowDisabler wd(win); + + return YieldFor(eventsToProcess); +} + + // ---------------------------------------------------------------------------- // idle handling // ---------------------------------------------------------------------------- diff --git a/src/common/event.cpp b/src/common/event.cpp index a3cce6f3f3..13adaf0aaa 100644 --- a/src/common/event.cpp +++ b/src/common/event.cpp @@ -65,6 +65,7 @@ #if wxUSE_GUI IMPLEMENT_DYNAMIC_CLASS(wxCommandEvent, wxEvent) + IMPLEMENT_DYNAMIC_CLASS(wxThreadEvent, wxEvent) IMPLEMENT_DYNAMIC_CLASS(wxNotifyEvent, wxCommandEvent) IMPLEMENT_DYNAMIC_CLASS(wxScrollEvent, wxCommandEvent) IMPLEMENT_DYNAMIC_CLASS(wxScrollWinEvent, wxEvent) @@ -147,6 +148,7 @@ IMPLEMENT_DYNAMIC_CLASS(wxEventTableEntryModule, wxModule) // List containing event handlers with pending events (each handler can occur // at most once here) wxList *wxHandlersWithPendingEvents = NULL; +wxList *wxHandlersWithPendingDelayedEvents = NULL; #if wxUSE_THREADS // protects wxHandlersWithPendingEvents list @@ -316,6 +318,9 @@ wxDEFINE_EVENT( wxEVT_COMMAND_ENTER, wxCommandEvent ) wxDEFINE_EVENT( wxEVT_HELP, wxHelpEvent ) wxDEFINE_EVENT( wxEVT_DETAILED_HELP, wxHelpEvent ) +// Thread event +DEFINE_EVENT_TYPE(wxEVT_COMMAND_THREAD) + #endif // wxUSE_GUI #if wxUSE_BASE @@ -350,15 +355,14 @@ wxEventFunctor::~wxEventFunctor() // ---------------------------------------------------------------------------- /* - * General wxWidgets events, covering - * all interesting things that might happen (button clicking, resizing, - * setting text in widgets, etc.). + * General wxWidgets events, covering all interesting things that might happen + * (button clicking, resizing, setting text in widgets, etc.). * * For each completely new event type, derive a new event class. * */ -wxEvent::wxEvent(int theId, wxEventType commandType ) +wxEvent::wxEvent(int theId, wxEventType commandType) { m_eventType = commandType; m_eventObject = NULL; @@ -1103,6 +1107,14 @@ wxEvtHandler::~wxEvtHandler() } //else: we weren't in this list at all, it's ok + if ( wxHandlersWithPendingDelayedEvents->DeleteObject(this) ) + { + // check that we were present only once in the list + wxASSERT_MSG( !wxHandlersWithPendingDelayedEvents->Find(this), + "Handler occurs twice in wxHandlersWithPendingDelayedEvents list" ); + } + //else: we weren't in this list at all, it's ok + #if wxUSE_THREADS if (wxHandlersWithPendingEventsLocker) wxLEAVE_CRIT_SECT(*wxHandlersWithPendingEventsLocker); @@ -1188,6 +1200,10 @@ void wxEvtHandler::QueueEvent(wxEvent *event) void wxEvtHandler::ProcessPendingEvents() { + // we need to process only a single pending event in this call because + // each call to ProcessEvent() could result in the destruction of this + // same event handler (see the comment at the end of this function) + wxENTER_CRIT_SECT( m_pendingEventsLock ); // this method is only called by wxApp if this handler does have @@ -1196,7 +1212,40 @@ void wxEvtHandler::ProcessPendingEvents() "should have pending events if called" ); wxList::compatibility_iterator node = m_pendingEvents->GetFirst(); - wxEventPtr event(static_cast(node->GetData())); + wxEvent* pEvent = static_cast(node->GetData()); + + // find the first event which can be processed now: + if (wxTheApp && wxTheApp->IsYielding()) + { + while (node && pEvent && !wxTheApp->IsEventAllowedInsideYield(pEvent->GetEventCategory())) + { + node = node->GetNext(); + pEvent = node ? static_cast(node->GetData()) : NULL; + } + + if (!node) + { + // all our events are NOT processable now... signal this: +#if wxUSE_THREADS + if (wxHandlersWithPendingEventsLocker) + wxENTER_CRIT_SECT(*wxHandlersWithPendingEventsLocker); +#endif + // move us from the list of handlers with processable pending events + // to the list of handlers with pending events which needs to be processed later + wxHandlersWithPendingEvents->DeleteObject(this); + if ( !wxHandlersWithPendingDelayedEvents->Find(this) ) + wxHandlersWithPendingDelayedEvents->Append(this); +#if wxUSE_THREADS + if (wxHandlersWithPendingEventsLocker) + wxLEAVE_CRIT_SECT(*wxHandlersWithPendingEventsLocker); +#endif + wxLEAVE_CRIT_SECT( m_pendingEventsLock ); + + return; + } + } + + wxEventPtr event(pEvent); // it's important we remove event from list before processing it, else a // nested event loop, for example from a modal dialog, might process the @@ -1306,6 +1355,27 @@ bool wxEvtHandler::ProcessEvent(wxEvent& event) { if ( wxTheApp ) { +/* + CANNOT ENABLE: ProcessEvent() must always immediately process the event! + + if (wxTheApp->IsYielding() && + !wxTheApp->IsEventAllowedInsideYield(event.GetEventCategory())) + { + wxEvent* queuedEv = event.Clone(); + + // queue this event rather than processing it now + QueueEvent(queuedEv); + // the wxWakeUpIdle call shouldn't probably be done + // in this context (there's wxYield in the call stack) + + return true; + // it's not completely true that the event was processed; + // but we cannot even say it was skipped or discarded... + } + //else: either we're not inside a wxYield() call or if we are, + // we can process this event immediately. +*/ + int rc = wxTheApp->FilterEvent(event); if ( rc != -1 ) { diff --git a/src/common/init.cpp b/src/common/init.cpp index 91e0abaf70..2450ec4033 100644 --- a/src/common/init.cpp +++ b/src/common/init.cpp @@ -63,6 +63,7 @@ public: wxDummyConsoleApp() { } virtual int OnRun() { wxFAIL_MSG( _T("unreachable code") ); return 0; } + virtual bool DoYield(bool, long) { return true; } DECLARE_NO_COPY_CLASS(wxDummyConsoleApp) }; diff --git a/src/dfb/app.cpp b/src/dfb/app.cpp index c95912e538..141bd1a031 100644 --- a/src/dfb/app.cpp +++ b/src/dfb/app.cpp @@ -163,8 +163,7 @@ void wxApp::WakeUpIdle() #endif } - -bool wxApp::Yield(bool onlyIfNeeded) +bool wxApp::DoYield(bool onlyIfNeeded, long eventsToProcess) { #if wxUSE_THREADS if ( !wxThread::IsMain() ) @@ -182,6 +181,7 @@ bool wxApp::Yield(bool onlyIfNeeded) } m_isInsideYield = true; + m_eventsToProcessInsideYield = eventsToProcess; #if wxUSE_LOG wxLog::Suspend(); diff --git a/src/dfb/evtloop.cpp b/src/dfb/evtloop.cpp index 89d0f75388..d18b73a251 100644 --- a/src/dfb/evtloop.cpp +++ b/src/dfb/evtloop.cpp @@ -204,6 +204,8 @@ wxIDirectFBEventBufferPtr wxGUIEventLoop::GetDirectFBEventBuffer() void wxGUIEventLoop::Yield() { + // TODO: implement event filtering using the eventsToProcess mask + // process all pending events: while ( Pending() ) Dispatch(); diff --git a/src/generic/progdlgg.cpp b/src/generic/progdlgg.cpp index 2f77e37bf0..114f32071c 100644 --- a/src/generic/progdlgg.cpp +++ b/src/generic/progdlgg.cpp @@ -401,7 +401,7 @@ wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip) m_msg->SetLabel(_("Done.")); } - wxYieldIfNeeded(); + wxTheApp->YieldFor(wxEVT_CATEGORY_UI); (void)ShowModal(); } @@ -451,7 +451,7 @@ bool wxProgressDialog::DoAfterUpdate(bool *skip) { // we have to yield because not only we want to update the display but // also to process the clicks on the cancel and skip buttons - wxYieldIfNeeded(); + wxTheApp->YieldFor(wxEVT_CATEGORY_UI|wxEVT_CATEGORY_USER_INPUT); Update(); @@ -670,7 +670,7 @@ void wxProgressDialog::UpdateMessage(const wxString &newmsg) Fit(); // adapt to the new label size - wxYieldIfNeeded() ; + wxTheApp->YieldFor(wxEVT_CATEGORY_UI); } } diff --git a/src/gtk/app.cpp b/src/gtk/app.cpp index e982ec4ab0..a7c984dd3b 100644 --- a/src/gtk/app.cpp +++ b/src/gtk/app.cpp @@ -49,12 +49,13 @@ //----------------------------------------------------------------------------- static GtkWidget *gs_RootWindow = NULL; +static wxArrayPtrVoid g_arrGdkEvents; //----------------------------------------------------------------------------- // wxYield //----------------------------------------------------------------------------- -bool wxApp::Yield(bool onlyIfNeeded) +bool wxApp::DoYield(bool onlyIfNeeded, long eventsToProcess) { if ( m_isInsideYield ) { @@ -75,6 +76,7 @@ bool wxApp::Yield(bool onlyIfNeeded) #endif // wxUSE_THREADS m_isInsideYield = true; + m_eventsToProcessInsideYield = eventsToProcess; #if wxUSE_LOG // disable log flushing from here because a call to wxYield() shouldn't @@ -82,16 +84,116 @@ bool wxApp::Yield(bool onlyIfNeeded) wxLog::Suspend(); #endif - while (EventsPending()) - gtk_main_iteration(); + // NOTE: gtk_main_iteration() doesn't allow us to filter events, so we + // rather use gtk_main_do_event() after filtering the events at + // GDK level - // It's necessary to call ProcessIdle() to update the frames sizes which - // might have been changed (it also will update other things set from - // OnUpdateUI() which is a nice (and desired) side effect). But we - // call ProcessIdle() only once since this is not meant for longish - // background jobs (controlled by wxIdleEvent::RequestMore() and the - // return value of Processidle(). - ProcessIdle(); + GdkDisplay* disp = gtk_widget_get_display(gs_RootWindow); + + // gdk_display_get_event() will transform X11 events into GDK events + // and will queue all of them in the display (private) structure; + // finally it will "unqueue" the last one and return it to us + GdkEvent* event = gdk_display_get_event(disp); + while (event) + { + // categorize the GDK event according to wxEventCategory. + // See http://library.gnome.org/devel/gdk/unstable/gdk-Events.html#GdkEventType + // for more info. + + wxEventCategory cat = wxEVT_CATEGORY_UNKNOWN; + switch (event->type) + { + case GDK_SELECTION_REQUEST: + case GDK_SELECTION_NOTIFY: + case GDK_SELECTION_CLEAR: + case GDK_OWNER_CHANGE: + cat = wxEVT_CATEGORY_CLIPBOARD; + break; + + + case GDK_KEY_PRESS: + case GDK_KEY_RELEASE: + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + case GDK_SCROLL: // generated from mouse buttons + case GDK_CLIENT_EVENT: + cat = wxEVT_CATEGORY_USER_INPUT; + break; + + + case GDK_PROXIMITY_IN: + case GDK_PROXIMITY_OUT: + + case GDK_MOTION_NOTIFY: + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + case GDK_VISIBILITY_NOTIFY: + case GDK_PROPERTY_NOTIFY: + + case GDK_FOCUS_CHANGE: + case GDK_CONFIGURE: + case GDK_WINDOW_STATE: + case GDK_SETTING: + case GDK_DELETE: + case GDK_DESTROY: + + case GDK_EXPOSE: + case GDK_NO_EXPOSE: + case GDK_MAP: + case GDK_UNMAP: + //case GDK_DAMAGE: + + case GDK_DRAG_ENTER: + case GDK_DRAG_LEAVE: + case GDK_DRAG_MOTION: + case GDK_DRAG_STATUS: + case GDK_DROP_START: + case GDK_DROP_FINISHED: + case GDK_GRAB_BROKEN: + cat = wxEVT_CATEGORY_UI; + break; + + default: + cat = wxEVT_CATEGORY_UNKNOWN; + break; + } + + if (eventsToProcess & cat) + gtk_main_do_event(event); // process it now + else + g_arrGdkEvents.Add(event); // process it later + + // get next event + event = gdk_display_get_event(disp); + } + + if (eventsToProcess != wxEVT_CATEGORY_CLIPBOARD) + { + // It's necessary to call ProcessIdle() to update the frames sizes which + // might have been changed (it also will update other things set from + // OnUpdateUI() which is a nice (and desired) side effect). But we + // call ProcessIdle() only once since this is not meant for longish + // background jobs (controlled by wxIdleEvent::RequestMore() and the + // return value of Processidle(). + ProcessIdle(); // ProcessIdle() also calls ProcessPendingEvents() + } + //else: if we are inside ~wxClipboardSync() and we call ProcessIdle() and + // the user app contains an UI update handler which calls wxClipboard::IsSupported, + // then we fall into a never-ending loop... + + // put all unprocessed GDK events back in the queue + for (size_t i=0; iYieldFor(wxEVT_CATEGORY_CLIPBOARD); } // this method must be called by GTK+ callbacks to indicate that we got the @@ -359,10 +359,10 @@ async_targets_selection_received( GtkWidget *WXUNUSED(widget), if (!clipboard->m_sink) return; - + wxClipboardEvent *event = new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED); event->SetEventObject( clipboard ); - + if ( !selection_data || selection_data->length <= 0 ) { clipboard->m_sink->QueueEvent( event ); @@ -404,7 +404,7 @@ async_targets_selection_received( GtkWidget *WXUNUSED(widget), event->AddFormat( format ); } - + clipboard->m_sink->QueueEvent( event ); clipboard->m_sink.Release(); } @@ -438,7 +438,7 @@ wxClipboard::wxClipboard() g_signal_connect (m_targetsWidget, "selection_received", G_CALLBACK (targets_selection_received), this); - // we use m_targetsWidgetAsync to query what formats asynchronously + // we use m_targetsWidgetAsync to query what formats are available asynchronously m_targetsWidgetAsync = gtk_window_new( GTK_WINDOW_POPUP ); gtk_widget_realize( m_targetsWidgetAsync ); @@ -525,15 +525,15 @@ bool wxClipboard::IsSupportedAsync(wxEvtHandler *sink) { if (m_sink.get()) return false; // currently busy, come back later - + wxCHECK_MSG( sink, false, wxT("no sink given") ); - + m_sink = sink; gtk_selection_convert( m_targetsWidgetAsync, GTKGetClipboardAtom(), g_targetsAtom, (guint32) GDK_CURRENT_TIME ); - + return true; } diff --git a/src/gtk1/app.cpp b/src/gtk1/app.cpp index 449a0f2a0b..17aa402900 100644 --- a/src/gtk1/app.cpp +++ b/src/gtk1/app.cpp @@ -103,7 +103,7 @@ static wxMutex gs_idleTagsMutex; // wxYield //----------------------------------------------------------------------------- -bool wxApp::Yield(bool onlyIfNeeded) +bool wxApp::DoYield(bool onlyIfNeeded, long eventsToProcess) { if ( m_isInsideYield ) { @@ -124,6 +124,7 @@ bool wxApp::Yield(bool onlyIfNeeded) #endif // wxUSE_THREADS m_isInsideYield = true; + m_eventsToProcessInsideYield = eventsToProcess; // We need to remove idle callbacks or the loop will // never finish. @@ -135,6 +136,7 @@ bool wxApp::Yield(bool onlyIfNeeded) wxLog::Suspend(); #endif + // TODO: implement event filtering using the eventsToProcess mask while (gtk_events_pending()) gtk_main_iteration(); diff --git a/src/mgl/app.cpp b/src/mgl/app.cpp index 0c0a1ac7d4..a43beb61f1 100644 --- a/src/mgl/app.cpp +++ b/src/mgl/app.cpp @@ -48,7 +48,7 @@ void wxApp::Exit() // wxYield //----------------------------------------------------------------------------- -bool wxApp::Yield(bool onlyIfNeeded) +bool wxApp::DoYield(bool onlyIfNeeded, long eventsToProcess) { if ( m_isInsideYield ) { @@ -69,12 +69,15 @@ bool wxApp::Yield(bool onlyIfNeeded) #endif // wxUSE_THREADS m_isInsideYield = true; + m_eventsToProcessInsideYield = eventsToProcess; wxLog::Suspend(); wxEventLoopBase * const eventLoop = wxEventLoop::GetActive(); if ( eventLoop ) { + // TODO: implement event filtering using the eventsToProcess mask + while (eventLoop->Pending()) eventLoop->Dispatch(); } diff --git a/src/motif/app.cpp b/src/motif/app.cpp index 6f85268d75..11d8129d70 100644 --- a/src/motif/app.cpp +++ b/src/motif/app.cpp @@ -470,7 +470,7 @@ void wxApp::SetTopLevelRealizedWidget(WXDisplay* display, WXWidget widget) // Yield to other processes -bool wxApp::Yield(bool onlyIfNeeded) +bool wxApp::DoYield(bool onlyIfNeeded, long eventsToProcess) { if ( m_isInsideYield ) { @@ -483,9 +483,11 @@ bool wxApp::Yield(bool onlyIfNeeded) } m_isInsideYield = true; + m_eventsToProcessInsideYield = eventsToProcess; wxEventLoopGuarantor dummyLoopIfNeeded; while (wxTheApp && wxTheApp->Pending()) + // TODO: implement event filtering using the eventsToProcess mask wxTheApp->Dispatch(); m_isInsideYield = false; diff --git a/src/msw/app.cpp b/src/msw/app.cpp index eccd8787f4..d15abf2186 100644 --- a/src/msw/app.cpp +++ b/src/msw/app.cpp @@ -1015,7 +1015,14 @@ int wxApp::GetShell32Version() // Yield to incoming messages // ---------------------------------------------------------------------------- -bool wxApp::Yield(bool onlyIfNeeded) +WX_DECLARE_OBJARRAY(MSG, wxMSGArray); + +#include +WX_DEFINE_OBJARRAY(wxMSGArray); + +static wxMSGArray g_arrMSG; + +bool wxApp::DoYield(bool onlyIfNeeded, long eventsToProcess) { if ( m_isInsideYield ) { @@ -1029,8 +1036,9 @@ bool wxApp::Yield(bool onlyIfNeeded) // set the flag and don't forget to reset it before returning m_isInsideYield = true; - wxON_BLOCK_EXIT_SET(m_isInsideYield, false); + m_eventsToProcessInsideYield = eventsToProcess; + wxON_BLOCK_EXIT_SET(m_isInsideYield, false); #if wxUSE_LOG // disable log flushing from here because a call to wxYield() shouldn't @@ -1041,7 +1049,6 @@ bool wxApp::Yield(bool onlyIfNeeded) wxON_BLOCK_EXIT0(wxLog::Resume); #endif // wxUSE_LOG - // we don't want to process WM_QUIT from here - it should be processed in // the main event loop in order to stop it wxEventLoopGuarantor dummyLoopIfNeeded; @@ -1053,13 +1060,105 @@ bool wxApp::Yield(bool onlyIfNeeded) wxMutexGuiLeaveOrEnter(); #endif // wxUSE_THREADS - if ( !wxTheApp->Dispatch() ) - break; + wxEventCategory cat; + switch (msg.message) + { + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCLBUTTONDBLCLK: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + case WM_NCRBUTTONDBLCLK: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONUP: + case WM_NCMBUTTONDBLCLK: + + case WM_KEYFIRST: + case WM_KEYDOWN: + case WM_KEYUP: + case WM_CHAR: + case WM_DEADCHAR: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_SYSCHAR: + case WM_SYSDEADCHAR: + case WM_KEYLAST: + case WM_HOTKEY: + case WM_IME_STARTCOMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_COMPOSITION: + case WM_IME_KEYLAST: + case WM_COMMAND: + case WM_SYSCOMMAND: + + case WM_IME_SETCONTEXT: + case WM_IME_NOTIFY: + case WM_IME_CONTROL: + case WM_IME_COMPOSITIONFULL: + case WM_IME_SELECT: + case WM_IME_CHAR: + case WM_IME_KEYDOWN: + case WM_IME_KEYUP: + + case WM_MOUSEHOVER: + case WM_NCMOUSELEAVE: + case WM_MOUSELEAVE: + + case WM_CUT: + case WM_COPY: + case WM_PASTE: + case WM_CLEAR: + case WM_UNDO: + + case WM_MOUSEFIRST: + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_MOUSELAST: + case WM_MOUSEWHEEL: + cat = wxEVT_CATEGORY_USER_INPUT; + + case WM_TIMER: + cat = wxEVT_CATEGORY_TIMER; + + default: + // there are too many of these types of messages to handle them in this switch + cat = wxEVT_CATEGORY_UI; + } + + if (cat & eventsToProcess) + { + if ( !wxTheApp->Dispatch() ) + break; + } + else + { + // remove the message and store it + PeekMessage(&msg, (HWND)0, 0, 0, PM_REMOVE) + g_arrMSG.Add(msg); + } } // if there are pending events, we must process them. ProcessPendingEvents(); + // put back unprocessed events in the queue + DWORD id = GetCurrentThreadId(); + for (size_t i=0; iDispatch(); + // TODO: implement event filtering using the eventsToProcess mask while (wxTheApp && wxTheApp->Pending()) wxTheApp->Dispatch(); -- 2.45.2