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 "wx/gtk/private/wrapgtk.h"
22 #include "wx/gtk/private/image.h"
23 
24 // ----------------------------------------------------------------------------
25 // GTK callbacks
26 // ----------------------------------------------------------------------------
27 
28 extern "C"
29 {
30 
31 static void
wxgtk_button_enter_callback(GtkWidget * WXUNUSED (widget),wxAnyButton * button)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
wxgtk_button_leave_callback(GtkWidget * WXUNUSED (widget),wxAnyButton * button)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
wxgtk_button_press_callback(GtkWidget * WXUNUSED (widget),wxAnyButton * button)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
wxgtk_button_released_callback(GtkWidget * WXUNUSED (widget),wxAnyButton * button)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 
DoEnable(bool enable)73 void wxAnyButton::DoEnable(bool enable)
74 {
75     // See wxWindow::DoEnable()
76     if ( !m_widget )
77         return;
78 
79     base_type::DoEnable(enable);
80 
81     gtk_widget_set_sensitive(gtk_bin_get_child(GTK_BIN(m_widget)), enable);
82 
83     if (enable)
84         GTKFixSensitivity();
85 
86     GTKUpdateBitmap();
87 }
88 
GTKGetWindow(wxArrayGdkWindows & WXUNUSED (windows)) const89 GdkWindow *wxAnyButton::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
90 {
91     return gtk_button_get_event_window(GTK_BUTTON(m_widget));
92 }
93 
94 // static
95 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))96 wxAnyButton::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
97 {
98     return GetDefaultAttributesFromGTKWidget(gtk_button_new());
99 }
100 
101 // ----------------------------------------------------------------------------
102 // bitmaps support
103 // ----------------------------------------------------------------------------
104 
GTKMouseEnters()105 void wxAnyButton::GTKMouseEnters()
106 {
107     m_isCurrent = true;
108 
109     GTKUpdateBitmap();
110 }
111 
GTKMouseLeaves()112 void wxAnyButton::GTKMouseLeaves()
113 {
114     m_isCurrent = false;
115 
116     GTKUpdateBitmap();
117 }
118 
GTKPressed()119 void wxAnyButton::GTKPressed()
120 {
121     m_isPressed = true;
122 
123     GTKUpdateBitmap();
124 }
125 
GTKReleased()126 void wxAnyButton::GTKReleased()
127 {
128     m_isPressed = false;
129 
130     GTKUpdateBitmap();
131 }
132 
GTKOnFocus(wxFocusEvent & event)133 void wxAnyButton::GTKOnFocus(wxFocusEvent& event)
134 {
135     event.Skip();
136 
137     GTKUpdateBitmap();
138 }
139 
GTKGetCurrentBitmapState() const140 wxAnyButton::State wxAnyButton::GTKGetCurrentBitmapState() const
141 {
142     if ( !IsThisEnabled() )
143     {
144         if ( m_bitmaps[State_Disabled].IsOk() )
145             return State_Disabled;
146     }
147     else
148     {
149         if ( m_isPressed && m_bitmaps[State_Pressed].IsOk() )
150             return State_Pressed;
151 
152         if ( m_isCurrent && m_bitmaps[State_Current].IsOk() )
153             return State_Current;
154 
155         if ( HasFocus() && m_bitmaps[State_Focused].IsOk() )
156             return State_Focused;
157     }
158 
159     // Fall back on the normal state: which still might be different from
160     // State_Normal for the toggle buttons, so the check for bitmap validity is
161     // still needed.
162     const State normalState = GetNormalState();
163     if ( m_bitmaps[normalState].IsOk() )
164         return normalState;
165 
166     // And if nothing else can (or should) be used, finally fall back to the
167     // normal state which is the only one guaranteed to have a bitmap (if we're
168     // using bitmaps at all and we're only called in this case).
169     return State_Normal;
170 }
171 
GTKUpdateBitmap()172 void wxAnyButton::GTKUpdateBitmap()
173 {
174     // if we don't show bitmaps at all, there is nothing to update
175     if ( m_bitmaps[State_Normal].IsOk() )
176     {
177         // if we do show them, this will return a state for which we do have a
178         // valid bitmap
179         State state = GTKGetCurrentBitmapState();
180 
181         GTKDoShowBitmap(m_bitmaps[state]);
182     }
183 }
184 
GTKDoShowBitmap(const wxBitmap & bitmap)185 void wxAnyButton::GTKDoShowBitmap(const wxBitmap& bitmap)
186 {
187     wxCHECK_RET(bitmap.IsOk(), "invalid bitmap");
188 
189     GtkWidget* image = gtk_button_get_image(GTK_BUTTON(m_widget));
190     if (image == NULL)
191         image = gtk_bin_get_child(GTK_BIN(m_widget));
192 
193     wxCHECK_RET(GTK_IS_IMAGE(image), "must have image widget");
194 
195     WX_GTK_IMAGE(image)->Set(bitmap);
196 }
197 
DoGetBitmap(State which) const198 wxBitmap wxAnyButton::DoGetBitmap(State which) const
199 {
200     return m_bitmaps[which];
201 }
202 
DoSetBitmap(const wxBitmap & bitmap,State which)203 void wxAnyButton::DoSetBitmap(const wxBitmap& bitmap, State which)
204 {
205     switch ( which )
206     {
207         case State_Normal:
208             if ( DontShowLabel() )
209             {
210                 // we only have the bitmap in this button, never remove it but
211                 // do invalidate the best size when the bitmap (and presumably
212                 // its size) changes
213                 InvalidateBestSize();
214             }
215             // normal image is special: setting it enables images for the button and
216             // resetting it to nothing disables all of them
217             else
218             {
219                 GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
220                 if ( image && !bitmap.IsOk() )
221                 {
222                     gtk_container_remove(GTK_CONTAINER(m_widget), image);
223                 }
224                 else if ( !image && bitmap.IsOk() )
225                 {
226                     image = wxGtkImage::New(this);
227                     gtk_button_set_image(GTK_BUTTON(m_widget), image);
228 
229                     // Setting the image recreates the label, so we need to
230                     // reapply the styles to it to preserve the existing text
231                     // font and colour if they're different from defaults.
232                     GTKApplyWidgetStyle();
233                 }
234                 else // image presence or absence didn't change
235                 {
236                     // don't invalidate best size below
237                     break;
238                 }
239 
240                 InvalidateBestSize();
241             }
242             break;
243 
244         case State_Pressed:
245             if ( bitmap.IsOk() )
246             {
247                 if ( !m_bitmaps[which].IsOk() )
248                 {
249                     // we need to install the callbacks to be notified about
250                     // the button pressed state change
251                     g_signal_connect
252                     (
253                         m_widget,
254                         "pressed",
255                         G_CALLBACK(wxgtk_button_press_callback),
256                         this
257                     );
258 
259                     g_signal_connect
260                     (
261                         m_widget,
262                         "released",
263                         G_CALLBACK(wxgtk_button_released_callback),
264                         this
265                     );
266                 }
267             }
268             else // no valid bitmap
269             {
270                 if ( m_bitmaps[which].IsOk() )
271                 {
272                     // we don't need to be notified about the button pressed
273                     // state changes any more
274                     g_signal_handlers_disconnect_by_func
275                     (
276                         m_widget,
277                         (gpointer)wxgtk_button_press_callback,
278                         this
279                     );
280 
281                     g_signal_handlers_disconnect_by_func
282                     (
283                         m_widget,
284                         (gpointer)wxgtk_button_released_callback,
285                         this
286                     );
287 
288                     // also make sure we don't remain stuck in pressed state
289                     if ( m_isPressed )
290                     {
291                         m_isPressed = false;
292                         GTKUpdateBitmap();
293                     }
294                 }
295             }
296             break;
297 
298         case State_Current:
299             // the logic here is the same as above for State_Pressed: we need
300             // to connect the handlers if we must be notified about the changes
301             // in the button current state and we disconnect them when/if we
302             // don't need them any more
303             if ( bitmap.IsOk() )
304             {
305                 if ( !m_bitmaps[which].IsOk() )
306                 {
307                     g_signal_connect
308                     (
309                         m_widget,
310                         "enter",
311                         G_CALLBACK(wxgtk_button_enter_callback),
312                         this
313                     );
314 
315                     g_signal_connect
316                     (
317                         m_widget,
318                         "leave",
319                         G_CALLBACK(wxgtk_button_leave_callback),
320                         this
321                     );
322                 }
323             }
324             else // no valid bitmap
325             {
326                 if ( m_bitmaps[which].IsOk() )
327                 {
328                     g_signal_handlers_disconnect_by_func
329                     (
330                         m_widget,
331                         (gpointer)wxgtk_button_enter_callback,
332                         this
333                     );
334 
335                     g_signal_handlers_disconnect_by_func
336                     (
337                         m_widget,
338                         (gpointer)wxgtk_button_leave_callback,
339                         this
340                     );
341 
342                     if ( m_isCurrent )
343                     {
344                         m_isCurrent = false;
345                         GTKUpdateBitmap();
346                     }
347                 }
348             }
349             break;
350 
351         case State_Focused:
352             if ( bitmap.IsOk() )
353             {
354                 Bind(wxEVT_SET_FOCUS, &wxAnyButton::GTKOnFocus, this);
355                 Bind(wxEVT_KILL_FOCUS, &wxAnyButton::GTKOnFocus, this);
356             }
357             else // no valid focused bitmap
358             {
359                 Unbind(wxEVT_SET_FOCUS, &wxAnyButton::GTKOnFocus, this);
360                 Unbind(wxEVT_KILL_FOCUS, &wxAnyButton::GTKOnFocus, this);
361             }
362             break;
363 
364         default:
365             // no callbacks to connect/disconnect
366             ;
367     }
368 
369     m_bitmaps[which] = bitmap;
370 
371 #if GTK_CHECK_VERSION(3,6,0) && !defined(__WXGTK4__)
372     // Allow explicitly set bitmaps to be shown regardless of theme setting
373     if (gtk_check_version(3,6,0) == NULL && bitmap.IsOk())
374         gtk_button_set_always_show_image(GTK_BUTTON(m_widget), true);
375 #endif
376 
377     // update the bitmap immediately if necessary, otherwise it will be done
378     // when the bitmap for the corresponding state is needed the next time by
379     // GTKUpdateBitmap()
380     if ( bitmap.IsOk() && which == GTKGetCurrentBitmapState() )
381     {
382         GTKDoShowBitmap(bitmap);
383     }
384 }
385 
DoSetBitmapPosition(wxDirection dir)386 void wxAnyButton::DoSetBitmapPosition(wxDirection dir)
387 {
388 #ifdef __WXGTK210__
389     if ( wx_is_at_least_gtk2(10) )
390     {
391         GtkPositionType gtkpos;
392         switch ( dir )
393         {
394             default:
395                 wxFAIL_MSG( "invalid position" );
396                 wxFALLTHROUGH;
397 
398             case wxLEFT:
399                 gtkpos = GTK_POS_LEFT;
400                 break;
401 
402             case wxRIGHT:
403                 gtkpos = GTK_POS_RIGHT;
404                 break;
405 
406             case wxTOP:
407                 gtkpos = GTK_POS_TOP;
408                 break;
409 
410             case wxBOTTOM:
411                 gtkpos = GTK_POS_BOTTOM;
412                 break;
413         }
414 
415         gtk_button_set_image_position(GTK_BUTTON(m_widget), gtkpos);
416 
417         // As in DoSetBitmap() above, the above call can invalidate the label
418         // style, so reapply it to preserve its font and colour.
419         GTKApplyWidgetStyle();
420 
421         InvalidateBestSize();
422     }
423 #endif // GTK+ 2.10+
424 }
425 
426 #endif // wxHAS_ANY_BUTTON
427