implement support for bitmaps for all states in 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 IMPLEMENT_DYNAMIC_CLASS(wxButton,wxControl)
109
110 bool 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)
118 {
119 if (!PreCreation( parent, pos, size ) ||
120 !CreateBase( parent, id, pos, size, style, validator, name ))
121 {
122 wxFAIL_MSG( wxT("wxButton creation failed") );
123 return false;
124 }
125
126 m_widget = gtk_button_new_with_mnemonic("");
127 g_object_ref(m_widget);
128
129 float x_alignment = 0.5;
130 if (HasFlag(wxBU_LEFT))
131 x_alignment = 0.0;
132 else if (HasFlag(wxBU_RIGHT))
133 x_alignment = 1.0;
134
135 float y_alignment = 0.5;
136 if (HasFlag(wxBU_TOP))
137 y_alignment = 0.0;
138 else if (HasFlag(wxBU_BOTTOM))
139 y_alignment = 1.0;
140
141 gtk_button_set_alignment(GTK_BUTTON(m_widget), x_alignment, y_alignment);
142
143 SetLabel(label);
144
145 if (style & wxNO_BORDER)
146 gtk_button_set_relief( GTK_BUTTON(m_widget), GTK_RELIEF_NONE );
147
148 g_signal_connect_after (m_widget, "clicked",
149 G_CALLBACK (wxgtk_button_clicked_callback),
150 this);
151
152 g_signal_connect_after (m_widget, "style_set",
153 G_CALLBACK (wxgtk_button_style_set_callback),
154 this);
155
156 m_parent->DoAddChild( this );
157
158 PostCreation(size);
159
160 return true;
161 }
162
163
164 wxWindow *wxButton::SetDefault()
165 {
166 wxWindow *oldDefault = wxButtonBase::SetDefault();
167
168 GTK_WIDGET_SET_FLAGS( m_widget, GTK_CAN_DEFAULT );
169 gtk_widget_grab_default( m_widget );
170
171 // resize for default border
172 wxgtk_button_style_set_callback( m_widget, NULL, this );
173
174 return oldDefault;
175 }
176
177 /* static */
178 wxSize wxButtonBase::GetDefaultSize()
179 {
180 static wxSize size = wxDefaultSize;
181 if (size == wxDefaultSize)
182 {
183 // NB: Default size of buttons should be same as size of stock
184 // buttons as used in most GTK+ apps. Unfortunately it's a little
185 // tricky to obtain this size: stock button's size may be smaller
186 // than size of button in GtkButtonBox and vice versa,
187 // GtkButtonBox's minimal button size may be smaller than stock
188 // button's size. We have to retrieve both values and combine them.
189
190 GtkWidget *wnd = gtk_window_new(GTK_WINDOW_TOPLEVEL);
191 GtkWidget *box = gtk_hbutton_box_new();
192 GtkWidget *btn = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
193 gtk_container_add(GTK_CONTAINER(box), btn);
194 gtk_container_add(GTK_CONTAINER(wnd), box);
195 GtkRequisition req;
196 gtk_widget_size_request(btn, &req);
197
198 gint minwidth, minheight;
199 gtk_widget_style_get(box,
200 "child-min-width", &minwidth,
201 "child-min-height", &minheight,
202 NULL);
203
204 size.x = wxMax(minwidth, req.width);
205 size.y = wxMax(minheight, req.height);
206
207 gtk_widget_destroy(wnd);
208 }
209 return size;
210 }
211
212 void wxButton::SetLabel( const wxString &lbl )
213 {
214 wxCHECK_RET( m_widget != NULL, wxT("invalid button") );
215
216 wxString label(lbl);
217
218 if (label.empty() && wxIsStockID(m_windowId))
219 label = wxGetStockLabel(m_windowId);
220
221 wxControl::SetLabel(label);
222
223 if (wxIsStockID(m_windowId) && wxIsStockLabel(m_windowId, label))
224 {
225 const char *stock = wxGetStockGtkID(m_windowId);
226 if (stock)
227 {
228 gtk_button_set_label(GTK_BUTTON(m_widget), stock);
229 gtk_button_set_use_stock(GTK_BUTTON(m_widget), TRUE);
230 return;
231 }
232 }
233
234 const wxString labelGTK = GTKConvertMnemonics(label);
235 gtk_button_set_label(GTK_BUTTON(m_widget), wxGTK_CONV(labelGTK));
236 gtk_button_set_use_stock(GTK_BUTTON(m_widget), FALSE);
237
238 GTKApplyWidgetStyle( false );
239 }
240
241 bool wxButton::Enable( bool enable )
242 {
243 bool isEnabled = IsEnabled();
244
245 if ( !wxControl::Enable( enable ) )
246 return false;
247
248 gtk_widget_set_sensitive(GTK_BIN(m_widget)->child, enable);
249
250 if (!isEnabled && enable)
251 {
252 GTKFixSensitivity();
253 }
254
255 GTKUpdateBitmap();
256
257 return true;
258 }
259
260 GdkWindow *wxButton::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
261 {
262 return GTK_BUTTON(m_widget)->event_window;
263 }
264
265 void wxButton::DoApplyWidgetStyle(GtkRcStyle *style)
266 {
267 gtk_widget_modify_style(m_widget, style);
268 GtkWidget *child = GTK_BIN(m_widget)->child;
269 gtk_widget_modify_style(child, style);
270
271 // for buttons with images, the path to the label is (at least in 2.12)
272 // GtkButton -> GtkAlignment -> GtkHBox -> GtkLabel
273 if ( GTK_IS_ALIGNMENT(child) )
274 {
275 GtkWidget *box = GTK_BIN(child)->child;
276 if ( GTK_IS_BOX(box) )
277 {
278 for (GList* item = GTK_BOX(box)->children; item; item = item->next)
279 {
280 GtkBoxChild* boxChild = static_cast<GtkBoxChild*>(item->data);
281 gtk_widget_modify_style(boxChild->widget, style);
282 }
283 }
284 }
285 }
286
287 wxSize wxButton::DoGetBestSize() const
288 {
289 // the default button in wxGTK is bigger than the other ones because of an
290 // extra border around it, but we don't want to take it into account in
291 // our size calculations (otherwise the result is visually ugly), so
292 // always return the size of non default button from here
293 const bool isDefault = GTK_WIDGET_HAS_DEFAULT(m_widget);
294 if ( isDefault )
295 {
296 // temporarily unset default flag
297 GTK_WIDGET_UNSET_FLAGS( m_widget, GTK_CAN_DEFAULT );
298 }
299
300 wxSize ret( wxControl::DoGetBestSize() );
301
302 if ( isDefault )
303 {
304 // set it back again
305 GTK_WIDGET_SET_FLAGS( m_widget, GTK_CAN_DEFAULT );
306 }
307
308 if (!HasFlag(wxBU_EXACTFIT))
309 {
310 wxSize defaultSize = GetDefaultSize();
311 if (ret.x < defaultSize.x)
312 ret.x = defaultSize.x;
313 if (ret.y < defaultSize.y)
314 ret.y = defaultSize.y;
315 }
316
317 CacheBestSize(ret);
318 return ret;
319 }
320
321 // static
322 wxVisualAttributes
323 wxButton::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
324 {
325 return GetDefaultAttributesFromGTKWidget(gtk_button_new);
326 }
327
328 // ----------------------------------------------------------------------------
329 // bitmaps support
330 // ----------------------------------------------------------------------------
331
332 void wxButton::GTKMouseEnters()
333 {
334 m_isCurrent = true;
335
336 GTKUpdateBitmap();
337 }
338
339 void wxButton::GTKMouseLeaves()
340 {
341 m_isCurrent = false;
342
343 GTKUpdateBitmap();
344 }
345
346 void wxButton::GTKPressed()
347 {
348 m_isPressed = true;
349
350 GTKUpdateBitmap();
351 }
352
353 void wxButton::GTKReleased()
354 {
355 m_isPressed = false;
356
357 GTKUpdateBitmap();
358 }
359
360 void wxButton::GTKOnFocus(wxFocusEvent& event)
361 {
362 event.Skip();
363
364 GTKUpdateBitmap();
365 }
366
367 wxButton::State wxButton::GTKGetCurrentState() const
368 {
369 if ( !IsThisEnabled() )
370 return m_bitmaps[State_Disabled].IsOk() ? State_Disabled : State_Normal;
371
372 if ( m_isPressed && m_bitmaps[State_Pressed].IsOk() )
373 return State_Pressed;
374
375 if ( m_isCurrent && m_bitmaps[State_Current].IsOk() )
376 return State_Current;
377
378 if ( HasFocus() && m_bitmaps[State_Focused].IsOk() )
379 return State_Focused;
380
381 return State_Normal;
382 }
383
384 void wxButton::GTKUpdateBitmap()
385 {
386 State state = GTKGetCurrentState();
387
388 GTKDoShowBitmap(m_bitmaps[state]);
389 }
390
391 void wxButton::GTKDoShowBitmap(const wxBitmap& bitmap)
392 {
393 wxASSERT_MSG( bitmap.IsOk(), "invalid bitmap" );
394
395 #ifdef __WXGTK26__
396 if ( !gtk_check_version(2,6,0) )
397 {
398 GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
399 wxCHECK_RET( image, "must have image widget" );
400
401 gtk_image_set_from_pixbuf(GTK_IMAGE(image), bitmap.GetPixbuf());
402 }
403 #endif // __WXGTK26__
404 }
405
406 wxBitmap wxButton::DoGetBitmap(State which) const
407 {
408 return m_bitmaps[which];
409 }
410
411 void wxButton::DoSetBitmap(const wxBitmap& bitmap, State which)
412 {
413 switch ( which )
414 {
415 case State_Normal:
416 #ifdef __WXGTK26__
417 // normal image is special: setting it enables images for the button and
418 // resetting it to nothing disables all of them
419 if ( !gtk_check_version(2,6,0) )
420 {
421 GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
422 if ( image && !bitmap.IsOk() )
423 {
424 gtk_container_remove(GTK_CONTAINER(m_widget), image);
425 }
426 else if ( !image && bitmap.IsOk() )
427 {
428 image = gtk_image_new();
429 gtk_button_set_image(GTK_BUTTON(m_widget), image);
430 }
431 else // image presence or absence didn't change
432 {
433 // don't invalidate best size below
434 break;
435 }
436
437 InvalidateBestSize();
438 }
439 #endif // GTK+ 2.6+
440 break;
441
442 case State_Pressed:
443 if ( bitmap.IsOk() )
444 {
445 if ( !m_bitmaps[which].IsOk() )
446 {
447 // we need to install the callbacks to be notified about
448 // the button pressed state change
449 g_signal_connect
450 (
451 m_widget,
452 "pressed",
453 G_CALLBACK(wxgtk_button_press_callback),
454 this
455 );
456
457 g_signal_connect
458 (
459 m_widget,
460 "released",
461 G_CALLBACK(wxgtk_button_released_callback),
462 this
463 );
464 }
465 }
466 else // no valid bitmap
467 {
468 if ( m_bitmaps[which].IsOk() )
469 {
470 // we don't need to be notified about the button pressed
471 // state changes any more
472 g_signal_handlers_disconnect_by_func
473 (
474 m_widget,
475 (gpointer)wxgtk_button_press_callback,
476 this
477 );
478
479 g_signal_handlers_disconnect_by_func
480 (
481 m_widget,
482 (gpointer)wxgtk_button_released_callback,
483 this
484 );
485
486 // also make sure we don't remain stuck in pressed state
487 if ( m_isPressed )
488 {
489 m_isPressed = false;
490 GTKUpdateBitmap();
491 }
492 }
493 }
494 break;
495
496 case State_Current:
497 // the logic here is the same as above for State_Pressed: we need
498 // to connect the handlers if we must be notified about the changes
499 // in the button current state and we disconnect them when/if we
500 // don't need them any more
501 if ( bitmap.IsOk() )
502 {
503 if ( !m_bitmaps[which].IsOk() )
504 {
505 g_signal_connect
506 (
507 m_widget,
508 "enter",
509 G_CALLBACK(wxgtk_button_enter_callback),
510 this
511 );
512
513 g_signal_connect
514 (
515 m_widget,
516 "leave",
517 G_CALLBACK(wxgtk_button_leave_callback),
518 this
519 );
520 }
521 }
522 else // no valid bitmap
523 {
524 if ( m_bitmaps[which].IsOk() )
525 {
526 g_signal_handlers_disconnect_by_func
527 (
528 m_widget,
529 (gpointer)wxgtk_button_enter_callback,
530 this
531 );
532
533 g_signal_handlers_disconnect_by_func
534 (
535 m_widget,
536 (gpointer)wxgtk_button_leave_callback,
537 this
538 );
539
540 if ( m_isCurrent )
541 {
542 m_isCurrent = false;
543 GTKUpdateBitmap();
544 }
545 }
546 }
547 break;
548
549 case State_Focused:
550 if ( bitmap.IsOk() )
551 {
552 Connect(wxEVT_SET_FOCUS,
553 wxFocusEventHandler(wxButton::GTKOnFocus));
554 Connect(wxEVT_KILL_FOCUS,
555 wxFocusEventHandler(wxButton::GTKOnFocus));
556 }
557 else // no valid focused bitmap
558 {
559 Disconnect(wxEVT_SET_FOCUS,
560 wxFocusEventHandler(wxButton::GTKOnFocus));
561 Disconnect(wxEVT_KILL_FOCUS,
562 wxFocusEventHandler(wxButton::GTKOnFocus));
563 }
564 break;
565
566 default:
567 // no callbacks to connect/disconnect
568 ;
569 }
570
571 m_bitmaps[which] = bitmap;
572
573 // update the bitmap immediately if necessary, otherwise it will be done
574 // when the bitmap for the corresponding state is needed the next time by
575 // GTKUpdateBitmap()
576 if ( bitmap.IsOk() && which == GTKGetCurrentState() )
577 {
578 GTKDoShowBitmap(bitmap);
579 }
580 }
581
582 void wxButton::DoSetBitmapPosition(wxDirection dir)
583 {
584 #ifdef __WXGTK210__
585 if ( !gtk_check_version(2,10,0) )
586 {
587 GtkPositionType gtkpos;
588 switch ( dir )
589 {
590 default:
591 wxFAIL_MSG( "invalid position" );
592 // fall through
593
594 case wxLEFT:
595 gtkpos = GTK_POS_LEFT;
596 break;
597
598 case wxRIGHT:
599 gtkpos = GTK_POS_RIGHT;
600 break;
601
602 case wxTOP:
603 gtkpos = GTK_POS_TOP;
604 break;
605
606 case wxBOTTOM:
607 gtkpos = GTK_POS_BOTTOM;
608 break;
609 }
610
611 gtk_button_set_image_position(GTK_BUTTON(m_widget), gtkpos);
612 }
613 #endif // GTK+ 2.10+
614 }
615
616 #endif // wxUSE_BUTTON