prevent infinite loop if gtk_menu_popup() fails, fixes #15387
[wxWidgets.git] / src / gtk / anybutton.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/anybutton.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Created: 1998-05-20 (extracted from button.cpp)
6 // Copyright: (c) 1998 Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #ifdef wxHAS_ANY_BUTTON
14
15 #ifndef WX_PRECOMP
16 #include "wx/anybutton.h"
17 #endif
18
19 #include "wx/stockitem.h"
20
21 #include <gtk/gtk.h>
22 #include "wx/gtk/private/gtk2-compat.h"
23
24 // ----------------------------------------------------------------------------
25 // GTK callbacks
26 // ----------------------------------------------------------------------------
27
28 extern "C"
29 {
30
31 static void
32 wxgtk_button_enter_callback(GtkWidget *WXUNUSED(widget), wxAnyButton *button)
33 {
34 if ( button->GTKShouldIgnoreEvent() )
35 return;
36
37 button->GTKMouseEnters();
38 }
39
40 static void
41 wxgtk_button_leave_callback(GtkWidget *WXUNUSED(widget), wxAnyButton *button)
42 {
43 if ( button->GTKShouldIgnoreEvent() )
44 return;
45
46 button->GTKMouseLeaves();
47 }
48
49 static void
50 wxgtk_button_press_callback(GtkWidget *WXUNUSED(widget), wxAnyButton *button)
51 {
52 if ( button->GTKShouldIgnoreEvent() )
53 return;
54
55 button->GTKPressed();
56 }
57
58 static void
59 wxgtk_button_released_callback(GtkWidget *WXUNUSED(widget), wxAnyButton *button)
60 {
61 if ( button->GTKShouldIgnoreEvent() )
62 return;
63
64 button->GTKReleased();
65 }
66
67 } // extern "C"
68
69 //-----------------------------------------------------------------------------
70 // wxAnyButton
71 //-----------------------------------------------------------------------------
72
73 bool wxAnyButton::Enable( bool enable )
74 {
75 if (!base_type::Enable(enable))
76 return false;
77
78 gtk_widget_set_sensitive(gtk_bin_get_child(GTK_BIN(m_widget)), enable);
79
80 if (enable)
81 GTKFixSensitivity();
82
83 GTKUpdateBitmap();
84
85 return true;
86 }
87
88 GdkWindow *wxAnyButton::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
89 {
90 return gtk_button_get_event_window(GTK_BUTTON(m_widget));
91 }
92
93 // static
94 wxVisualAttributes
95 wxAnyButton::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
96 {
97 return GetDefaultAttributesFromGTKWidget(gtk_button_new());
98 }
99
100 // ----------------------------------------------------------------------------
101 // bitmaps support
102 // ----------------------------------------------------------------------------
103
104 void wxAnyButton::GTKMouseEnters()
105 {
106 m_isCurrent = true;
107
108 GTKUpdateBitmap();
109 }
110
111 void wxAnyButton::GTKMouseLeaves()
112 {
113 m_isCurrent = false;
114
115 GTKUpdateBitmap();
116 }
117
118 void wxAnyButton::GTKPressed()
119 {
120 m_isPressed = true;
121
122 GTKUpdateBitmap();
123 }
124
125 void wxAnyButton::GTKReleased()
126 {
127 m_isPressed = false;
128
129 GTKUpdateBitmap();
130 }
131
132 void wxAnyButton::GTKOnFocus(wxFocusEvent& event)
133 {
134 event.Skip();
135
136 GTKUpdateBitmap();
137 }
138
139 wxAnyButton::State wxAnyButton::GTKGetCurrentState() const
140 {
141 if ( !IsThisEnabled() )
142 return m_bitmaps[State_Disabled].IsOk() ? State_Disabled : State_Normal;
143
144 if ( m_isPressed && m_bitmaps[State_Pressed].IsOk() )
145 return State_Pressed;
146
147 if ( m_isCurrent && m_bitmaps[State_Current].IsOk() )
148 return State_Current;
149
150 if ( HasFocus() && m_bitmaps[State_Focused].IsOk() )
151 return State_Focused;
152
153 return State_Normal;
154 }
155
156 void wxAnyButton::GTKUpdateBitmap()
157 {
158 // if we don't show bitmaps at all, there is nothing to update
159 if ( m_bitmaps[State_Normal].IsOk() )
160 {
161 // if we do show them, this will return a state for which we do have a
162 // valid bitmap
163 State state = GTKGetCurrentState();
164
165 GTKDoShowBitmap(m_bitmaps[state]);
166 }
167 }
168
169 void wxAnyButton::GTKDoShowBitmap(const wxBitmap& bitmap)
170 {
171 wxASSERT_MSG( bitmap.IsOk(), "invalid bitmap" );
172
173 GtkWidget *image;
174 if ( DontShowLabel() )
175 {
176 image = gtk_bin_get_child(GTK_BIN(m_widget));
177 }
178 else // have both label and bitmap
179 {
180 image = gtk_button_get_image(GTK_BUTTON(m_widget));
181 }
182
183 wxCHECK_RET( image && GTK_IS_IMAGE(image), "must have image widget" );
184
185 gtk_image_set_from_pixbuf(GTK_IMAGE(image), bitmap.GetPixbuf());
186 }
187
188 wxBitmap wxAnyButton::DoGetBitmap(State which) const
189 {
190 return m_bitmaps[which];
191 }
192
193 void wxAnyButton::DoSetBitmap(const wxBitmap& bitmap, State which)
194 {
195 switch ( which )
196 {
197 case State_Normal:
198 if ( DontShowLabel() )
199 {
200 // we only have the bitmap in this button, never remove it but
201 // do invalidate the best size when the bitmap (and presumably
202 // its size) changes
203 InvalidateBestSize();
204 }
205 // normal image is special: setting it enables images for the button and
206 // resetting it to nothing disables all of them
207 else
208 {
209 GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
210 if ( image && !bitmap.IsOk() )
211 {
212 gtk_container_remove(GTK_CONTAINER(m_widget), image);
213 }
214 else if ( !image && bitmap.IsOk() )
215 {
216 image = gtk_image_new();
217 gtk_button_set_image(GTK_BUTTON(m_widget), image);
218 }
219 else // image presence or absence didn't change
220 {
221 // don't invalidate best size below
222 break;
223 }
224
225 InvalidateBestSize();
226 }
227 break;
228
229 case State_Pressed:
230 if ( bitmap.IsOk() )
231 {
232 if ( !m_bitmaps[which].IsOk() )
233 {
234 // we need to install the callbacks to be notified about
235 // the button pressed state change
236 g_signal_connect
237 (
238 m_widget,
239 "pressed",
240 G_CALLBACK(wxgtk_button_press_callback),
241 this
242 );
243
244 g_signal_connect
245 (
246 m_widget,
247 "released",
248 G_CALLBACK(wxgtk_button_released_callback),
249 this
250 );
251 }
252 }
253 else // no valid bitmap
254 {
255 if ( m_bitmaps[which].IsOk() )
256 {
257 // we don't need to be notified about the button pressed
258 // state changes any more
259 g_signal_handlers_disconnect_by_func
260 (
261 m_widget,
262 (gpointer)wxgtk_button_press_callback,
263 this
264 );
265
266 g_signal_handlers_disconnect_by_func
267 (
268 m_widget,
269 (gpointer)wxgtk_button_released_callback,
270 this
271 );
272
273 // also make sure we don't remain stuck in pressed state
274 if ( m_isPressed )
275 {
276 m_isPressed = false;
277 GTKUpdateBitmap();
278 }
279 }
280 }
281 break;
282
283 case State_Current:
284 // the logic here is the same as above for State_Pressed: we need
285 // to connect the handlers if we must be notified about the changes
286 // in the button current state and we disconnect them when/if we
287 // don't need them any more
288 if ( bitmap.IsOk() )
289 {
290 if ( !m_bitmaps[which].IsOk() )
291 {
292 g_signal_connect
293 (
294 m_widget,
295 "enter",
296 G_CALLBACK(wxgtk_button_enter_callback),
297 this
298 );
299
300 g_signal_connect
301 (
302 m_widget,
303 "leave",
304 G_CALLBACK(wxgtk_button_leave_callback),
305 this
306 );
307 }
308 }
309 else // no valid bitmap
310 {
311 if ( m_bitmaps[which].IsOk() )
312 {
313 g_signal_handlers_disconnect_by_func
314 (
315 m_widget,
316 (gpointer)wxgtk_button_enter_callback,
317 this
318 );
319
320 g_signal_handlers_disconnect_by_func
321 (
322 m_widget,
323 (gpointer)wxgtk_button_leave_callback,
324 this
325 );
326
327 if ( m_isCurrent )
328 {
329 m_isCurrent = false;
330 GTKUpdateBitmap();
331 }
332 }
333 }
334 break;
335
336 case State_Focused:
337 if ( bitmap.IsOk() )
338 {
339 Connect(wxEVT_SET_FOCUS,
340 wxFocusEventHandler(wxAnyButton::GTKOnFocus));
341 Connect(wxEVT_KILL_FOCUS,
342 wxFocusEventHandler(wxAnyButton::GTKOnFocus));
343 }
344 else // no valid focused bitmap
345 {
346 Disconnect(wxEVT_SET_FOCUS,
347 wxFocusEventHandler(wxAnyButton::GTKOnFocus));
348 Disconnect(wxEVT_KILL_FOCUS,
349 wxFocusEventHandler(wxAnyButton::GTKOnFocus));
350 }
351 break;
352
353 default:
354 // no callbacks to connect/disconnect
355 ;
356 }
357
358 m_bitmaps[which] = bitmap;
359
360 // update the bitmap immediately if necessary, otherwise it will be done
361 // when the bitmap for the corresponding state is needed the next time by
362 // GTKUpdateBitmap()
363 if ( bitmap.IsOk() && which == GTKGetCurrentState() )
364 {
365 GTKDoShowBitmap(bitmap);
366 }
367 }
368
369 void wxAnyButton::DoSetBitmapPosition(wxDirection dir)
370 {
371 #ifdef __WXGTK210__
372 if ( !gtk_check_version(2,10,0) )
373 {
374 GtkPositionType gtkpos;
375 switch ( dir )
376 {
377 default:
378 wxFAIL_MSG( "invalid position" );
379 // fall through
380
381 case wxLEFT:
382 gtkpos = GTK_POS_LEFT;
383 break;
384
385 case wxRIGHT:
386 gtkpos = GTK_POS_RIGHT;
387 break;
388
389 case wxTOP:
390 gtkpos = GTK_POS_TOP;
391 break;
392
393 case wxBOTTOM:
394 gtkpos = GTK_POS_BOTTOM;
395 break;
396 }
397
398 gtk_button_set_image_position(GTK_BUTTON(m_widget), gtkpos);
399 InvalidateBestSize();
400 }
401 #endif // GTK+ 2.10+
402 }
403
404 #endif // wxHAS_ANY_BUTTON