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