Implement support for markup labels for wxGTK wxButton.
[wxWidgets.git] / src / gtk / button.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/button.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
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 #if wxUSE_BUTTON
14
15 #ifndef WX_PRECOMP
16 #include "wx/button.h"
17 #endif
18
19 #include "wx/stockitem.h"
20
21 #include "wx/gtk/private.h"
22
23 // ----------------------------------------------------------------------------
24 // GTK callbacks
25 // ----------------------------------------------------------------------------
26
27 extern "C"
28 {
29
30 static void
31 wxgtk_button_clicked_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
32 {
33 if ( button->GTKShouldIgnoreEvent() )
34 return;
35
36 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, button->GetId());
37 event.SetEventObject(button);
38 button->HandleWindowEvent(event);
39 }
40
41 static void
42 wxgtk_button_enter_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
43 {
44 if ( button->GTKShouldIgnoreEvent() )
45 return;
46
47 button->GTKMouseEnters();
48 }
49
50 static void
51 wxgtk_button_leave_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
52 {
53 if ( button->GTKShouldIgnoreEvent() )
54 return;
55
56 button->GTKMouseLeaves();
57 }
58
59 static void
60 wxgtk_button_press_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
61 {
62 if ( button->GTKShouldIgnoreEvent() )
63 return;
64
65 button->GTKPressed();
66 }
67
68 static void
69 wxgtk_button_released_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
70 {
71 if ( button->GTKShouldIgnoreEvent() )
72 return;
73
74 button->GTKReleased();
75 }
76
77 //-----------------------------------------------------------------------------
78 // "style_set" from m_widget
79 //-----------------------------------------------------------------------------
80
81 static void
82 wxgtk_button_style_set_callback(GtkWidget* widget, GtkStyle*, wxButton* win)
83 {
84 /* the default button has a border around it */
85 wxWindow* parent = win->GetParent();
86 if (parent && parent->m_wxwindow && GTK_WIDGET_CAN_DEFAULT(widget))
87 {
88 GtkBorder* border = NULL;
89 gtk_widget_style_get(widget, "default_border", &border, NULL);
90 if (border)
91 {
92 win->MoveWindow(
93 win->m_x - border->left,
94 win->m_y - border->top,
95 win->m_width + border->left + border->right,
96 win->m_height + border->top + border->bottom);
97 gtk_border_free(border);
98 }
99 }
100 }
101
102 } // extern "C"
103
104 //-----------------------------------------------------------------------------
105 // wxButton
106 //-----------------------------------------------------------------------------
107
108 bool wxButton::Create(wxWindow *parent,
109 wxWindowID id,
110 const wxString &label,
111 const wxPoint& pos,
112 const wxSize& size,
113 long style,
114 const wxValidator& validator,
115 const wxString& name)
116 {
117 if (!PreCreation( parent, pos, size ) ||
118 !CreateBase( parent, id, pos, size, style, validator, name ))
119 {
120 wxFAIL_MSG( wxT("wxButton creation failed") );
121 return false;
122 }
123
124 // create either a standard button with text label (which may still contain
125 // an image under GTK+ 2.6+) or a bitmap-only button if we don't have any
126 // label
127 const bool
128 useLabel = !(style & wxBU_NOTEXT) && (!label.empty() || wxIsStockID(id));
129 if ( useLabel )
130 {
131 m_widget = gtk_button_new_with_mnemonic("");
132 }
133 else // no label, suppose we will have a bitmap
134 {
135 m_widget = gtk_button_new();
136
137 GtkWidget *image = gtk_image_new();
138 gtk_widget_show(image);
139 gtk_container_add(GTK_CONTAINER(m_widget), image);
140 }
141
142 g_object_ref(m_widget);
143
144 float x_alignment = 0.5;
145 if (HasFlag(wxBU_LEFT))
146 x_alignment = 0.0;
147 else if (HasFlag(wxBU_RIGHT))
148 x_alignment = 1.0;
149
150 float y_alignment = 0.5;
151 if (HasFlag(wxBU_TOP))
152 y_alignment = 0.0;
153 else if (HasFlag(wxBU_BOTTOM))
154 y_alignment = 1.0;
155
156 gtk_button_set_alignment(GTK_BUTTON(m_widget), x_alignment, y_alignment);
157
158 if ( useLabel )
159 SetLabel(label);
160
161 if (style & wxNO_BORDER)
162 gtk_button_set_relief( GTK_BUTTON(m_widget), GTK_RELIEF_NONE );
163
164 g_signal_connect_after (m_widget, "clicked",
165 G_CALLBACK (wxgtk_button_clicked_callback),
166 this);
167
168 g_signal_connect_after (m_widget, "style_set",
169 G_CALLBACK (wxgtk_button_style_set_callback),
170 this);
171
172 m_parent->DoAddChild( this );
173
174 PostCreation(size);
175
176 return true;
177 }
178
179
180 wxWindow *wxButton::SetDefault()
181 {
182 wxWindow *oldDefault = wxButtonBase::SetDefault();
183
184 GTK_WIDGET_SET_FLAGS( m_widget, GTK_CAN_DEFAULT );
185 gtk_widget_grab_default( m_widget );
186
187 // resize for default border
188 wxgtk_button_style_set_callback( m_widget, NULL, this );
189
190 return oldDefault;
191 }
192
193 /* static */
194 wxSize wxButtonBase::GetDefaultSize()
195 {
196 static wxSize size = wxDefaultSize;
197 if (size == wxDefaultSize)
198 {
199 // NB: Default size of buttons should be same as size of stock
200 // buttons as used in most GTK+ apps. Unfortunately it's a little
201 // tricky to obtain this size: stock button's size may be smaller
202 // than size of button in GtkButtonBox and vice versa,
203 // GtkButtonBox's minimal button size may be smaller than stock
204 // button's size. We have to retrieve both values and combine them.
205
206 GtkWidget *wnd = gtk_window_new(GTK_WINDOW_TOPLEVEL);
207 GtkWidget *box = gtk_hbutton_box_new();
208 GtkWidget *btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
209 gtk_container_add(GTK_CONTAINER(box), btn);
210 gtk_container_add(GTK_CONTAINER(wnd), box);
211 GtkRequisition req;
212 gtk_widget_size_request(btn, &req);
213
214 gint minwidth, minheight;
215 gtk_widget_style_get(box,
216 "child-min-width", &minwidth,
217 "child-min-height", &minheight,
218 NULL);
219
220 size.x = wxMax(minwidth, req.width);
221 size.y = wxMax(minheight, req.height);
222
223 gtk_widget_destroy(wnd);
224 }
225 return size;
226 }
227
228 void wxButton::SetLabel( const wxString &lbl )
229 {
230 wxCHECK_RET( m_widget != NULL, wxT("invalid button") );
231
232 wxString label(lbl);
233
234 if (label.empty() && wxIsStockID(m_windowId))
235 label = wxGetStockLabel(m_windowId);
236
237 wxControl::SetLabel(label);
238
239 // don't use label if it was explicitly disabled
240 if ( HasFlag(wxBU_NOTEXT) )
241 return;
242
243 if (wxIsStockID(m_windowId) && wxIsStockLabel(m_windowId, label))
244 {
245 const char *stock = wxGetStockGtkID(m_windowId);
246 if (stock)
247 {
248 gtk_button_set_label(GTK_BUTTON(m_widget), stock);
249 gtk_button_set_use_stock(GTK_BUTTON(m_widget), TRUE);
250 return;
251 }
252 }
253
254 // this call is necessary if the button had been initially created without
255 // a (text) label -- then we didn't use gtk_button_new_with_mnemonic() and
256 // so "use-underline" GtkButton property remained unset
257 gtk_button_set_use_underline(GTK_BUTTON(m_widget), TRUE);
258 const wxString labelGTK = GTKConvertMnemonics(label);
259 gtk_button_set_label(GTK_BUTTON(m_widget), wxGTK_CONV(labelGTK));
260 gtk_button_set_use_stock(GTK_BUTTON(m_widget), FALSE);
261
262 GTKApplyWidgetStyle( false );
263 }
264
265 bool wxButton::DoSetLabelMarkup(const wxString& markup)
266 {
267 wxCHECK_MSG( m_widget != NULL, false, "invalid button" );
268
269 const wxString stripped = RemoveMarkup(markup);
270 if ( stripped.empty() && !markup.empty() )
271 return false;
272
273 wxControl::SetLabel(stripped);
274
275 GtkLabel * const label = GTKGetLabel();
276 wxCHECK_MSG( label, false, "no label in this button?" );
277
278 GTKSetLabelWithMarkupForLabel(label, markup);
279
280 return true;
281 }
282
283 bool wxButton::Enable( bool enable )
284 {
285 if (!base_type::Enable(enable))
286 return false;
287
288 gtk_widget_set_sensitive(GTK_BIN(m_widget)->child, enable);
289
290 if (enable)
291 GTKFixSensitivity();
292
293 GTKUpdateBitmap();
294
295 return true;
296 }
297
298 GdkWindow *wxButton::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
299 {
300 return GTK_BUTTON(m_widget)->event_window;
301 }
302
303 GtkLabel *wxButton::GTKGetLabel() const
304 {
305 GtkWidget *child = GTK_BIN(m_widget)->child;
306 if ( GTK_IS_ALIGNMENT(child) )
307 {
308 GtkWidget *box = GTK_BIN(child)->child;
309 for (GList* item = GTK_BOX(box)->children; item; item = item->next)
310 {
311 GtkBoxChild* boxChild = static_cast<GtkBoxChild*>(item->data);
312 if ( GTK_IS_LABEL(boxChild->widget) )
313 return GTK_LABEL(boxChild->widget);
314 }
315
316 return NULL;
317 }
318
319 return GTK_LABEL(child);
320 }
321
322 void wxButton::DoApplyWidgetStyle(GtkRcStyle *style)
323 {
324 gtk_widget_modify_style(m_widget, style);
325 GtkWidget *child = GTK_BIN(m_widget)->child;
326 gtk_widget_modify_style(child, style);
327
328 // for buttons with images, the path to the label is (at least in 2.12)
329 // GtkButton -> GtkAlignment -> GtkHBox -> GtkLabel
330 if ( GTK_IS_ALIGNMENT(child) )
331 {
332 GtkWidget *box = GTK_BIN(child)->child;
333 if ( GTK_IS_BOX(box) )
334 {
335 for (GList* item = GTK_BOX(box)->children; item; item = item->next)
336 {
337 GtkBoxChild* boxChild = static_cast<GtkBoxChild*>(item->data);
338 gtk_widget_modify_style(boxChild->widget, style);
339 }
340 }
341 }
342 }
343
344 wxSize wxButton::DoGetBestSize() const
345 {
346 // the default button in wxGTK is bigger than the other ones because of an
347 // extra border around it, but we don't want to take it into account in
348 // our size calculations (otherwise the result is visually ugly), so
349 // always return the size of non default button from here
350 const bool isDefault = GTK_WIDGET_HAS_DEFAULT(m_widget);
351 if ( isDefault )
352 {
353 // temporarily unset default flag
354 GTK_WIDGET_UNSET_FLAGS( m_widget, GTK_CAN_DEFAULT );
355 }
356
357 wxSize ret( wxControl::DoGetBestSize() );
358
359 if ( isDefault )
360 {
361 // set it back again
362 GTK_WIDGET_SET_FLAGS( m_widget, GTK_CAN_DEFAULT );
363 }
364
365 if (!HasFlag(wxBU_EXACTFIT))
366 {
367 wxSize defaultSize = GetDefaultSize();
368 if (ret.x < defaultSize.x)
369 ret.x = defaultSize.x;
370 if (ret.y < defaultSize.y)
371 ret.y = defaultSize.y;
372 }
373
374 CacheBestSize(ret);
375 return ret;
376 }
377
378 // static
379 wxVisualAttributes
380 wxButton::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
381 {
382 return GetDefaultAttributesFromGTKWidget(gtk_button_new);
383 }
384
385 // ----------------------------------------------------------------------------
386 // bitmaps support
387 // ----------------------------------------------------------------------------
388
389 void wxButton::GTKMouseEnters()
390 {
391 m_isCurrent = true;
392
393 GTKUpdateBitmap();
394 }
395
396 void wxButton::GTKMouseLeaves()
397 {
398 m_isCurrent = false;
399
400 GTKUpdateBitmap();
401 }
402
403 void wxButton::GTKPressed()
404 {
405 m_isPressed = true;
406
407 GTKUpdateBitmap();
408 }
409
410 void wxButton::GTKReleased()
411 {
412 m_isPressed = false;
413
414 GTKUpdateBitmap();
415 }
416
417 void wxButton::GTKOnFocus(wxFocusEvent& event)
418 {
419 event.Skip();
420
421 GTKUpdateBitmap();
422 }
423
424 wxButton::State wxButton::GTKGetCurrentState() const
425 {
426 if ( !IsThisEnabled() )
427 return m_bitmaps[State_Disabled].IsOk() ? State_Disabled : State_Normal;
428
429 if ( m_isPressed && m_bitmaps[State_Pressed].IsOk() )
430 return State_Pressed;
431
432 if ( m_isCurrent && m_bitmaps[State_Current].IsOk() )
433 return State_Current;
434
435 if ( HasFocus() && m_bitmaps[State_Focused].IsOk() )
436 return State_Focused;
437
438 return State_Normal;
439 }
440
441 void wxButton::GTKUpdateBitmap()
442 {
443 // if we don't show bitmaps at all, there is nothing to update
444 if ( m_bitmaps[State_Normal].IsOk() )
445 {
446 // if we do show them, this will return a state for which we do have a
447 // valid bitmap
448 State state = GTKGetCurrentState();
449
450 GTKDoShowBitmap(m_bitmaps[state]);
451 }
452 }
453
454 void wxButton::GTKDoShowBitmap(const wxBitmap& bitmap)
455 {
456 wxASSERT_MSG( bitmap.IsOk(), "invalid bitmap" );
457
458 GtkWidget *image;
459 if ( DontShowLabel() )
460 {
461 image = GTK_BIN(m_widget)->child;
462 }
463 else // have both label and bitmap
464 {
465 #ifdef __WXGTK26__
466 if ( !gtk_check_version(2,6,0) )
467 {
468 image = gtk_button_get_image(GTK_BUTTON(m_widget));
469 }
470 else
471 #endif // __WXGTK26__
472 {
473 // buttons with both label and bitmap are only supported with GTK+
474 // 2.6 so far
475 //
476 // it shouldn't be difficult to implement them ourselves for the
477 // previous GTK+ versions by stuffing a container with a label and
478 // an image inside GtkButton but there doesn't seem to be much
479 // point in doing this for ancient GTK+ versions
480 return;
481 }
482 }
483
484 wxCHECK_RET( image && GTK_IS_IMAGE(image), "must have image widget" );
485
486 gtk_image_set_from_pixbuf(GTK_IMAGE(image), bitmap.GetPixbuf());
487 }
488
489 wxBitmap wxButton::DoGetBitmap(State which) const
490 {
491 return m_bitmaps[which];
492 }
493
494 void wxButton::DoSetBitmap(const wxBitmap& bitmap, State which)
495 {
496 switch ( which )
497 {
498 case State_Normal:
499 if ( DontShowLabel() )
500 {
501 // we only have the bitmap in this button, never remove it but
502 // do invalidate the best size when the bitmap (and presumably
503 // its size) changes
504 InvalidateBestSize();
505 }
506 #ifdef __WXGTK26__
507 // normal image is special: setting it enables images for the button and
508 // resetting it to nothing disables all of them
509 else if ( !gtk_check_version(2,6,0) )
510 {
511 GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
512 if ( image && !bitmap.IsOk() )
513 {
514 gtk_container_remove(GTK_CONTAINER(m_widget), image);
515 }
516 else if ( !image && bitmap.IsOk() )
517 {
518 image = gtk_image_new();
519 gtk_button_set_image(GTK_BUTTON(m_widget), image);
520 }
521 else // image presence or absence didn't change
522 {
523 // don't invalidate best size below
524 break;
525 }
526
527 InvalidateBestSize();
528 }
529 #endif // GTK+ 2.6+
530 break;
531
532 case State_Pressed:
533 if ( bitmap.IsOk() )
534 {
535 if ( !m_bitmaps[which].IsOk() )
536 {
537 // we need to install the callbacks to be notified about
538 // the button pressed state change
539 g_signal_connect
540 (
541 m_widget,
542 "pressed",
543 G_CALLBACK(wxgtk_button_press_callback),
544 this
545 );
546
547 g_signal_connect
548 (
549 m_widget,
550 "released",
551 G_CALLBACK(wxgtk_button_released_callback),
552 this
553 );
554 }
555 }
556 else // no valid bitmap
557 {
558 if ( m_bitmaps[which].IsOk() )
559 {
560 // we don't need to be notified about the button pressed
561 // state changes any more
562 g_signal_handlers_disconnect_by_func
563 (
564 m_widget,
565 (gpointer)wxgtk_button_press_callback,
566 this
567 );
568
569 g_signal_handlers_disconnect_by_func
570 (
571 m_widget,
572 (gpointer)wxgtk_button_released_callback,
573 this
574 );
575
576 // also make sure we don't remain stuck in pressed state
577 if ( m_isPressed )
578 {
579 m_isPressed = false;
580 GTKUpdateBitmap();
581 }
582 }
583 }
584 break;
585
586 case State_Current:
587 // the logic here is the same as above for State_Pressed: we need
588 // to connect the handlers if we must be notified about the changes
589 // in the button current state and we disconnect them when/if we
590 // don't need them any more
591 if ( bitmap.IsOk() )
592 {
593 if ( !m_bitmaps[which].IsOk() )
594 {
595 g_signal_connect
596 (
597 m_widget,
598 "enter",
599 G_CALLBACK(wxgtk_button_enter_callback),
600 this
601 );
602
603 g_signal_connect
604 (
605 m_widget,
606 "leave",
607 G_CALLBACK(wxgtk_button_leave_callback),
608 this
609 );
610 }
611 }
612 else // no valid bitmap
613 {
614 if ( m_bitmaps[which].IsOk() )
615 {
616 g_signal_handlers_disconnect_by_func
617 (
618 m_widget,
619 (gpointer)wxgtk_button_enter_callback,
620 this
621 );
622
623 g_signal_handlers_disconnect_by_func
624 (
625 m_widget,
626 (gpointer)wxgtk_button_leave_callback,
627 this
628 );
629
630 if ( m_isCurrent )
631 {
632 m_isCurrent = false;
633 GTKUpdateBitmap();
634 }
635 }
636 }
637 break;
638
639 case State_Focused:
640 if ( bitmap.IsOk() )
641 {
642 Connect(wxEVT_SET_FOCUS,
643 wxFocusEventHandler(wxButton::GTKOnFocus));
644 Connect(wxEVT_KILL_FOCUS,
645 wxFocusEventHandler(wxButton::GTKOnFocus));
646 }
647 else // no valid focused bitmap
648 {
649 Disconnect(wxEVT_SET_FOCUS,
650 wxFocusEventHandler(wxButton::GTKOnFocus));
651 Disconnect(wxEVT_KILL_FOCUS,
652 wxFocusEventHandler(wxButton::GTKOnFocus));
653 }
654 break;
655
656 default:
657 // no callbacks to connect/disconnect
658 ;
659 }
660
661 m_bitmaps[which] = bitmap;
662
663 // update the bitmap immediately if necessary, otherwise it will be done
664 // when the bitmap for the corresponding state is needed the next time by
665 // GTKUpdateBitmap()
666 if ( bitmap.IsOk() && which == GTKGetCurrentState() )
667 {
668 GTKDoShowBitmap(bitmap);
669 }
670 }
671
672 void wxButton::DoSetBitmapPosition(wxDirection dir)
673 {
674 #ifdef __WXGTK210__
675 if ( !gtk_check_version(2,10,0) )
676 {
677 GtkPositionType gtkpos;
678 switch ( dir )
679 {
680 default:
681 wxFAIL_MSG( "invalid position" );
682 // fall through
683
684 case wxLEFT:
685 gtkpos = GTK_POS_LEFT;
686 break;
687
688 case wxRIGHT:
689 gtkpos = GTK_POS_RIGHT;
690 break;
691
692 case wxTOP:
693 gtkpos = GTK_POS_TOP;
694 break;
695
696 case wxBOTTOM:
697 gtkpos = GTK_POS_BOTTOM;
698 break;
699 }
700
701 gtk_button_set_image_position(GTK_BUTTON(m_widget), gtkpos);
702 InvalidateBestSize();
703 }
704 #endif // GTK+ 2.10+
705 }
706
707 #endif // wxUSE_BUTTON