]> Pileus Git - ~andy/gtk/blob - gtk/gtktogglebutton.c
GtkToggleButton: Set widget state as state flags.
[~andy/gtk] / gtk / gtktogglebutton.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include "config.h"
28
29 #include "gtktogglebutton.h"
30
31 #include "gtkbuttonprivate.h"
32 #include "gtklabel.h"
33 #include "gtkmain.h"
34 #include "gtkmarshalers.h"
35 #include "gtktoggleaction.h"
36 #include "gtkactivatable.h"
37 #include "gtkprivate.h"
38 #include "gtkintl.h"
39
40
41 #define DEFAULT_LEFT_POS  4
42 #define DEFAULT_TOP_POS   4
43 #define DEFAULT_SPACING   7
44
45 struct _GtkToggleButtonPrivate
46 {
47   guint active         : 1;
48   guint draw_indicator : 1;
49   guint inconsistent   : 1;
50 };
51
52 enum {
53   TOGGLED,
54   LAST_SIGNAL
55 };
56
57 enum {
58   PROP_0,
59   PROP_ACTIVE,
60   PROP_INCONSISTENT,
61   PROP_DRAW_INDICATOR
62 };
63
64
65 static gint gtk_toggle_button_draw         (GtkWidget            *widget,
66                                             cairo_t              *cr);
67 static gboolean gtk_toggle_button_mnemonic_activate  (GtkWidget            *widget,
68                                                       gboolean              group_cycling);
69 static void gtk_toggle_button_pressed       (GtkButton            *button);
70 static void gtk_toggle_button_released      (GtkButton            *button);
71 static void gtk_toggle_button_clicked       (GtkButton            *button);
72 static void gtk_toggle_button_set_property  (GObject              *object,
73                                              guint                 prop_id,
74                                              const GValue         *value,
75                                              GParamSpec           *pspec);
76 static void gtk_toggle_button_get_property  (GObject              *object,
77                                              guint                 prop_id,
78                                              GValue               *value,
79                                              GParamSpec           *pspec);
80 static void gtk_toggle_button_update_state  (GtkButton            *button);
81
82
83 static void gtk_toggle_button_activatable_interface_init (GtkActivatableIface  *iface);
84 static void gtk_toggle_button_update                 (GtkActivatable       *activatable,
85                                                       GtkAction            *action,
86                                                       const gchar          *property_name);
87 static void gtk_toggle_button_sync_action_properties (GtkActivatable       *activatable,
88                                                       GtkAction            *action);
89
90 static GtkActivatableIface *parent_activatable_iface;
91 static guint                toggle_button_signals[LAST_SIGNAL] = { 0 };
92
93 G_DEFINE_TYPE_WITH_CODE (GtkToggleButton, gtk_toggle_button, GTK_TYPE_BUTTON,
94                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
95                                                 gtk_toggle_button_activatable_interface_init))
96
97 static void
98 gtk_toggle_button_class_init (GtkToggleButtonClass *class)
99 {
100   GObjectClass *gobject_class;
101   GtkWidgetClass *widget_class;
102   GtkButtonClass *button_class;
103
104   gobject_class = G_OBJECT_CLASS (class);
105   widget_class = (GtkWidgetClass*) class;
106   button_class = (GtkButtonClass*) class;
107
108   gobject_class->set_property = gtk_toggle_button_set_property;
109   gobject_class->get_property = gtk_toggle_button_get_property;
110
111   widget_class->draw = gtk_toggle_button_draw;
112   widget_class->mnemonic_activate = gtk_toggle_button_mnemonic_activate;
113
114   button_class->pressed = gtk_toggle_button_pressed;
115   button_class->released = gtk_toggle_button_released;
116   button_class->clicked = gtk_toggle_button_clicked;
117   button_class->enter = gtk_toggle_button_update_state;
118   button_class->leave = gtk_toggle_button_update_state;
119
120   class->toggled = NULL;
121
122   g_object_class_install_property (gobject_class,
123                                    PROP_ACTIVE,
124                                    g_param_spec_boolean ("active",
125                                                          P_("Active"),
126                                                          P_("If the toggle button should be pressed in"),
127                                                          FALSE,
128                                                          GTK_PARAM_READWRITE));
129
130   g_object_class_install_property (gobject_class,
131                                    PROP_INCONSISTENT,
132                                    g_param_spec_boolean ("inconsistent",
133                                                          P_("Inconsistent"),
134                                                          P_("If the toggle button is in an \"in between\" state"),
135                                                          FALSE,
136                                                          GTK_PARAM_READWRITE));
137
138   g_object_class_install_property (gobject_class,
139                                    PROP_DRAW_INDICATOR,
140                                    g_param_spec_boolean ("draw-indicator",
141                                                          P_("Draw Indicator"),
142                                                          P_("If the toggle part of the button is displayed"),
143                                                          FALSE,
144                                                          GTK_PARAM_READWRITE));
145
146   toggle_button_signals[TOGGLED] =
147     g_signal_new (I_("toggled"),
148                   G_OBJECT_CLASS_TYPE (gobject_class),
149                   G_SIGNAL_RUN_FIRST,
150                   G_STRUCT_OFFSET (GtkToggleButtonClass, toggled),
151                   NULL, NULL,
152                   _gtk_marshal_VOID__VOID,
153                   G_TYPE_NONE, 0);
154
155   g_type_class_add_private (class, sizeof (GtkToggleButtonPrivate));
156 }
157
158 static void
159 gtk_toggle_button_init (GtkToggleButton *toggle_button)
160 {
161   GtkToggleButtonPrivate *priv;
162
163   toggle_button->priv = G_TYPE_INSTANCE_GET_PRIVATE (toggle_button,
164                                                      GTK_TYPE_TOGGLE_BUTTON,
165                                                      GtkToggleButtonPrivate);
166   priv = toggle_button->priv;
167
168   priv->active = FALSE;
169   priv->draw_indicator = FALSE;
170   GTK_BUTTON (toggle_button)->priv->depress_on_activate = TRUE;
171 }
172
173 static void
174 gtk_toggle_button_activatable_interface_init (GtkActivatableIface *iface)
175 {
176   parent_activatable_iface = g_type_interface_peek_parent (iface);
177   iface->update = gtk_toggle_button_update;
178   iface->sync_action_properties = gtk_toggle_button_sync_action_properties;
179 }
180
181 static void
182 gtk_toggle_button_update (GtkActivatable *activatable,
183                           GtkAction      *action,
184                           const gchar    *property_name)
185 {
186   GtkToggleButton *button;
187
188   parent_activatable_iface->update (activatable, action, property_name);
189
190   button = GTK_TOGGLE_BUTTON (activatable);
191
192   if (strcmp (property_name, "active") == 0)
193     {
194       gtk_action_block_activate (action);
195       gtk_toggle_button_set_active (button, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
196       gtk_action_unblock_activate (action);
197     }
198
199 }
200
201 static void
202 gtk_toggle_button_sync_action_properties (GtkActivatable *activatable,
203                                           GtkAction      *action)
204 {
205   GtkToggleButton *button;
206
207   parent_activatable_iface->sync_action_properties (activatable, action);
208
209   if (!GTK_IS_TOGGLE_ACTION (action))
210     return;
211
212   button = GTK_TOGGLE_BUTTON (activatable);
213
214   gtk_action_block_activate (action);
215   gtk_toggle_button_set_active (button, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
216   gtk_action_unblock_activate (action);
217 }
218
219
220 GtkWidget*
221 gtk_toggle_button_new (void)
222 {
223   return g_object_new (GTK_TYPE_TOGGLE_BUTTON, NULL);
224 }
225
226 GtkWidget*
227 gtk_toggle_button_new_with_label (const gchar *label)
228 {
229   return g_object_new (GTK_TYPE_TOGGLE_BUTTON, "label", label, NULL);
230 }
231
232 /**
233  * gtk_toggle_button_new_with_mnemonic:
234  * @label: the text of the button, with an underscore in front of the
235  *         mnemonic character
236  * @returns: a new #GtkToggleButton
237  *
238  * Creates a new #GtkToggleButton containing a label. The label
239  * will be created using gtk_label_new_with_mnemonic(), so underscores
240  * in @label indicate the mnemonic for the button.
241  **/
242 GtkWidget*
243 gtk_toggle_button_new_with_mnemonic (const gchar *label)
244 {
245   return g_object_new (GTK_TYPE_TOGGLE_BUTTON, 
246                        "label", label, 
247                        "use-underline", TRUE, 
248                        NULL);
249 }
250
251 static void
252 gtk_toggle_button_set_property (GObject      *object,
253                                 guint         prop_id,
254                                 const GValue *value,
255                                 GParamSpec   *pspec)
256 {
257   GtkToggleButton *tb;
258
259   tb = GTK_TOGGLE_BUTTON (object);
260
261   switch (prop_id)
262     {
263     case PROP_ACTIVE:
264       gtk_toggle_button_set_active (tb, g_value_get_boolean (value));
265       break;
266     case PROP_INCONSISTENT:
267       gtk_toggle_button_set_inconsistent (tb, g_value_get_boolean (value));
268       break;
269     case PROP_DRAW_INDICATOR:
270       gtk_toggle_button_set_mode (tb, g_value_get_boolean (value));
271       break;
272     default:
273       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
274       break;
275     }
276 }
277
278 static void
279 gtk_toggle_button_get_property (GObject      *object,
280                                 guint         prop_id,
281                                 GValue       *value,
282                                 GParamSpec   *pspec)
283 {
284   GtkToggleButton *tb = GTK_TOGGLE_BUTTON (object);
285   GtkToggleButtonPrivate *priv = tb->priv;
286
287   switch (prop_id)
288     {
289     case PROP_ACTIVE:
290       g_value_set_boolean (value, priv->active);
291       break;
292     case PROP_INCONSISTENT:
293       g_value_set_boolean (value, priv->inconsistent);
294       break;
295     case PROP_DRAW_INDICATOR:
296       g_value_set_boolean (value, priv->draw_indicator);
297       break;
298     default:
299       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
300       break;
301     }
302 }
303
304 /**
305  * gtk_toggle_button_set_mode:
306  * @toggle_button: a #GtkToggleButton
307  * @draw_indicator: if %TRUE, draw the button as a separate indicator
308  * and label; if %FALSE, draw the button like a normal button
309  *
310  * Sets whether the button is displayed as a separate indicator and label.
311  * You can call this function on a checkbutton or a radiobutton with
312  * @draw_indicator = %FALSE to make the button look like a normal button
313  *
314  * This function only affects instances of classes like #GtkCheckButton
315  * and #GtkRadioButton that derive from #GtkToggleButton,
316  * not instances of #GtkToggleButton itself.
317  */
318 void
319 gtk_toggle_button_set_mode (GtkToggleButton *toggle_button,
320                             gboolean         draw_indicator)
321 {
322   GtkToggleButtonPrivate *priv;
323
324   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
325
326   priv = toggle_button->priv;
327
328   draw_indicator = draw_indicator ? TRUE : FALSE;
329
330   if (priv->draw_indicator != draw_indicator)
331     {
332       priv->draw_indicator = draw_indicator;
333       GTK_BUTTON (toggle_button)->priv->depress_on_activate = !draw_indicator;
334
335       if (gtk_widget_get_visible (GTK_WIDGET (toggle_button)))
336         gtk_widget_queue_resize (GTK_WIDGET (toggle_button));
337
338       g_object_notify (G_OBJECT (toggle_button), "draw-indicator");
339     }
340 }
341
342 /**
343  * gtk_toggle_button_get_mode:
344  * @toggle_button: a #GtkToggleButton
345  *
346  * Retrieves whether the button is displayed as a separate indicator
347  * and label. See gtk_toggle_button_set_mode().
348  *
349  * Return value: %TRUE if the togglebutton is drawn as a separate indicator
350  *   and label.
351  **/
352 gboolean
353 gtk_toggle_button_get_mode (GtkToggleButton *toggle_button)
354 {
355   g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE);
356
357   return toggle_button->priv->draw_indicator;
358 }
359
360 void
361 gtk_toggle_button_set_active (GtkToggleButton *toggle_button,
362                               gboolean         is_active)
363 {
364   GtkToggleButtonPrivate *priv;
365
366   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
367
368   priv = toggle_button->priv;
369
370   is_active = is_active != FALSE;
371
372   if (priv->active != is_active)
373     gtk_button_clicked (GTK_BUTTON (toggle_button));
374 }
375
376 void
377 _gtk_toggle_button_set_active (GtkToggleButton *toggle_button,
378                                gboolean         is_active)
379 {
380   toggle_button->priv->active = is_active;
381 }
382
383 gboolean
384 gtk_toggle_button_get_active (GtkToggleButton *toggle_button)
385 {
386   g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE);
387
388   return toggle_button->priv->active;
389 }
390
391
392 void
393 gtk_toggle_button_toggled (GtkToggleButton *toggle_button)
394 {
395   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
396
397   g_signal_emit (toggle_button, toggle_button_signals[TOGGLED], 0);
398 }
399
400 /**
401  * gtk_toggle_button_set_inconsistent:
402  * @toggle_button: a #GtkToggleButton
403  * @setting: %TRUE if state is inconsistent
404  *
405  * If the user has selected a range of elements (such as some text or
406  * spreadsheet cells) that are affected by a toggle button, and the
407  * current values in that range are inconsistent, you may want to
408  * display the toggle in an "in between" state. This function turns on
409  * "in between" display.  Normally you would turn off the inconsistent
410  * state again if the user toggles the toggle button. This has to be
411  * done manually, gtk_toggle_button_set_inconsistent() only affects
412  * visual appearance, it doesn't affect the semantics of the button.
413  * 
414  **/
415 void
416 gtk_toggle_button_set_inconsistent (GtkToggleButton *toggle_button,
417                                     gboolean         setting)
418 {
419   GtkToggleButtonPrivate *priv;
420
421   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
422
423   priv = toggle_button->priv;
424
425   setting = setting != FALSE;
426
427   if (setting != priv->inconsistent)
428     {
429       priv->inconsistent = setting;
430
431       gtk_toggle_button_update_state (GTK_BUTTON (toggle_button));
432       gtk_widget_queue_draw (GTK_WIDGET (toggle_button));
433
434       g_object_notify (G_OBJECT (toggle_button), "inconsistent");      
435     }
436 }
437
438 /**
439  * gtk_toggle_button_get_inconsistent:
440  * @toggle_button: a #GtkToggleButton
441  * 
442  * Gets the value set by gtk_toggle_button_set_inconsistent().
443  * 
444  * Return value: %TRUE if the button is displayed as inconsistent, %FALSE otherwise
445  **/
446 gboolean
447 gtk_toggle_button_get_inconsistent (GtkToggleButton *toggle_button)
448 {
449   g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE);
450
451   return toggle_button->priv->inconsistent;
452 }
453
454 static gint
455 gtk_toggle_button_draw (GtkWidget *widget,
456                         cairo_t   *cr)
457 {
458   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (widget);
459   GtkToggleButtonPrivate *priv = toggle_button->priv;
460   GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
461   GtkButton *button = GTK_BUTTON (widget);
462   GtkStateType state_type;
463   GtkShadowType shadow_type;
464
465   state_type = gtk_widget_get_state (widget);
466
467   if (priv->inconsistent)
468     {
469       if (state_type == GTK_STATE_ACTIVE)
470         state_type = GTK_STATE_NORMAL;
471       shadow_type = GTK_SHADOW_ETCHED_IN;
472     }
473   else
474     shadow_type = button->priv->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
475
476   _gtk_button_paint (button, cr,
477                      gtk_widget_get_allocated_width (widget),
478                      gtk_widget_get_allocated_height (widget),
479                      state_type, shadow_type,
480                      "togglebutton", "togglebuttondefault");
481
482   if (child)
483     gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
484
485   return FALSE;
486 }
487
488 static gboolean
489 gtk_toggle_button_mnemonic_activate (GtkWidget *widget,
490                                      gboolean   group_cycling)
491 {
492   /*
493    * We override the standard implementation in 
494    * gtk_widget_real_mnemonic_activate() in order to focus the widget even
495    * if there is no mnemonic conflict.
496    */
497   if (gtk_widget_get_can_focus (widget))
498     gtk_widget_grab_focus (widget);
499
500   if (!group_cycling)
501     gtk_widget_activate (widget);
502
503   return TRUE;
504 }
505
506 static void
507 gtk_toggle_button_pressed (GtkButton *button)
508 {
509   button->priv->button_down = TRUE;
510
511   gtk_toggle_button_update_state (button);
512   gtk_widget_queue_draw (GTK_WIDGET (button));
513 }
514
515 static void
516 gtk_toggle_button_released (GtkButton *button)
517 {
518   if (button->priv->button_down)
519     {
520       button->priv->button_down = FALSE;
521
522       if (button->priv->in_button)
523         gtk_button_clicked (button);
524
525       gtk_toggle_button_update_state (button);
526       gtk_widget_queue_draw (GTK_WIDGET (button));
527     }
528 }
529
530 static void
531 gtk_toggle_button_clicked (GtkButton *button)
532 {
533   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
534   GtkToggleButtonPrivate *priv = toggle_button->priv;
535
536   priv->active = !priv->active;
537
538   gtk_toggle_button_toggled (toggle_button);
539
540   gtk_toggle_button_update_state (button);
541
542   g_object_notify (G_OBJECT (toggle_button), "active");
543
544   if (GTK_BUTTON_CLASS (gtk_toggle_button_parent_class)->clicked)
545     GTK_BUTTON_CLASS (gtk_toggle_button_parent_class)->clicked (button);
546 }
547
548 static void
549 gtk_toggle_button_update_state (GtkButton *button)
550 {
551   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
552   GtkToggleButtonPrivate *priv = toggle_button->priv;
553   gboolean depressed, touchscreen;
554   GtkStateFlags new_state = 0;
555
556   g_object_get (gtk_widget_get_settings (GTK_WIDGET (button)),
557                 "gtk-touchscreen-mode", &touchscreen,
558                 NULL);
559
560   if (priv->inconsistent)
561     new_state |= GTK_STATE_FLAG_INCONSISTENT;
562
563   if (priv->inconsistent)
564     depressed = FALSE;
565   else if (button->priv->in_button && button->priv->button_down)
566     depressed = TRUE;
567   else
568     depressed = priv->active;
569
570   if (!touchscreen && button->priv->in_button && (!button->priv->button_down || priv->draw_indicator))
571     new_state |= GTK_STATE_FLAG_PRELIGHT;
572   else if (depressed)
573     new_state |= GTK_STATE_FLAG_ACTIVE;
574
575   _gtk_button_set_depressed (button, depressed);
576   gtk_widget_set_state_flags (GTK_WIDGET (toggle_button), new_state, TRUE);
577 }