implement the wxGTK selective yield with a different approach: rather than getting...
[wxWidgets.git] / src / gtk / evtloop.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/evtloop.cpp
3 // Purpose: implements wxEventLoop for GTK+
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 10.07.01
7 // RCS-ID: $Id$
8 // Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // License: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26
27 #include "wx/evtloop.h"
28
29 #ifndef WX_PRECOMP
30 #include "wx/app.h"
31 #include "wx/log.h"
32 #endif // WX_PRECOMP
33
34 #include <gtk/gtk.h>
35
36 // ============================================================================
37 // wxEventLoop implementation
38 // ============================================================================
39
40 extern GtkWidget *wxGetRootWindow();
41
42 // ----------------------------------------------------------------------------
43 // wxEventLoop running and exiting
44 // ----------------------------------------------------------------------------
45
46 wxGUIEventLoop::wxGUIEventLoop()
47 {
48 m_exitcode = 0;
49 }
50
51 int wxGUIEventLoop::Run()
52 {
53 // event loops are not recursive, you need to create another loop!
54 wxCHECK_MSG( !IsRunning(), -1, "can't reenter a message loop" );
55
56 wxEventLoopActivator activate(this);
57
58 gtk_main();
59
60 OnExit();
61
62 return m_exitcode;
63 }
64
65 void wxGUIEventLoop::Exit(int rc)
66 {
67 wxCHECK_RET( IsRunning(), "can't call Exit() if not running" );
68
69 m_exitcode = rc;
70
71 gtk_main_quit();
72 }
73
74 void wxGUIEventLoop::WakeUp()
75 {
76 // TODO: idle events handling should really be done by wxEventLoop itself
77 // but for now it's completely in gtk/app.cpp so just call there when
78 // we have wxTheApp and hope that it doesn't matter that we do
79 // nothing when we don't...
80 if ( wxTheApp )
81 wxTheApp->WakeUpIdle();
82 }
83
84 // ----------------------------------------------------------------------------
85 // wxEventLoop message processing dispatching
86 // ----------------------------------------------------------------------------
87
88 bool wxGUIEventLoop::Pending() const
89 {
90 if ( wxTheApp )
91 {
92 // this avoids false positives from our idle source
93 return wxTheApp->EventsPending();
94 }
95
96 return gtk_events_pending() != 0;
97 }
98
99 bool wxGUIEventLoop::Dispatch()
100 {
101 wxCHECK_MSG( IsRunning(), false, _T("can't call Dispatch() if not running") );
102
103 // gtk_main_iteration() returns TRUE only if gtk_main_quit() was called
104 return !gtk_main_iteration();
105 }
106
107 extern "C" {
108 static gboolean wx_event_loop_timeout(void* data)
109 {
110 bool* expired = static_cast<bool*>(data);
111 *expired = true;
112
113 // return FALSE to remove this timeout
114 return FALSE;
115 }
116 }
117
118 int wxGUIEventLoop::DispatchTimeout(unsigned long timeout)
119 {
120 bool expired = false;
121 const unsigned id = g_timeout_add(timeout, wx_event_loop_timeout, &expired);
122 bool quit = gtk_main_iteration() != 0;
123
124 if ( expired )
125 return -1;
126
127 g_source_remove(id);
128
129 return !quit;
130 }
131
132 //-----------------------------------------------------------------------------
133 // YieldFor
134 //-----------------------------------------------------------------------------
135
136 static void wxgtk_main_do_event(GdkEvent *event, wxGUIEventLoop* evtloop)
137 {
138 // categorize the GDK event according to wxEventCategory.
139 // See http://library.gnome.org/devel/gdk/unstable/gdk-Events.html#GdkEventType
140 // for more info.
141
142 wxEventCategory cat = wxEVT_CATEGORY_UNKNOWN;
143 switch (event->type)
144 {
145 case GDK_SELECTION_REQUEST:
146 case GDK_SELECTION_NOTIFY:
147 case GDK_SELECTION_CLEAR:
148 case GDK_OWNER_CHANGE:
149 cat = wxEVT_CATEGORY_CLIPBOARD;
150 break;
151
152
153 case GDK_KEY_PRESS:
154 case GDK_KEY_RELEASE:
155 case GDK_BUTTON_PRESS:
156 case GDK_2BUTTON_PRESS:
157 case GDK_3BUTTON_PRESS:
158 case GDK_BUTTON_RELEASE:
159 case GDK_SCROLL: // generated from mouse buttons
160 case GDK_CLIENT_EVENT:
161 cat = wxEVT_CATEGORY_USER_INPUT;
162 break;
163
164 case GDK_PROXIMITY_IN:
165 case GDK_PROXIMITY_OUT:
166
167 case GDK_MOTION_NOTIFY:
168 case GDK_ENTER_NOTIFY:
169 case GDK_LEAVE_NOTIFY:
170 case GDK_VISIBILITY_NOTIFY:
171 case GDK_PROPERTY_NOTIFY:
172
173 case GDK_FOCUS_CHANGE:
174 case GDK_CONFIGURE:
175 case GDK_WINDOW_STATE:
176 case GDK_SETTING:
177 case GDK_DELETE:
178 case GDK_DESTROY:
179
180 case GDK_EXPOSE:
181 case GDK_NO_EXPOSE:
182 case GDK_MAP:
183 case GDK_UNMAP:
184
185 case GDK_DRAG_ENTER:
186 case GDK_DRAG_LEAVE:
187 case GDK_DRAG_MOTION:
188 case GDK_DRAG_STATUS:
189 case GDK_DROP_START:
190 case GDK_DROP_FINISHED:
191 case GDK_GRAB_BROKEN:
192 cat = wxEVT_CATEGORY_UI;
193 break;
194
195 default:
196 cat = wxEVT_CATEGORY_UNKNOWN;
197 break;
198 }
199
200 // is this event allowed now?
201 if (evtloop->IsEventAllowedInsideYield(cat))
202 gtk_main_do_event(event); // process it now
203 else if (event->type != GDK_NOTHING)
204 evtloop->StoreGdkEventForLaterProcessing(gdk_event_copy(event));
205 // process it later (but make a copy; the caller will free the event pointer)
206 }
207
208 bool wxGUIEventLoop::YieldFor(long eventsToProcess)
209 {
210 #if wxUSE_THREADS
211 if ( !wxThread::IsMain() )
212 {
213 // can't call gtk_main_iteration() from other threads like this
214 return true;
215 }
216 #endif // wxUSE_THREADS
217
218 m_isInsideYield = true;
219 m_eventsToProcessInsideYield = eventsToProcess;
220
221 #if wxUSE_LOG
222 // disable log flushing from here because a call to wxYield() shouldn't
223 // normally result in message boxes popping up &c
224 wxLog::Suspend();
225 #endif
226
227 // temporarily replace the global GDK event handler with our function, which
228 // categorizes the events and using m_eventsToProcessInsideYield decides
229 // if an event should be processed immediately or not
230 // NOTE: this approach is better than using gdk_display_get_event() because
231 // gtk_main_iteration() does more than just calling gdk_display_get_event()
232 // and then call gtk_main_do_event()!
233 // In particular in this way we also process input from sources like
234 // GIOChannels (this is needed for e.g. wxGUIAppTraits::WaitForChild).
235 gdk_event_handler_set ((GdkEventFunc)wxgtk_main_do_event, this, NULL);
236 while (Pending()) // avoid false positives from our idle source
237 gtk_main_iteration();
238 gdk_event_handler_set ((GdkEventFunc)gtk_main_do_event, NULL, NULL);
239
240 if (eventsToProcess != wxEVT_CATEGORY_CLIPBOARD)
241 {
242 // It's necessary to call ProcessIdle() to update the frames sizes which
243 // might have been changed (it also will update other things set from
244 // OnUpdateUI() which is a nice (and desired) side effect). But we
245 // call ProcessIdle() only once since this is not meant for longish
246 // background jobs (controlled by wxIdleEvent::RequestMore() and the
247 // return value of Processidle().
248 ProcessIdle(); // ProcessIdle() also calls ProcessPendingEvents()
249 }
250 //else: if we are inside ~wxClipboardSync() and we call ProcessIdle() and
251 // the user app contains an UI update handler which calls wxClipboard::IsSupported,
252 // then we fall into a never-ending loop...
253
254 // put all unprocessed GDK events back in the queue
255 GdkDisplay* disp = gtk_widget_get_display(wxGetRootWindow());
256 for (size_t i=0; i<m_arrGdkEvents.GetCount(); i++)
257 {
258 GdkEvent* ev = (GdkEvent*)m_arrGdkEvents[i];
259
260 // NOTE: gdk_display_put_event makes a copy of the event passed to it
261 gdk_display_put_event(disp, ev);
262 gdk_event_free(ev);
263 }
264
265 m_arrGdkEvents.Clear();
266
267 #if wxUSE_LOG
268 // let the logs be flashed again
269 wxLog::Resume();
270 #endif
271
272 m_isInsideYield = false;
273
274 return true;
275 }