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