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 "gtkcheckbutton.h"
28
29#include "gtkactionhelperprivate.h"
30#include "gtkboxlayout.h"
31#include "gtkbuiltiniconprivate.h"
32#include "gtkcssnumbervalueprivate.h"
33#include "gtkgestureclick.h"
34#include "gtkintl.h"
35#include "gtklabel.h"
36#include "gtkprivate.h"
37#include "gtkshortcuttrigger.h"
38#include "gtkcssnodeprivate.h"
39#include "gtkwidgetprivate.h"
40#include "gtkmodelbuttonprivate.h"
41
42/**
43 * GtkCheckButton:
44 *
45 * A `GtkCheckButton` places a label next to an indicator.
46 *
47 * ![Example GtkCheckButtons](check-button.png)
48 *
49 * A `GtkCheckButton` is created by calling either [ctor@Gtk.CheckButton.new]
50 * or [ctor@Gtk.CheckButton.new_with_label].
51 *
52 * The state of a `GtkCheckButton` can be set specifically using
53 * [method@Gtk.CheckButton.set_active], and retrieved using
54 * [method@Gtk.CheckButton.get_active].
55 *
56 * # Inconsistent state
57 *
58 * In addition to "on" and "off", check buttons can be an
59 * "in between" state that is neither on nor off. This can be used
60 * e.g. when the user has selected a range of elements (such as some
61 * text or spreadsheet cells) that are affected by a check button,
62 * and the current values in that range are inconsistent.
63 *
64 * To set a `GtkCheckButton` to inconsistent state, use
65 * [method@Gtk.CheckButton.set_inconsistent].
66 *
67 * # Grouping
68 *
69 * Check buttons can be grouped together, to form mutually exclusive
70 * groups - only one of the buttons can be toggled at a time, and toggling
71 * another one will switch the currently toggled one off.
72 *
73 * Grouped check buttons use a different indicator, and are commonly referred
74 * to as *radio buttons*.
75 *
76 * ![Example GtkCheckButtons](radio-button.png)
77 *
78 * To add a `GtkCheckButton` to a group, use [method@Gtk.CheckButton.set_group].
79 *
80 * # CSS nodes
81 *
82 * ```
83 * checkbutton[.text-button]
84 * ├── check
85 * ╰── [label]
86 * ```
87 *
88 * A `GtkCheckButton` has a main node with name checkbutton. If the
89 * [property@Gtk.CheckButton:label] property is set, it contains a label
90 * child. The indicator node is named check when no group is set, and
91 * radio if the checkbutton is grouped together with other checkbuttons.
92 *
93 * # Accessibility
94 *
95 * `GtkCheckButton` uses the %GTK_ACCESSIBLE_ROLE_CHECKBOX role.
96 */
97
98typedef struct {
99 GtkWidget *indicator_widget;
100 GtkWidget *label_widget;
101
102 guint inconsistent: 1;
103 guint active: 1;
104 guint use_underline: 1;
105
106 GtkCheckButton *group_next;
107 GtkCheckButton *group_prev;
108
109 GtkActionHelper *action_helper;
110} GtkCheckButtonPrivate;
111
112enum {
113 PROP_0,
114 PROP_ACTIVE,
115 PROP_GROUP,
116 PROP_LABEL,
117 PROP_INCONSISTENT,
118 PROP_USE_UNDERLINE,
119
120 /* actionable properties */
121 PROP_ACTION_NAME,
122 PROP_ACTION_TARGET,
123 LAST_PROP = PROP_ACTION_NAME
124};
125
126enum {
127 TOGGLED,
128 ACTIVATE,
129 LAST_SIGNAL
130};
131
132static void gtk_check_button_actionable_iface_init (GtkActionableInterface *iface);
133
134static guint signals[LAST_SIGNAL] = { 0 };
135static GParamSpec *props[LAST_PROP] = { NULL, };
136
137G_DEFINE_TYPE_WITH_CODE (GtkCheckButton, gtk_check_button, GTK_TYPE_WIDGET,
138 G_ADD_PRIVATE (GtkCheckButton)
139 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, gtk_check_button_actionable_iface_init))
140
141static void
142gtk_check_button_dispose (GObject *object)
143{
144 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (GTK_CHECK_BUTTON (object));
145
146 g_clear_object (&priv->action_helper);
147
148 g_clear_pointer (&priv->indicator_widget, gtk_widget_unparent);
149 g_clear_pointer (&priv->label_widget, gtk_widget_unparent);
150
151 gtk_check_button_set_group (GTK_CHECK_BUTTON (object), NULL);
152
153 G_OBJECT_CLASS (gtk_check_button_parent_class)->dispose (object);
154}
155
156static void
157button_role_changed (GtkCheckButton *self)
158{
159 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
160
161 if (gtk_action_helper_get_role (helper: priv->action_helper) == GTK_BUTTON_ROLE_RADIO)
162 gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget),
163 name: g_quark_from_static_string(string: "radio"));
164 else
165 gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget),
166 name: g_quark_from_static_string(string: "check"));
167}
168
169static void
170ensure_action_helper (GtkCheckButton *self)
171{
172 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
173
174 if (priv->action_helper)
175 return;
176
177 priv->action_helper = gtk_action_helper_new (GTK_ACTIONABLE (self));
178 g_signal_connect_swapped (priv->action_helper, "notify::role",
179 G_CALLBACK (button_role_changed), self);
180}
181
182static void
183gtk_check_button_set_action_name (GtkActionable *actionable,
184 const char *action_name)
185{
186 GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
187 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
188
189 ensure_action_helper (self);
190
191 gtk_action_helper_set_action_name (helper: priv->action_helper, action_name);
192}
193
194static void
195gtk_check_button_set_action_target_value (GtkActionable *actionable,
196 GVariant *action_target)
197{
198 GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
199 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
200
201 ensure_action_helper (self);
202
203 gtk_action_helper_set_action_target_value (helper: priv->action_helper, action_target);
204}
205
206static void
207gtk_check_button_set_property (GObject *object,
208 guint prop_id,
209 const GValue *value,
210 GParamSpec *pspec)
211{
212 switch (prop_id)
213 {
214 case PROP_ACTIVE:
215 gtk_check_button_set_active (GTK_CHECK_BUTTON (object), setting: g_value_get_boolean (value));
216 break;
217 case PROP_GROUP:
218 gtk_check_button_set_group (GTK_CHECK_BUTTON (object), group: g_value_get_object (value));
219 break;
220 case PROP_LABEL:
221 gtk_check_button_set_label (GTK_CHECK_BUTTON (object), label: g_value_get_string (value));
222 break;
223 case PROP_INCONSISTENT:
224 gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (object), inconsistent: g_value_get_boolean (value));
225 break;
226 case PROP_USE_UNDERLINE:
227 gtk_check_button_set_use_underline (GTK_CHECK_BUTTON (object), setting: g_value_get_boolean (value));
228 break;
229 case PROP_ACTION_NAME:
230 gtk_check_button_set_action_name (GTK_ACTIONABLE (object), action_name: g_value_get_string (value));
231 break;
232 case PROP_ACTION_TARGET:
233 gtk_check_button_set_action_target_value (GTK_ACTIONABLE (object), action_target: g_value_get_variant (value));
234 break;
235 default:
236 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
237 break;
238 }
239}
240
241static void
242gtk_check_button_get_property (GObject *object,
243 guint prop_id,
244 GValue *value,
245 GParamSpec *pspec)
246{
247 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (GTK_CHECK_BUTTON (object));
248
249 switch (prop_id)
250 {
251 case PROP_ACTIVE:
252 g_value_set_boolean (value, v_boolean: gtk_check_button_get_active (GTK_CHECK_BUTTON (object)));
253 break;
254 case PROP_LABEL:
255 g_value_set_string (value, v_string: gtk_check_button_get_label (GTK_CHECK_BUTTON (object)));
256 break;
257 case PROP_INCONSISTENT:
258 g_value_set_boolean (value, v_boolean: gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (object)));
259 break;
260 case PROP_USE_UNDERLINE:
261 g_value_set_boolean (value, v_boolean: gtk_check_button_get_use_underline (GTK_CHECK_BUTTON (object)));
262 break;
263 case PROP_ACTION_NAME:
264 g_value_set_string (value, v_string: gtk_action_helper_get_action_name (helper: priv->action_helper));
265 break;
266 case PROP_ACTION_TARGET:
267 g_value_set_variant (value, variant: gtk_action_helper_get_action_target_value (helper: priv->action_helper));
268 break;
269 default:
270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
271 break;
272 }
273}
274
275static const char *
276gtk_check_button_get_action_name (GtkActionable *actionable)
277{
278 GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
279 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
280
281 return gtk_action_helper_get_action_name (helper: priv->action_helper);
282}
283
284static GVariant *
285gtk_check_button_get_action_target_value (GtkActionable *actionable)
286{
287 GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
288 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
289
290 return gtk_action_helper_get_action_target_value (helper: priv->action_helper);
291}
292
293static void
294gtk_check_button_actionable_iface_init (GtkActionableInterface *iface)
295{
296 iface->get_action_name = gtk_check_button_get_action_name;
297 iface->set_action_name = gtk_check_button_set_action_name;
298 iface->get_action_target_value = gtk_check_button_get_action_target_value;
299 iface->set_action_target_value = gtk_check_button_set_action_target_value;
300}
301
302static void
303click_pressed_cb (GtkGestureClick *gesture,
304 guint n_press,
305 double x,
306 double y,
307 GtkWidget *widget)
308{
309 if (gtk_widget_get_focus_on_click (widget) && !gtk_widget_has_focus (widget))
310 gtk_widget_grab_focus (widget);
311}
312
313static void
314click_released_cb (GtkGestureClick *gesture,
315 guint n_press,
316 double x,
317 double y,
318 GtkWidget *widget)
319{
320 GtkCheckButton *self = GTK_CHECK_BUTTON (widget);
321 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
322
323 if (priv->active && (priv->group_prev || priv->group_next))
324 return;
325
326 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
327 if (gtk_widget_is_sensitive (widget) &&
328 gtk_widget_contains (widget, x, y))
329 {
330 if (priv->action_helper)
331 gtk_action_helper_activate (helper: priv->action_helper);
332 else
333 gtk_check_button_set_active (self, setting: !priv->active);
334 }
335}
336
337static void
338update_accessible_state (GtkCheckButton *check_button)
339{
340 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self: check_button);
341
342 GtkAccessibleTristate checked_state;
343
344 if (priv->inconsistent)
345 checked_state = GTK_ACCESSIBLE_TRISTATE_MIXED;
346 else if (priv->active)
347 checked_state = GTK_ACCESSIBLE_TRISTATE_TRUE;
348 else
349 checked_state = GTK_ACCESSIBLE_TRISTATE_FALSE;
350
351 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: check_button),
352 first_state: GTK_ACCESSIBLE_STATE_CHECKED, checked_state,
353 -1);
354}
355
356
357static GtkCheckButton *
358get_group_next (GtkCheckButton *self)
359{
360 return ((GtkCheckButtonPrivate *)gtk_check_button_get_instance_private (self))->group_next;
361}
362
363static GtkCheckButton *
364get_group_prev (GtkCheckButton *self)
365{
366 return ((GtkCheckButtonPrivate *)gtk_check_button_get_instance_private (self))->group_prev;
367}
368
369static GtkCheckButton *
370get_group_first (GtkCheckButton *self)
371{
372 GtkCheckButton *group_first = NULL;
373 GtkCheckButton *iter;
374
375 /* Find first in group */
376 iter = self;
377 while (iter)
378 {
379 group_first = iter;
380
381 iter = get_group_prev (self: iter);
382 if (!iter)
383 break;
384 }
385
386 g_assert (group_first);
387
388 return group_first;
389}
390
391static GtkCheckButton *
392get_group_active_button (GtkCheckButton *self)
393{
394 GtkCheckButton *iter;
395
396 for (iter = get_group_first (self); iter; iter = get_group_next (self: iter))
397 {
398 if (gtk_check_button_get_active (self: iter))
399 return iter;
400 }
401
402 return NULL;
403}
404
405static void
406gtk_check_button_state_flags_changed (GtkWidget *widget,
407 GtkStateFlags previous_flags)
408{
409 GtkCheckButton *self = GTK_CHECK_BUTTON (widget);
410 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
411 GtkStateFlags state = gtk_widget_get_state_flags (GTK_WIDGET (self));
412
413 gtk_widget_set_state_flags (widget: priv->indicator_widget, flags: state, TRUE);
414
415 GTK_WIDGET_CLASS (gtk_check_button_parent_class)->state_flags_changed (widget, previous_flags);
416}
417
418static gboolean
419gtk_check_button_focus (GtkWidget *widget,
420 GtkDirectionType direction)
421{
422 GtkCheckButton *self = GTK_CHECK_BUTTON (widget);
423
424 if (gtk_widget_is_focus (widget))
425 {
426 GtkCheckButton *iter;
427 GPtrArray *child_array;
428 GtkWidget *new_focus = NULL;
429 guint index;
430 gboolean found;
431 guint i;
432
433 if (direction == GTK_DIR_TAB_FORWARD ||
434 direction == GTK_DIR_TAB_BACKWARD)
435 return FALSE;
436
437 child_array = g_ptr_array_new ();
438 for (iter = get_group_first (self); iter; iter = get_group_next (self: iter))
439 g_ptr_array_add (array: child_array, data: iter);
440
441 gtk_widget_focus_sort (widget, direction, focus_order: child_array);
442 found = g_ptr_array_find (haystack: child_array, needle: widget, index_: &index);
443
444 if (found)
445 {
446 /* Start at the *next* widget in the list */
447 if (index < child_array->len - 1)
448 index ++;
449 }
450 else
451 {
452 /* Search from the start of the list */
453 index = 0;
454 }
455
456 for (i = index; i < child_array->len; i ++)
457 {
458 GtkWidget *child = g_ptr_array_index (child_array, i);
459
460 if (gtk_widget_get_mapped (widget: child) && gtk_widget_is_sensitive (widget: child))
461 {
462 new_focus = child;
463 break;
464 }
465 }
466
467
468 if (new_focus)
469 {
470 gtk_widget_grab_focus (widget: new_focus);
471 gtk_widget_activate (widget: new_focus);
472 }
473
474 g_ptr_array_free (array: child_array, TRUE);
475
476 return TRUE;
477 }
478 else
479 {
480 GtkCheckButton *active_button;
481
482 active_button = get_group_active_button (self);
483 if (active_button && active_button != self)
484 return FALSE;
485
486 gtk_widget_grab_focus (widget);
487 return TRUE;
488 }
489}
490
491static void
492gtk_check_button_real_activate (GtkCheckButton *self)
493{
494 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
495
496 if (priv->active && (priv->group_prev || priv->group_next))
497 return;
498
499 if (priv->action_helper)
500 gtk_action_helper_activate (helper: priv->action_helper);
501 else
502 gtk_check_button_set_active (self, setting: !gtk_check_button_get_active (self));
503}
504
505static void
506gtk_check_button_class_init (GtkCheckButtonClass *class)
507{
508 const guint activate_keyvals[] = {
509 GDK_KEY_space,
510 GDK_KEY_KP_Space,
511 GDK_KEY_Return,
512 GDK_KEY_ISO_Enter,
513 GDK_KEY_KP_Enter
514 };
515 GObjectClass *object_class = G_OBJECT_CLASS (class);
516 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
517 GtkShortcutAction *activate_action;
518
519 object_class->dispose = gtk_check_button_dispose;
520 object_class->set_property = gtk_check_button_set_property;
521 object_class->get_property = gtk_check_button_get_property;
522
523 widget_class->state_flags_changed = gtk_check_button_state_flags_changed;
524 widget_class->focus = gtk_check_button_focus;
525
526 class->activate = gtk_check_button_real_activate;
527
528 /**
529 * GtkCheckButton:active: (attributes org.gtk.Property.get=gtk_check_button_get_active org.gtk.Property.set=gtk_check_button_set_active)
530 *
531 * If the check button is active.
532 *
533 * Setting `active` to %TRUE will add the `:checked:` state to both
534 * the check button and the indicator CSS node.
535 */
536 props[PROP_ACTIVE] =
537 g_param_spec_boolean (name: "active",
538 P_("Active"),
539 P_("If the toggle button should be pressed in"),
540 FALSE,
541 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
542
543 /**
544 * GtkCheckButton:group: (attributes org.gtk.Property.set=gtk_check_button_set_group)
545 *
546 * The check button whose group this widget belongs to.
547 */
548 props[PROP_GROUP] =
549 g_param_spec_object (name: "group",
550 P_("Group"),
551 P_("The check button whose group this widget belongs to."),
552 GTK_TYPE_CHECK_BUTTON,
553 GTK_PARAM_WRITABLE);
554
555 /**
556 * GtkCheckButton:label: (attributes org.gtk.Property.get=gtk_check_button_get_label org.gtk.Property.set=gtk_check_button_set_label)
557 *
558 * Text of the label inside the check button, if it contains a label widget.
559 */
560 props[PROP_LABEL] =
561 g_param_spec_string (name: "label",
562 P_("Label"),
563 P_("Text of the label widget inside the button, if the button contains a label widget"),
564 NULL,
565 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
566
567 /**
568 * GtkCheckButton:inconsistent: (attributes org.gtk.Property.get=gtk_check_button_get_inconsistent org.gtk.Property.set=gtk_check_button_set_inconsistent)
569 *
570 * If the check button is in an “in between” state.
571 *
572 * The inconsistent state only affects visual appearance,
573 * not the semantics of the button.
574 */
575 props[PROP_INCONSISTENT] =
576 g_param_spec_boolean (name: "inconsistent",
577 P_("Inconsistent"),
578 P_("If the check button is in an “in between” state"),
579 FALSE,
580 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
581
582 /**
583 * GtkCheckButton:use-underline: (attributes org.gtk.Property.get=gtk_check_button_get_use_underline org.gtk.Property.set=gtk_check_button_set_use_underline)
584 *
585 * If set, an underline in the text indicates that the following
586 * character is to be used as mnemonic.
587 */
588 props[PROP_USE_UNDERLINE] =
589 g_param_spec_boolean (name: "use-underline",
590 P_("Use underline"),
591 P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
592 FALSE,
593 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
594
595 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props);
596
597 g_object_class_override_property (oclass: object_class, property_id: PROP_ACTION_NAME, name: "action-name");
598 g_object_class_override_property (oclass: object_class, property_id: PROP_ACTION_TARGET, name: "action-target");
599
600 /**
601 * GtkCheckButton::toggled:
602 *
603 * Emitted when the buttons's [property@Gtk.CheckButton:active]
604 * property changes.
605 */
606 signals[TOGGLED] =
607 g_signal_new (I_("toggled"),
608 G_OBJECT_CLASS_TYPE (object_class),
609 signal_flags: G_SIGNAL_RUN_FIRST,
610 G_STRUCT_OFFSET (GtkCheckButtonClass, toggled),
611 NULL, NULL,
612 NULL,
613 G_TYPE_NONE, n_params: 0);
614
615 /**
616 * GtkCheckButton::activate:
617 * @widget: the object which received the signal.
618 *
619 * Emitted to when the check button is activated.
620 *
621 * The `::activate` signal on `GtkCheckButton` is an action signal and
622 * emitting it causes the button to animate press then release.
623 *
624 * Applications should never connect to this signal, but use the
625 * [signal@Gtk.CheckButton::toggled] signal.
626 *
627 * Since: 4.2
628 */
629 signals[ACTIVATE] =
630 g_signal_new (I_ ("activate"),
631 G_OBJECT_CLASS_TYPE (object_class),
632 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
633 G_STRUCT_OFFSET (GtkCheckButtonClass, activate),
634 NULL, NULL,
635 NULL,
636 G_TYPE_NONE, n_params: 0);
637
638 gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE]);
639
640 activate_action = gtk_signal_action_new (signal_name: "activate");
641 for (guint i = 0; i < G_N_ELEMENTS (activate_keyvals); i++)
642 {
643 GtkShortcut *activate_shortcut = gtk_shortcut_new (trigger: gtk_keyval_trigger_new (keyval: activate_keyvals[i], modifiers: 0),
644 g_object_ref (activate_action));
645
646 gtk_widget_class_add_shortcut (widget_class, shortcut: activate_shortcut);
647 g_object_unref (object: activate_shortcut);
648 }
649 g_object_unref (object: activate_action);
650
651 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
652 gtk_widget_class_set_css_name (widget_class, I_("checkbutton"));
653 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_CHECKBOX);
654}
655
656static void
657gtk_check_button_init (GtkCheckButton *self)
658{
659 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
660 GtkGesture *gesture;
661
662 gtk_widget_set_receives_default (GTK_WIDGET (self), FALSE);
663 priv->indicator_widget = gtk_builtin_icon_new (css_name: "check");
664 gtk_widget_set_halign (widget: priv->indicator_widget, align: GTK_ALIGN_CENTER);
665 gtk_widget_set_valign (widget: priv->indicator_widget, align: GTK_ALIGN_CENTER);
666 gtk_widget_set_parent (widget: priv->indicator_widget, GTK_WIDGET (self));
667
668 update_accessible_state (check_button: self);
669
670 gesture = gtk_gesture_click_new ();
671 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
672 gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
673 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_PRIMARY);
674 g_signal_connect (gesture, "pressed", G_CALLBACK (click_pressed_cb), self);
675 g_signal_connect (gesture, "released", G_CALLBACK (click_released_cb), self);
676 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), phase: GTK_PHASE_CAPTURE);
677 gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
678
679 gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
680}
681
682/**
683 * gtk_check_button_new:
684 *
685 * Creates a new `GtkCheckButton`.
686 *
687 * Returns: a new `GtkCheckButton`
688 */
689GtkWidget *
690gtk_check_button_new (void)
691{
692 return g_object_new (GTK_TYPE_CHECK_BUTTON, NULL);
693}
694
695/**
696 * gtk_check_button_new_with_label:
697 * @label: (nullable): the text for the check button.
698 *
699 * Creates a new `GtkCheckButton` with the given text.
700 *
701 * Returns: a new `GtkCheckButton`
702 */
703GtkWidget*
704gtk_check_button_new_with_label (const char *label)
705{
706 return g_object_new (GTK_TYPE_CHECK_BUTTON, first_property_name: "label", label, NULL);
707}
708
709/**
710 * gtk_check_button_new_with_mnemonic:
711 * @label: (nullable): The text of the button, with an underscore
712 * in front of the mnemonic character
713 *
714 * Creates a new `GtkCheckButton` with the given text and a mnemonic.
715 *
716 * Returns: a new `GtkCheckButton`
717 */
718GtkWidget*
719gtk_check_button_new_with_mnemonic (const char *label)
720{
721 return g_object_new (GTK_TYPE_CHECK_BUTTON,
722 first_property_name: "label", label,
723 "use-underline", TRUE,
724 NULL);
725}
726
727/**
728 * gtk_check_button_set_inconsistent: (attributes org.gtk.Method.set_property=inconsistent)
729 * @check_button: a `GtkCheckButton`
730 * @inconsistent: %TRUE if state is inconsistent
731 *
732 * Sets the `GtkCheckButton` to inconsistent state.
733 *
734 * You shoud turn off the inconsistent state again if the user checks
735 * the check button. This has to be done manually.
736 */
737void
738gtk_check_button_set_inconsistent (GtkCheckButton *check_button,
739 gboolean inconsistent)
740{
741 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self: check_button);
742
743 g_return_if_fail (GTK_IS_CHECK_BUTTON (check_button));
744
745 inconsistent = !!inconsistent;
746 if (priv->inconsistent != inconsistent)
747 {
748 priv->inconsistent = inconsistent;
749
750 if (inconsistent)
751 {
752 gtk_widget_set_state_flags (GTK_WIDGET (check_button), flags: GTK_STATE_FLAG_INCONSISTENT, FALSE);
753 gtk_widget_set_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_INCONSISTENT, FALSE);
754 }
755 else
756 {
757 gtk_widget_unset_state_flags (GTK_WIDGET (check_button), flags: GTK_STATE_FLAG_INCONSISTENT);
758 gtk_widget_unset_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_INCONSISTENT);
759 }
760
761 update_accessible_state (check_button);
762
763 g_object_notify_by_pspec (G_OBJECT (check_button), pspec: props[PROP_INCONSISTENT]);
764 }
765}
766
767/**
768 * gtk_check_button_get_inconsistent: (attributes org.gtk.Method.get_property=inconsistent)
769 * @check_button: a `GtkCheckButton`
770 *
771 * Returns whether the check button is in an inconsistent state.
772 *
773 * Returns: %TRUE if @check_button is currently in an inconsistent state
774 */
775gboolean
776gtk_check_button_get_inconsistent (GtkCheckButton *check_button)
777{
778 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self: check_button);
779
780 g_return_val_if_fail (GTK_IS_CHECK_BUTTON (check_button), FALSE);
781
782 return priv->inconsistent;
783}
784
785/**
786 * gtk_check_button_get_active: (attributes org.gtk.Method.get_property=active)
787 * @self: a `GtkCheckButton`
788 *
789 * Returns whether the check button is active.
790 *
791 * Returns: whether the check button is active
792 */
793gboolean
794gtk_check_button_get_active (GtkCheckButton *self)
795{
796 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
797
798 g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), FALSE);
799
800 return priv->active;
801}
802
803/**
804 * gtk_check_button_set_active: (attributes org.gtk.Method.set_property=active)
805 * @self: a `GtkCheckButton`
806 * @setting: the new value to set
807 *
808 * Changes the check buttons active state.
809 */
810void
811gtk_check_button_set_active (GtkCheckButton *self,
812 gboolean setting)
813{
814 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
815
816 g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
817
818 setting = !!setting;
819
820 if (setting == priv->active)
821 return;
822
823 if (setting)
824 {
825 gtk_widget_set_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED, FALSE);
826 gtk_widget_set_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_CHECKED, FALSE);
827 }
828 else
829 {
830 gtk_widget_unset_state_flags (GTK_WIDGET (self), flags: GTK_STATE_FLAG_CHECKED);
831 gtk_widget_unset_state_flags (widget: priv->indicator_widget, flags: GTK_STATE_FLAG_CHECKED);
832 }
833
834 if (setting && (priv->group_prev || priv->group_next))
835 {
836 GtkCheckButton *group_first = NULL;
837 GtkCheckButton *iter;
838
839 group_first = get_group_first (self);
840 g_assert (group_first);
841
842 /* Set all buttons in group to !active */
843 for (iter = group_first; iter; iter = get_group_next (self: iter))
844 gtk_check_button_set_active (self: iter, FALSE);
845
846 /* ... and the next code block will set this one to active */
847 }
848
849 priv->active = setting;
850 update_accessible_state (check_button: self);
851 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_ACTIVE]);
852 g_signal_emit (instance: self, signal_id: signals[TOGGLED], detail: 0);
853}
854
855/**
856 * gtk_check_button_get_label: (attributes org.gtk.Method.get_property=label)
857 * @self: a `GtkCheckButton`
858 *
859 * Returns the label of the check button.
860 *
861 * Returns: (nullable) (transfer none): The label @self shows next
862 * to the indicator. If no label is shown, %NULL will be returned.
863 */
864const char *
865gtk_check_button_get_label (GtkCheckButton *self)
866{
867 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
868
869 g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), "");
870
871 if (priv->label_widget)
872 return gtk_label_get_label (GTK_LABEL (priv->label_widget));
873
874 return NULL;
875}
876
877/**
878 * gtk_check_button_set_label: (attributes org.gtk.Method.set_property=label)
879 * @self: a `GtkCheckButton`
880 * @label: (nullable): The text shown next to the indicator, or %NULL
881 * to show no text
882 *
883 * Sets the text of @self.
884 *
885 * If [property@Gtk.CheckButton:use-underline] is %TRUE, an underscore
886 * in @label is interpreted as mnemonic indicator, see
887 * [method@Gtk.CheckButton.set_use_underline] for details on this behavior.
888 */
889void
890gtk_check_button_set_label (GtkCheckButton *self,
891 const char *label)
892{
893 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
894
895 g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
896
897 if (label == NULL || label[0] == '\0')
898 {
899 g_clear_pointer (&priv->label_widget, gtk_widget_unparent);
900 gtk_widget_remove_css_class (GTK_WIDGET (self), css_class: "text-button");
901 }
902 else
903 {
904 if (!priv->label_widget)
905 {
906 priv->label_widget = gtk_label_new (NULL);
907 gtk_widget_set_hexpand (widget: priv->label_widget, TRUE);
908 gtk_label_set_xalign (GTK_LABEL (priv->label_widget), xalign: 0.0f);
909 gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), setting: priv->use_underline);
910 gtk_widget_insert_after (widget: priv->label_widget, GTK_WIDGET (self), previous_sibling: priv->indicator_widget);
911 }
912 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "text-button");
913 gtk_label_set_label (GTK_LABEL (priv->label_widget), str: label);
914 }
915
916 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
917 first_property: GTK_ACCESSIBLE_PROPERTY_LABEL, label,
918 -1);
919
920 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_LABEL]);
921}
922
923/**
924 * gtk_check_button_set_group: (attributes org.gtk.Method.set_property=group)
925 * @self: a `GtkCheckButton`
926 * @group: (nullable) (transfer none): another `GtkCheckButton` to
927 * form a group with
928 *
929 * Adds @self to the group of @group.
930 *
931 * In a group of multiple check buttons, only one button can be active
932 * at a time. The behavior of a checkbutton in a group is also commonly
933 * known as a *radio button*.
934 *
935 * Setting the group of a check button also changes the css name of the
936 * indicator widget's CSS node to 'radio'.
937 *
938 * Setting up groups in a cycle leads to undefined behavior.
939 *
940 * Note that the same effect can be achieved via the [iface@Gtk.Actionable]
941 * API, by using the same action with parameter type and state type 's'
942 * for all buttons in the group, and giving each button its own target
943 * value.
944 */
945void
946gtk_check_button_set_group (GtkCheckButton *self,
947 GtkCheckButton *group)
948{
949 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
950 GtkCheckButtonPrivate *group_priv = gtk_check_button_get_instance_private (self: group);
951
952 g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
953 g_return_if_fail (self != group);
954
955 if (!group)
956 {
957 if (priv->group_prev)
958 {
959 GtkCheckButtonPrivate *p = gtk_check_button_get_instance_private (self: priv->group_prev);
960 p->group_next = priv->group_next;
961 }
962 if (priv->group_next)
963 {
964 GtkCheckButtonPrivate *p = gtk_check_button_get_instance_private (self: priv->group_next);
965 p->group_prev = priv->group_prev;
966 }
967
968 priv->group_next = NULL;
969 priv->group_prev = NULL;
970
971 if (priv->indicator_widget)
972 gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget),
973 name: g_quark_from_static_string(string: "check"));
974
975 return;
976 }
977
978 if (priv->group_next == group)
979 return;
980
981 priv->group_prev = NULL;
982 if (group_priv->group_prev)
983 {
984 GtkCheckButtonPrivate *prev = gtk_check_button_get_instance_private (self: group_priv->group_prev);
985
986 prev->group_next = self;
987 priv->group_prev = group_priv->group_prev;
988 }
989
990 group_priv->group_prev = self;
991 priv->group_next = group;
992
993 if (priv->indicator_widget)
994 gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: priv->indicator_widget),
995 name: g_quark_from_static_string(string: "radio"));
996
997 gtk_css_node_set_name (cssnode: gtk_widget_get_css_node (widget: group_priv->indicator_widget),
998 name: g_quark_from_static_string(string: "radio"));
999
1000 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_GROUP]);
1001}
1002
1003/**
1004 * gtk_check_button_get_use_underline: (attributes org.gtk.Method.get_property=use-underline)
1005 * @self: a `GtkCheckButton`
1006 *
1007 * Returns whether underlines in the label indicate mnemonics.
1008 *
1009 * Returns: The value of the [property@Gtk.CheckButton:use-underline] property.
1010 * See [method@Gtk.CheckButton.set_use_underline] for details on how to set
1011 * a new value.
1012 */
1013gboolean
1014gtk_check_button_get_use_underline (GtkCheckButton *self)
1015{
1016 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
1017
1018 g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), FALSE);
1019
1020 return priv->use_underline;
1021}
1022
1023/**
1024 * gtk_check_button_set_use_underline: (attributes org.gtk.Method.set_property=use-underline)
1025 * @self: a `GtkCheckButton`
1026 * @setting: the new value to set
1027 *
1028 * Sets whether underlines in the label indicate mnemonics.
1029 *
1030 * If @setting is %TRUE, an underscore character in @self's label
1031 * indicates a mnemonic accelerator key. This behavior is similar
1032 * to [property@Gtk.Label:use-underline].
1033 */
1034void
1035gtk_check_button_set_use_underline (GtkCheckButton *self,
1036 gboolean setting)
1037{
1038 GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
1039
1040 g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
1041
1042 setting = !!setting;
1043
1044 if (setting == priv->use_underline)
1045 return;
1046
1047 priv->use_underline = setting;
1048 if (priv->label_widget)
1049 gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), setting: priv->use_underline);
1050
1051 g_object_notify_by_pspec (G_OBJECT (self), pspec: props[PROP_USE_UNDERLINE]);
1052}
1053

source code of gtk/gtk/gtkcheckbutton.c