1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/button.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Copyright:   (c) 1998 Robert Roebling
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11 
12 #if wxUSE_BUTTON
13 
14 #ifndef WX_PRECOMP
15     #include "wx/button.h"
16 #endif
17 
18 #include "wx/stockitem.h"
19 
20 #include "wx/gtk/private.h"
21 #include "wx/gtk/private/list.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_clicked_callback(GtkWidget * WXUNUSED (widget),wxButton * button)32 wxgtk_button_clicked_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
33 {
34     if ( button->GTKShouldIgnoreEvent() )
35         return;
36 
37     wxCommandEvent event(wxEVT_BUTTON, button->GetId());
38     event.SetEventObject(button);
39     button->HandleWindowEvent(event);
40 }
41 
42 //-----------------------------------------------------------------------------
43 // "style_set" from m_widget
44 //-----------------------------------------------------------------------------
45 
46 static void
wxgtk_button_style_set_callback(GtkWidget * widget,GtkStyle *,wxButton * win)47 wxgtk_button_style_set_callback(GtkWidget* widget, GtkStyle*, wxButton* win)
48 {
49     /* the default button has a border around it */
50     wxWindow* parent = win->GetParent();
51     if (parent && parent->m_wxwindow && gtk_widget_get_can_default(widget))
52     {
53         GtkBorder* border = NULL;
54         gtk_widget_style_get(widget, "default_border", &border, NULL);
55         if (border)
56         {
57             win->MoveWindow(
58                 win->m_x - border->left,
59                 win->m_y - border->top,
60                 win->m_width + border->left + border->right,
61                 win->m_height + border->top + border->bottom);
62             gtk_border_free(border);
63         }
64     }
65 }
66 
67 } // extern "C"
68 
69 //-----------------------------------------------------------------------------
70 // wxButton
71 //-----------------------------------------------------------------------------
72 
73 #ifndef __WXGTK3__
74 bool wxButton::m_exactFitStyleDefined = false;
75 #endif // !__WXGTK3__
76 
Create(wxWindow * parent,wxWindowID id,const wxString & label,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)77 bool wxButton::Create(wxWindow *parent,
78                       wxWindowID id,
79                       const wxString &label,
80                       const wxPoint& pos,
81                       const wxSize& size,
82                       long style,
83                       const wxValidator& validator,
84                       const wxString& name)
85 {
86     if (!PreCreation( parent, pos, size ) ||
87         !CreateBase( parent, id, pos, size, style, validator, name ))
88     {
89         wxFAIL_MSG( wxT("wxButton creation failed") );
90         return false;
91     }
92 
93     // create either a standard button with text label (which may still contain
94     // an image under GTK+ 2.6+) or a bitmap-only button if we don't have any
95     // label
96     const bool
97         useLabel = !(style & wxBU_NOTEXT) && (!label.empty() || wxIsStockID(id));
98     if ( useLabel )
99     {
100         m_widget = gtk_button_new_with_mnemonic("");
101     }
102     else // no label, suppose we will have a bitmap
103     {
104         m_widget = gtk_button_new();
105 
106         GtkWidget* image = wxGtkImage::New(this);
107         gtk_widget_show(image);
108         gtk_container_add(GTK_CONTAINER(m_widget), image);
109     }
110 
111     g_object_ref(m_widget);
112 
113     float x_alignment = 0.5f;
114     if (HasFlag(wxBU_LEFT))
115         x_alignment = 0;
116     else if (HasFlag(wxBU_RIGHT))
117         x_alignment = 1;
118 
119     float y_alignment = 0.5f;
120     if (HasFlag(wxBU_TOP))
121         y_alignment = 0;
122     else if (HasFlag(wxBU_BOTTOM))
123         y_alignment = 1;
124 
125 #ifdef __WXGTK4__
126     if (useLabel)
127     {
128         g_object_set(gtk_bin_get_child(GTK_BIN(m_widget)),
129             "xalign", x_alignment, "yalign", y_alignment, NULL);
130     }
131 #else
132     wxGCC_WARNING_SUPPRESS(deprecated-declarations)
133     gtk_button_set_alignment(GTK_BUTTON(m_widget), x_alignment, y_alignment);
134     wxGCC_WARNING_RESTORE()
135 #endif
136 
137     if ( useLabel )
138         SetLabel(label);
139 
140     if (style & wxNO_BORDER)
141        gtk_button_set_relief( GTK_BUTTON(m_widget), GTK_RELIEF_NONE );
142 
143     if ( useLabel && (style & wxBU_EXACTFIT) )
144     {
145 #ifdef __WXGTK3__
146         GTKApplyCssStyle("* { padding:0 }");
147 #else
148         // Define a special button style without inner border
149         // if it's not yet done.
150         if ( !m_exactFitStyleDefined )
151         {
152             gtk_rc_parse_string(
153               "style \"wxButton_wxBU_EXACTFIT_style\"\n"
154               "{ GtkButton::inner-border = { 0, 0, 0, 0 } }\n"
155               "widget \"*wxButton_wxBU_EXACTFIT*\" style \"wxButton_wxBU_EXACTFIT_style\"\n"
156             );
157             m_exactFitStyleDefined = true;
158         }
159 
160         // Assign the button to the GTK style without inner border.
161         gtk_widget_set_name(m_widget, "wxButton_wxBU_EXACTFIT");
162 #endif // __WXGTK3__ / !__WXGTK3__
163     }
164 
165     g_signal_connect_after (m_widget, "clicked",
166                             G_CALLBACK (wxgtk_button_clicked_callback),
167                             this);
168 
169     g_signal_connect_after (m_widget, "style_set",
170                             G_CALLBACK (wxgtk_button_style_set_callback),
171                             this);
172 
173     m_parent->DoAddChild( this );
174 
175     PostCreation(size);
176 
177     return true;
178 }
179 
180 
SetDefault()181 wxWindow *wxButton::SetDefault()
182 {
183     wxWindow *oldDefault = wxButtonBase::SetDefault();
184 
185     gtk_widget_set_can_default(m_widget, TRUE);
186     gtk_widget_grab_default( m_widget );
187 
188     // resize for default border
189     wxgtk_button_style_set_callback( m_widget, NULL, this );
190 
191     return oldDefault;
192 }
193 
194 /* static */
GetDefaultSize(wxWindow * WXUNUSED (win))195 wxSize wxButtonBase::GetDefaultSize(wxWindow* WXUNUSED(win))
196 {
197     static wxSize size = wxDefaultSize;
198     if (size == wxDefaultSize)
199     {
200         // NB: Default size of buttons should be same as size of stock
201         //     buttons as used in most GTK+ apps. Unfortunately it's a little
202         //     tricky to obtain this size: stock button's size may be smaller
203         //     than size of button in GtkButtonBox and vice versa,
204         //     GtkButtonBox's minimal button size may be smaller than stock
205         //     button's size. We have to retrieve both values and combine them.
206 
207         GtkWidget *wnd = gtk_window_new(GTK_WINDOW_TOPLEVEL);
208         GtkWidget *box = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
209 #ifdef __WXGTK4__
210         wxString labelGTK = GTKConvertMnemonics(wxGetStockLabel(wxID_CANCEL));
211         GtkWidget *btn = gtk_button_new_with_mnemonic(labelGTK.utf8_str());
212 #else
213         wxGCC_WARNING_SUPPRESS(deprecated-declarations)
214         GtkWidget* btn = gtk_button_new_from_stock("gtk-cancel");
215         wxGCC_WARNING_RESTORE()
216 #endif
217         gtk_container_add(GTK_CONTAINER(box), btn);
218         gtk_container_add(GTK_CONTAINER(wnd), box);
219         GtkRequisition req;
220         gtk_widget_get_preferred_size(btn, NULL, &req);
221 
222         gint minwidth, minheight;
223         gtk_widget_style_get(box,
224                              "child-min-width", &minwidth,
225                              "child-min-height", &minheight,
226                              NULL);
227 
228         size.x = wxMax(minwidth, req.width);
229         size.y = wxMax(minheight, req.height);
230 
231         gtk_widget_destroy(wnd);
232     }
233     return size;
234 }
235 
SetLabel(const wxString & lbl)236 void wxButton::SetLabel( const wxString &lbl )
237 {
238     wxCHECK_RET( m_widget != NULL, wxT("invalid button") );
239 
240     wxString label(lbl);
241 
242     if (label.empty() && wxIsStockID(m_windowId))
243         label = wxGetStockLabel(m_windowId);
244 
245     wxAnyButton::SetLabel(label);
246 
247     // don't use label if it was explicitly disabled
248     if ( HasFlag(wxBU_NOTEXT) )
249         return;
250 
251 #ifndef __WXGTK4__
252     wxGCC_WARNING_SUPPRESS(deprecated-declarations)
253     if (wxIsStockID(m_windowId) && wxIsStockLabel(m_windowId, label))
254     {
255         const char *stock = wxGetStockGtkID(m_windowId);
256         if (stock)
257         {
258             gtk_button_set_label(GTK_BUTTON(m_widget), stock);
259             gtk_button_set_use_stock(GTK_BUTTON(m_widget), TRUE);
260             return;
261         }
262     }
263     wxGCC_WARNING_RESTORE()
264 #endif
265 
266     // this call is necessary if the button had been initially created without
267     // a (text) label -- then we didn't use gtk_button_new_with_mnemonic() and
268     // so "use-underline" GtkButton property remained unset
269     gtk_button_set_use_underline(GTK_BUTTON(m_widget), TRUE);
270     const wxString labelGTK = GTKConvertMnemonics(label);
271     gtk_button_set_label(GTK_BUTTON(m_widget), wxGTK_CONV(labelGTK));
272 #ifndef __WXGTK4__
273     wxGCC_WARNING_SUPPRESS(deprecated-declarations)
274     gtk_button_set_use_stock(GTK_BUTTON(m_widget), FALSE);
275     wxGCC_WARNING_RESTORE()
276 #endif
277 
278     GTKApplyWidgetStyle( false );
279 }
280 
281 #if wxUSE_MARKUP
DoSetLabelMarkup(const wxString & markup)282 bool wxButton::DoSetLabelMarkup(const wxString& markup)
283 {
284     wxCHECK_MSG( m_widget != NULL, false, "invalid button" );
285 
286     const wxString stripped = RemoveMarkup(markup);
287     if ( stripped.empty() && !markup.empty() )
288         return false;
289 
290     SetLabel(stripped);
291 
292     GtkLabel * const label = GTKGetLabel();
293     wxCHECK_MSG( label, false, "no label in this button?" );
294 
295     GTKSetLabelWithMarkupForLabel(label, markup);
296 
297     return true;
298 }
299 
GTKGetLabel() const300 GtkLabel *wxButton::GTKGetLabel() const
301 {
302     GtkWidget* child = gtk_bin_get_child(GTK_BIN(m_widget));
303 #ifdef __WXGTK4__
304     if (GTK_IS_LABEL(child))
305         return GTK_LABEL(child);
306 
307     return NULL;
308 #else
309     wxGCC_WARNING_SUPPRESS(deprecated-declarations)
310     if ( GTK_IS_ALIGNMENT(child) )
311     {
312         GtkWidget* box = gtk_bin_get_child(GTK_BIN(child));
313         GtkLabel* label = NULL;
314         wxGtkList list(gtk_container_get_children(GTK_CONTAINER(box)));
315         for (GList* item = list; item; item = item->next)
316         {
317             if (GTK_IS_LABEL(item->data))
318                 label = GTK_LABEL(item->data);
319         }
320 
321         return label;
322     }
323 
324     return GTK_LABEL(child);
325     wxGCC_WARNING_RESTORE()
326 #endif
327 }
328 #endif // wxUSE_MARKUP
329 
DoApplyWidgetStyle(GtkRcStyle * style)330 void wxButton::DoApplyWidgetStyle(GtkRcStyle *style)
331 {
332     GTKApplyStyle(m_widget, style);
333     GtkWidget* child = gtk_bin_get_child(GTK_BIN(m_widget));
334     GTKApplyStyle(child, style);
335 
336 #ifndef __WXGTK4__
337     wxGCC_WARNING_SUPPRESS(deprecated-declarations)
338     // for buttons with images, the path to the label is (at least in 2.12)
339     // GtkButton -> GtkAlignment -> GtkHBox -> GtkLabel
340     if ( GTK_IS_ALIGNMENT(child) )
341     {
342         GtkWidget* box = gtk_bin_get_child(GTK_BIN(child));
343         if ( GTK_IS_BOX(box) )
344         {
345             wxGtkList list(gtk_container_get_children(GTK_CONTAINER(box)));
346             for (GList* item = list; item; item = item->next)
347             {
348                 GTKApplyStyle(GTK_WIDGET(item->data), style);
349             }
350         }
351     }
352     wxGCC_WARNING_RESTORE()
353 #endif
354 }
355 
DoGetBestSize() const356 wxSize wxButton::DoGetBestSize() const
357 {
358     // the default button in wxGTK is bigger than the other ones because of an
359     // extra border around it, but we don't want to take it into account in
360     // our size calculations (otherwise the result is visually ugly), so
361     // always return the size of non default button from here
362     const bool isDefault = gtk_widget_has_default(m_widget) != 0;
363     if ( isDefault )
364     {
365         // temporarily unset default flag
366         gtk_widget_set_can_default(m_widget, FALSE);
367     }
368 
369     wxSize ret( wxAnyButton::DoGetBestSize() );
370 
371     if ( isDefault )
372     {
373         // set it back again
374         gtk_widget_set_can_default(m_widget, TRUE);
375     }
376 
377     if (!HasFlag(wxBU_EXACTFIT))
378     {
379         wxSize defaultSize = GetDefaultSize();
380         if (ret.x < defaultSize.x)
381             ret.x = defaultSize.x;
382         if (ret.y < defaultSize.y)
383             ret.y = defaultSize.y;
384     }
385 
386     return ret;
387 }
388 
389 // static
390 wxVisualAttributes
GetClassDefaultAttributes(wxWindowVariant WXUNUSED (variant))391 wxButton::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
392 {
393     return GetDefaultAttributesFromGTKWidget(gtk_button_new());
394 }
395 #endif // wxUSE_BUTTON
396