]> Pileus Git - ~andy/gtk/blob - gtk/gtktogglebutton.c
GtkToggleButton: Make it able to be prelight and active at the same time.
[~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       GtkStyleContext *context;
333
334       priv->draw_indicator = draw_indicator;
335       GTK_BUTTON (toggle_button)->priv->depress_on_activate = !draw_indicator;
336
337       if (gtk_widget_get_visible (GTK_WIDGET (toggle_button)))
338         gtk_widget_queue_resize (GTK_WIDGET (toggle_button));
339
340       g_object_notify (G_OBJECT (toggle_button), "draw-indicator");
341
342       /* Make toggle buttons conditionally have the "button"
343        * class depending on draw_indicator.
344        */
345       context = gtk_widget_get_style_context (GTK_WIDGET (toggle_button));
346
347       if (draw_indicator)
348         gtk_style_context_remove_class (context, GTK_STYLE_CLASS_BUTTON);
349       else
350         gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
351     }
352 }
353
354 /**
355  * gtk_toggle_button_get_mode:
356  * @toggle_button: a #GtkToggleButton
357  *
358  * Retrieves whether the button is displayed as a separate indicator
359  * and label. See gtk_toggle_button_set_mode().
360  *
361  * Return value: %TRUE if the togglebutton is drawn as a separate indicator
362  *   and label.
363  **/
364 gboolean
365 gtk_toggle_button_get_mode (GtkToggleButton *toggle_button)
366 {
367   g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE);
368
369   return toggle_button->priv->draw_indicator;
370 }
371
372 void
373 gtk_toggle_button_set_active (GtkToggleButton *toggle_button,
374                               gboolean         is_active)
375 {
376   GtkToggleButtonPrivate *priv;
377
378   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
379
380   priv = toggle_button->priv;
381
382   is_active = is_active != FALSE;
383
384   if (priv->active != is_active)
385     gtk_button_clicked (GTK_BUTTON (toggle_button));
386 }
387
388 void
389 _gtk_toggle_button_set_active (GtkToggleButton *toggle_button,
390                                gboolean         is_active)
391 {
392   toggle_button->priv->active = is_active;
393 }
394
395 gboolean
396 gtk_toggle_button_get_active (GtkToggleButton *toggle_button)
397 {
398   g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE);
399
400   return toggle_button->priv->active;
401 }
402
403
404 void
405 gtk_toggle_button_toggled (GtkToggleButton *toggle_button)
406 {
407   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
408
409   g_signal_emit (toggle_button, toggle_button_signals[TOGGLED], 0);
410 }
411
412 /**
413  * gtk_toggle_button_set_inconsistent:
414  * @toggle_button: a #GtkToggleButton
415  * @setting: %TRUE if state is inconsistent
416  *
417  * If the user has selected a range of elements (such as some text or
418  * spreadsheet cells) that are affected by a toggle button, and the
419  * current values in that range are inconsistent, you may want to
420  * display the toggle in an "in between" state. This function turns on
421  * "in between" display.  Normally you would turn off the inconsistent
422  * state again if the user toggles the toggle button. This has to be
423  * done manually, gtk_toggle_button_set_inconsistent() only affects
424  * visual appearance, it doesn't affect the semantics of the button.
425  * 
426  **/
427 void
428 gtk_toggle_button_set_inconsistent (GtkToggleButton *toggle_button,
429                                     gboolean         setting)
430 {
431   GtkToggleButtonPrivate *priv;
432
433   g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
434
435   priv = toggle_button->priv;
436
437   setting = setting != FALSE;
438
439   if (setting != priv->inconsistent)
440     {
441       priv->inconsistent = setting;
442
443       gtk_toggle_button_update_state (GTK_BUTTON (toggle_button));
444       gtk_widget_queue_draw (GTK_WIDGET (toggle_button));
445
446       g_object_notify (G_OBJECT (toggle_button), "inconsistent");      
447     }
448 }
449
450 /**
451  * gtk_toggle_button_get_inconsistent:
452  * @toggle_button: a #GtkToggleButton
453  * 
454  * Gets the value set by gtk_toggle_button_set_inconsistent().
455  * 
456  * Return value: %TRUE if the button is displayed as inconsistent, %FALSE otherwise
457  **/
458 gboolean
459 gtk_toggle_button_get_inconsistent (GtkToggleButton *toggle_button)
460 {
461   g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE);
462
463   return toggle_button->priv->inconsistent;
464 }
465
466 static gint
467 gtk_toggle_button_draw (GtkWidget *widget,
468                         cairo_t   *cr)
469 {
470   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (widget);
471   GtkToggleButtonPrivate *priv = toggle_button->priv;
472   GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
473   GtkButton *button = GTK_BUTTON (widget);
474   GtkStateType state_type;
475   GtkShadowType shadow_type;
476
477   state_type = gtk_widget_get_state (widget);
478
479   if (priv->inconsistent)
480     {
481       if (state_type == GTK_STATE_ACTIVE)
482         state_type = GTK_STATE_NORMAL;
483       shadow_type = GTK_SHADOW_ETCHED_IN;
484     }
485   else
486     shadow_type = button->priv->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
487
488   _gtk_button_paint (button, cr,
489                      gtk_widget_get_allocated_width (widget),
490                      gtk_widget_get_allocated_height (widget),
491                      state_type, shadow_type,
492                      "togglebutton", "togglebuttondefault");
493
494   if (child)
495     gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
496
497   return FALSE;
498 }
499
500 static gboolean
501 gtk_toggle_button_mnemonic_activate (GtkWidget *widget,
502                                      gboolean   group_cycling)
503 {
504   /*
505    * We override the standard implementation in 
506    * gtk_widget_real_mnemonic_activate() in order to focus the widget even
507    * if there is no mnemonic conflict.
508    */
509   if (gtk_widget_get_can_focus (widget))
510     gtk_widget_grab_focus (widget);
511
512   if (!group_cycling)
513     gtk_widget_activate (widget);
514
515   return TRUE;
516 }
517
518 static void
519 gtk_toggle_button_pressed (GtkButton *button)
520 {
521   button->priv->button_down = TRUE;
522
523   gtk_toggle_button_update_state (button);
524   gtk_widget_queue_draw (GTK_WIDGET (button));
525 }
526
527 static void
528 gtk_toggle_button_released (GtkButton *button)
529 {
530   if (button->priv->button_down)
531     {
532       button->priv->button_down = FALSE;
533
534       if (button->priv->in_button)
535         gtk_button_clicked (button);
536
537       gtk_toggle_button_update_state (button);
538       gtk_widget_queue_draw (GTK_WIDGET (button));
539     }
540 }
541
542 static void
543 gtk_toggle_button_clicked (GtkButton *button)
544 {
545   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
546   GtkToggleButtonPrivate *priv = toggle_button->priv;
547
548   priv->active = !priv->active;
549
550   gtk_toggle_button_toggled (toggle_button);
551
552   gtk_toggle_button_update_state (button);
553
554   g_object_notify (G_OBJECT (toggle_button), "active");
555
556   if (GTK_BUTTON_CLASS (gtk_toggle_button_parent_class)->clicked)
557     GTK_BUTTON_CLASS (gtk_toggle_button_parent_class)->clicked (button);
558 }
559
560 static void
561 gtk_toggle_button_update_state (GtkButton *button)
562 {
563   GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
564   GtkToggleButtonPrivate *priv = toggle_button->priv;
565   gboolean depressed, touchscreen;
566   GtkStateFlags new_state = 0;
567
568   g_object_get (gtk_widget_get_settings (GTK_WIDGET (button)),
569                 "gtk-touchscreen-mode", &touchscreen,
570                 NULL);
571
572   if (priv->inconsistent)
573     new_state |= GTK_STATE_FLAG_INCONSISTENT;
574
575   if (priv->inconsistent)
576     depressed = FALSE;
577   else if (button->priv->in_button && button->priv->button_down)
578     depressed = TRUE;
579   else
580     depressed = priv->active;
581
582   if (!touchscreen && button->priv->in_button && (!button->priv->button_down || priv->draw_indicator))
583     new_state |= GTK_STATE_FLAG_PRELIGHT;
584
585   if (depressed)
586     new_state |= GTK_STATE_FLAG_ACTIVE;
587
588   _gtk_button_set_depressed (button, depressed);
589   gtk_widget_set_state_flags (GTK_WIDGET (toggle_button), new_state, TRUE);
590 }