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, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gtktogglebutton.h"
28
29#include "gtkaccessible.h"
30#include "gtkbuttonprivate.h"
31#include "gtkenums.h"
32#include "gtkintl.h"
33#include "gtklabel.h"
34#include "gtkmain.h"
35#include "gtkmarshalers.h"
36#include "gtkprivate.h"
37
38/**
39 * GtkToggleButton:
40 *
41 * A `GtkToggleButton` is a button which remains “pressed-in” when
42 * clicked.
43 *
44 * Clicking again will cause the toggle button to return to its normal state.
45 *
46 * A toggle button is created by calling either [ctor@Gtk.ToggleButton.new] or
47 * [ctor@Gtk.ToggleButton.new_with_label]. If using the former, it is advisable
48 * to pack a widget, (such as a `GtkLabel` and/or a `GtkImage`), into the toggle
49 * button’s container. (See [class@Gtk.Button] for more information).
50 *
51 * The state of a `GtkToggleButton` can be set specifically using
52 * [method@Gtk.ToggleButton.set_active], and retrieved using
53 * [method@Gtk.ToggleButton.get_active].
54 *
55 * To simply switch the state of a toggle button, use
56 * [method@Gtk.ToggleButton.toggled].
57 *
58 * ## Grouping
59 *
60 * Toggle buttons can be grouped together, to form mutually exclusive
61 * groups - only one of the buttons can be toggled at a time, and toggling
62 * another one will switch the currently toggled one off.
63 *
64 * To add a `GtkToggleButton` to a group, use [method@Gtk.ToggleButton.set_group].
65 *
66 * ## CSS nodes
67 *
68 * `GtkToggleButton` has a single CSS node with name button. To differentiate
69 * it from a plain `GtkButton`, it gets the `.toggle` style class.
70 *
71 * ## Creating two `GtkToggleButton` widgets.
72 *
73 * ```c
74 * static void
75 * output_state (GtkToggleButton *source,
76 * gpointer user_data)
77 * {
78 * g_print ("Toggle button "%s" is active: %s",
79 * gtk_button_get_label (GTK_BUTTON (source)),
80 * gtk_toggle_button_get_active (source) ? "Yes" : "No");
81 * }
82 *
83 * static void
84 * make_toggles (void)
85 * {
86 * GtkWidget *window, *toggle1, *toggle2;
87 * GtkWidget *box;
88 * const char *text;
89 *
90 * window = gtk_window_new ();
91 * box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
92 *
93 * text = "Hi, I’m toggle button one";
94 * toggle1 = gtk_toggle_button_new_with_label (text);
95 *
96 * g_signal_connect (toggle1, "toggled",
97 * G_CALLBACK (output_state),
98 * NULL);
99 * gtk_box_append (GTK_BOX (box), toggle1);
100 *
101 * text = "Hi, I’m toggle button two";
102 * toggle2 = gtk_toggle_button_new_with_label (text);
103 * g_signal_connect (toggle2, "toggled",
104 * G_CALLBACK (output_state),
105 * NULL);
106 * gtk_box_append (GTK_BOX (box), toggle2);
107 *
108 * gtk_window_set_child (GTK_WINDOW (window), box);
109 * gtk_widget_show (window);
110 * }
111 * ```
112 */
113
114typedef struct _GtkToggleButtonPrivate GtkToggleButtonPrivate;
115struct _GtkToggleButtonPrivate
116{
117 GtkToggleButton *group_next;
118 GtkToggleButton *group_prev;
119
120 guint active : 1;
121};
122
123enum {
124 TOGGLED,
125 LAST_SIGNAL
126};
127
128enum {
129 PROP_0,
130 PROP_ACTIVE,
131 PROP_GROUP,
132 NUM_PROPERTIES
133};
134
135static guint toggle_button_signals[LAST_SIGNAL] = { 0 };
136static GParamSpec *toggle_button_props[NUM_PROPERTIES] = { NULL, };
137
138G_DEFINE_TYPE_WITH_CODE (GtkToggleButton, gtk_toggle_button, GTK_TYPE_BUTTON,
139 G_ADD_PRIVATE (GtkToggleButton))
140
141static void
142gtk_toggle_button_set_property (GObject *object,
143 guint prop_id,
144 const GValue *value,
145 GParamSpec *pspec)
146{
147 GtkToggleButton *tb = GTK_TOGGLE_BUTTON (object);
148
149 switch (prop_id)
150 {
151 case PROP_ACTIVE:
152 gtk_toggle_button_set_active (toggle_button: tb, is_active: g_value_get_boolean (value));
153 break;
154 case PROP_GROUP:
155 gtk_toggle_button_set_group (GTK_TOGGLE_BUTTON (object), group: g_value_get_object (value));
156 break;
157 default:
158 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
159 break;
160 }
161}
162
163static void
164gtk_toggle_button_get_property (GObject *object,
165 guint prop_id,
166 GValue *value,
167 GParamSpec *pspec)
168{
169 GtkToggleButton *tb = GTK_TOGGLE_BUTTON (object);
170 GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: tb);
171
172 switch (prop_id)
173 {
174 case PROP_ACTIVE:
175 g_value_set_boolean (value, v_boolean: priv->active);
176 break;
177 default:
178 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
179 break;
180 }
181}
182
183static gboolean
184gtk_toggle_button_mnemonic_activate (GtkWidget *widget,
185 gboolean group_cycling)
186{
187 /*
188 * We override the standard implementation in
189 * gtk_widget_real_mnemonic_activate() in order to focus the widget even
190 * if there is no mnemonic conflict.
191 */
192 if (gtk_widget_get_focusable (widget))
193 gtk_widget_grab_focus (widget);
194
195 if (!group_cycling)
196 gtk_widget_activate (widget);
197
198 return TRUE;
199}
200
201static void
202gtk_toggle_button_clicked (GtkButton *button)
203{
204 GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (button);
205 GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button);
206
207 if (priv->active && (priv->group_prev || priv->group_next))
208 return;
209
210 if (gtk_button_get_action_helper (button))
211 return;
212
213 gtk_toggle_button_set_active (toggle_button, is_active: !priv->active);
214}
215
216static void
217gtk_toggle_button_dispose (GObject *object)
218{
219 GtkToggleButton *toggle_button = GTK_TOGGLE_BUTTON (object);
220
221 gtk_toggle_button_set_group (toggle_button, NULL);
222
223 G_OBJECT_CLASS (gtk_toggle_button_parent_class)->dispose (object);
224}
225
226static GtkToggleButton *
227get_group_next (GtkToggleButton *self)
228{
229 return ((GtkToggleButtonPrivate *)gtk_toggle_button_get_instance_private (self))->group_next;
230}
231
232static GtkToggleButton *
233get_group_prev (GtkToggleButton *self)
234{
235 return ((GtkToggleButtonPrivate *)gtk_toggle_button_get_instance_private (self))->group_prev;
236}
237
238static GtkToggleButton *
239get_group_first (GtkToggleButton *self)
240{
241 GtkToggleButton *group_first = NULL;
242 GtkToggleButton *iter;
243
244 /* Find first in group */
245 iter = self;
246 while (iter)
247 {
248 group_first = iter;
249
250 iter = get_group_prev (self: iter);
251 if (!iter)
252 break;
253 }
254
255 g_assert (group_first);
256
257 return group_first;
258}
259
260static void
261gtk_toggle_button_class_init (GtkToggleButtonClass *class)
262{
263 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
264 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
265 GtkButtonClass *button_class = GTK_BUTTON_CLASS (class);
266
267 gobject_class->dispose = gtk_toggle_button_dispose;
268 gobject_class->set_property = gtk_toggle_button_set_property;
269 gobject_class->get_property = gtk_toggle_button_get_property;
270
271 widget_class->mnemonic_activate = gtk_toggle_button_mnemonic_activate;
272
273 button_class->clicked = gtk_toggle_button_clicked;
274
275 class->toggled = NULL;
276
277 /**
278 * GtkToggleButton:active: (attributes org.gtk.Property.get=gtk_toggle_button_get_active org.gtk.Property.set=gtk_toggle_button_set_active)
279 *
280 * If the toggle button should be pressed in.
281 */
282 toggle_button_props[PROP_ACTIVE] =
283 g_param_spec_boolean (name: "active",
284 P_("Active"),
285 P_("If the toggle button should be pressed in"),
286 FALSE,
287 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
288
289 /**
290 * GtkToggleButton:group: (attributes org.gtk.Property.set=gtk_toggle_button_set_group)
291 *
292 * The toggle button whose group this widget belongs to.
293 */
294 toggle_button_props[PROP_GROUP] =
295 g_param_spec_object (name: "group",
296 P_("Group"),
297 P_("The toggle button whose group this widget belongs to."),
298 GTK_TYPE_TOGGLE_BUTTON,
299 GTK_PARAM_WRITABLE);
300
301 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: toggle_button_props);
302
303 /**
304 * GtkToggleButton::toggled:
305 * @togglebutton: the object which received the signal.
306 *
307 * Emitted whenever the `GtkToggleButton`'s state is changed.
308 */
309 toggle_button_signals[TOGGLED] =
310 g_signal_new (I_("toggled"),
311 G_OBJECT_CLASS_TYPE (gobject_class),
312 signal_flags: G_SIGNAL_RUN_FIRST,
313 G_STRUCT_OFFSET (GtkToggleButtonClass, toggled),
314 NULL, NULL,
315 NULL,
316 G_TYPE_NONE, n_params: 0);
317
318 gtk_widget_class_set_css_name (widget_class, I_("button"));
319}
320
321static void
322gtk_toggle_button_init (GtkToggleButton *toggle_button)
323{
324 GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button);
325
326 priv->active = FALSE;
327
328 gtk_widget_add_css_class (GTK_WIDGET (toggle_button), css_class: "toggle");
329}
330
331
332/**
333 * gtk_toggle_button_new:
334 *
335 * Creates a new toggle button.
336 *
337 * A widget should be packed into the button, as in [ctor@Gtk.Button.new].
338 *
339 * Returns: a new toggle button.
340 */
341GtkWidget *
342gtk_toggle_button_new (void)
343{
344 return g_object_new (GTK_TYPE_TOGGLE_BUTTON, NULL);
345}
346
347/**
348 * gtk_toggle_button_new_with_label:
349 * @label: a string containing the message to be placed in the toggle button.
350 *
351 * Creates a new toggle button with a text label.
352 *
353 * Returns: a new toggle button.
354 */
355GtkWidget *
356gtk_toggle_button_new_with_label (const char *label)
357{
358 return g_object_new (GTK_TYPE_TOGGLE_BUTTON, first_property_name: "label", label, NULL);
359}
360
361/**
362 * gtk_toggle_button_new_with_mnemonic:
363 * @label: the text of the button, with an underscore in front of the
364 * mnemonic character
365 *
366 * Creates a new `GtkToggleButton` containing a label.
367 *
368 * The label will be created using [ctor@Gtk.Label.new_with_mnemonic],
369 * so underscores in @label indicate the mnemonic for the button.
370 *
371 * Returns: a new `GtkToggleButton`
372 */
373GtkWidget *
374gtk_toggle_button_new_with_mnemonic (const char *label)
375{
376 return g_object_new (GTK_TYPE_TOGGLE_BUTTON,
377 first_property_name: "label", label,
378 "use-underline", TRUE,
379 NULL);
380}
381
382/**
383 * gtk_toggle_button_set_active: (attributes org.gtk.Method.set_property=active)
384 * @toggle_button: a `GtkToggleButton`.
385 * @is_active: %TRUE or %FALSE.
386 *
387 * Sets the status of the toggle button.
388 *
389 * Set to %TRUE if you want the `GtkToggleButton` to be “pressed in”,
390 * and %FALSE to raise it.
391 *
392 * If the status of the button changes, this action causes the
393 * [signal@GtkToggleButton::toggled] signal to be emitted.
394 */
395void
396gtk_toggle_button_set_active (GtkToggleButton *toggle_button,
397 gboolean is_active)
398{
399 GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button);
400
401 g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
402
403 is_active = is_active != FALSE;
404
405 if (priv->active == is_active)
406 return;
407
408 if (is_active && (priv->group_prev || priv->group_next))
409 {
410 GtkToggleButton *group_first = NULL;
411 GtkToggleButton *iter;
412
413 group_first = get_group_first (self: toggle_button);
414 g_assert (group_first);
415
416 /* Set all buttons in group to !active */
417 for (iter = group_first; iter; iter = get_group_next (self: iter))
418 gtk_toggle_button_set_active (toggle_button: iter, FALSE);
419
420 /* ... and the next code block will set this one to active */
421 }
422
423 priv->active = is_active;
424
425 if (is_active)
426 gtk_widget_set_state_flags (GTK_WIDGET (toggle_button), flags: GTK_STATE_FLAG_CHECKED, FALSE);
427 else
428 gtk_widget_unset_state_flags (GTK_WIDGET (toggle_button), flags: GTK_STATE_FLAG_CHECKED);
429
430 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: toggle_button),
431 first_state: GTK_ACCESSIBLE_STATE_PRESSED, is_active,
432 -1);
433
434 gtk_toggle_button_toggled (toggle_button);
435
436 g_object_notify_by_pspec (G_OBJECT (toggle_button), pspec: toggle_button_props[PROP_ACTIVE]);
437}
438
439/**
440 * gtk_toggle_button_get_active: (attributes org.gtk.Method.get_property=active)
441 * @toggle_button: a `GtkToggleButton`.
442 *
443 * Queries a `GtkToggleButton` and returns its current state.
444 *
445 * Returns %TRUE if the toggle button is pressed in and %FALSE
446 * if it is raised.
447 *
448 * Returns: whether the button is pressed
449 */
450gboolean
451gtk_toggle_button_get_active (GtkToggleButton *toggle_button)
452{
453 GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button);
454
455 g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button), FALSE);
456
457 return priv->active;
458}
459
460/**
461 * gtk_toggle_button_toggled:
462 * @toggle_button: a `GtkToggleButton`.
463 *
464 * Emits the ::toggled signal on the `GtkToggleButton`.
465 *
466 * There is no good reason for an application ever to call this function.
467 */
468void
469gtk_toggle_button_toggled (GtkToggleButton *toggle_button)
470{
471 g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
472
473 g_signal_emit (instance: toggle_button, signal_id: toggle_button_signals[TOGGLED], detail: 0);
474}
475
476/**
477 * gtk_toggle_button_set_group: (attributes org.gtk.Method.set_property=group)
478 * @toggle_button: a `GtkToggleButton`
479 * @group: (nullable) (transfer none): another `GtkToggleButton` to
480 * form a group with
481 *
482 * Adds @self to the group of @group.
483 *
484 * In a group of multiple toggle buttons, only one button can be active
485 * at a time.
486 *
487 * Setting up groups in a cycle leads to undefined behavior.
488 *
489 * Note that the same effect can be achieved via the [iface@Gtk.Actionable]
490 * API, by using the same action with parameter type and state type 's'
491 * for all buttons in the group, and giving each button its own target
492 * value.
493 */
494void
495gtk_toggle_button_set_group (GtkToggleButton *toggle_button,
496 GtkToggleButton *group)
497{
498 GtkToggleButtonPrivate *priv = gtk_toggle_button_get_instance_private (self: toggle_button);
499 GtkToggleButtonPrivate *group_priv = gtk_toggle_button_get_instance_private (self: group);
500
501 g_return_if_fail (GTK_IS_TOGGLE_BUTTON (toggle_button));
502 g_return_if_fail (toggle_button != group);
503
504 if (!group)
505 {
506 if (priv->group_prev)
507 {
508 GtkToggleButtonPrivate *p = gtk_toggle_button_get_instance_private (self: priv->group_prev);
509 p->group_next = priv->group_next;
510 }
511 if (priv->group_next)
512 {
513 GtkToggleButtonPrivate *p = gtk_toggle_button_get_instance_private (self: priv->group_next);
514 p->group_prev = priv->group_prev;
515 }
516
517 priv->group_next = NULL;
518 priv->group_prev = NULL;
519 g_object_notify_by_pspec (G_OBJECT (toggle_button), pspec: toggle_button_props[PROP_GROUP]);
520 return;
521 }
522
523 if (priv->group_next == group)
524 return;
525
526 priv->group_prev = NULL;
527 if (group_priv->group_prev)
528 {
529 GtkToggleButtonPrivate *prev = gtk_toggle_button_get_instance_private (self: group_priv->group_prev);
530
531 prev->group_next = toggle_button;
532 priv->group_prev = group_priv->group_prev;
533 }
534
535 group_priv->group_prev = toggle_button;
536 priv->group_next = group;
537
538 g_object_notify_by_pspec (G_OBJECT (toggle_button), pspec: toggle_button_props[PROP_GROUP]);
539}
540

source code of gtk/gtk/gtktogglebutton.c