gtk2/gtk/gtkcheckbutton.c
Matthias Clasen 3b70cd3226 checkbutton: Match GtkButton behavior
When leaving the widget before releasing the button,
we should not activate the checkbutton, to match the
way GtkButton behaves.

Fixes: #4061
2021-06-19 13:30:31 -07:00

1052 lines
33 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gtkcheckbutton.h"
#include "gtkactionhelperprivate.h"
#include "gtkboxlayout.h"
#include "gtkbuiltiniconprivate.h"
#include "gtkcssnumbervalueprivate.h"
#include "gtkgestureclick.h"
#include "gtkintl.h"
#include "gtklabel.h"
#include "gtkprivate.h"
#include "gtkshortcuttrigger.h"
#include "gtkcssnodeprivate.h"
#include "gtkwidgetprivate.h"
#include "gtkmodelbuttonprivate.h"
/**
* GtkCheckButton:
*
* A `GtkCheckButton` places a label next to an indicator.
*
* ![Example GtkCheckButtons](check-button.png)
*
* A `GtkCheckButton` is created by calling either [ctor@Gtk.CheckButton.new]
* or [ctor@Gtk.CheckButton.new_with_label].
*
* The state of a `GtkCheckButton` can be set specifically using
* [method@Gtk.CheckButton.set_active], and retrieved using
* [method@Gtk.CheckButton.get_active].
*
* # Inconsistent state
*
* In addition to "on" and "off", check buttons can be an
* "in between" state that is neither on nor off. This can be used
* e.g. when the user has selected a range of elements (such as some
* text or spreadsheet cells) that are affected by a check button,
* and the current values in that range are inconsistent.
*
* To set a `GtkCheckButton` to inconsistent state, use
* [method@Gtk.CheckButton.set_inconsistent].
*
* # Grouping
*
* Check buttons can be grouped together, to form mutually exclusive
* groups - only one of the buttons can be toggled at a time, and toggling
* another one will switch the currently toggled one off.
*
* Grouped check buttons use a different indicator, and are commonly referred
* to as *radio buttons*.
*
* ![Example GtkCheckButtons](radio-button.png)
*
* To add a `GtkCheckButton` to a group, use [method@Gtk.CheckButton.set_group].
*
* # CSS nodes
*
* ```
* checkbutton[.text-button]
* ├── check
* ╰── [label]
* ```
*
* A `GtkCheckButton` has a main node with name checkbutton. If the
* [property@Gtk.CheckButton:label] property is set, it contains a label
* child. The indicator node is named check when no group is set, and
* radio if the checkbutton is grouped together with other checkbuttons.
*
* # Accessibility
*
* `GtkCheckButton` uses the %GTK_ACCESSIBLE_ROLE_CHECKBOX role.
*/
typedef struct {
GtkWidget *indicator_widget;
GtkWidget *label_widget;
guint inconsistent: 1;
guint active: 1;
guint use_underline: 1;
GtkCheckButton *group_next;
GtkCheckButton *group_prev;
GtkActionHelper *action_helper;
} GtkCheckButtonPrivate;
enum {
PROP_0,
PROP_ACTIVE,
PROP_GROUP,
PROP_LABEL,
PROP_INCONSISTENT,
PROP_USE_UNDERLINE,
/* actionable properties */
PROP_ACTION_NAME,
PROP_ACTION_TARGET,
LAST_PROP = PROP_ACTION_NAME
};
enum {
TOGGLED,
ACTIVATE,
LAST_SIGNAL
};
static void gtk_check_button_actionable_iface_init (GtkActionableInterface *iface);
static guint signals[LAST_SIGNAL] = { 0 };
static GParamSpec *props[LAST_PROP] = { NULL, };
G_DEFINE_TYPE_WITH_CODE (GtkCheckButton, gtk_check_button, GTK_TYPE_WIDGET,
G_ADD_PRIVATE (GtkCheckButton)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, gtk_check_button_actionable_iface_init))
static void
gtk_check_button_dispose (GObject *object)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (GTK_CHECK_BUTTON (object));
g_clear_object (&priv->action_helper);
g_clear_pointer (&priv->indicator_widget, gtk_widget_unparent);
g_clear_pointer (&priv->label_widget, gtk_widget_unparent);
gtk_check_button_set_group (GTK_CHECK_BUTTON (object), NULL);
G_OBJECT_CLASS (gtk_check_button_parent_class)->dispose (object);
}
static void
button_role_changed (GtkCheckButton *self)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
if (gtk_action_helper_get_role (priv->action_helper) == GTK_BUTTON_ROLE_RADIO)
gtk_css_node_set_name (gtk_widget_get_css_node (priv->indicator_widget),
g_quark_from_static_string("radio"));
else
gtk_css_node_set_name (gtk_widget_get_css_node (priv->indicator_widget),
g_quark_from_static_string("check"));
}
static void
ensure_action_helper (GtkCheckButton *self)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
if (priv->action_helper)
return;
priv->action_helper = gtk_action_helper_new (GTK_ACTIONABLE (self));
g_signal_connect_swapped (priv->action_helper, "notify::role",
G_CALLBACK (button_role_changed), self);
}
static void
gtk_check_button_set_action_name (GtkActionable *actionable,
const char *action_name)
{
GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
ensure_action_helper (self);
gtk_action_helper_set_action_name (priv->action_helper, action_name);
}
static void
gtk_check_button_set_action_target_value (GtkActionable *actionable,
GVariant *action_target)
{
GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
ensure_action_helper (self);
gtk_action_helper_set_action_target_value (priv->action_helper, action_target);
}
static void
gtk_check_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
switch (prop_id)
{
case PROP_ACTIVE:
gtk_check_button_set_active (GTK_CHECK_BUTTON (object), g_value_get_boolean (value));
break;
case PROP_GROUP:
gtk_check_button_set_group (GTK_CHECK_BUTTON (object), g_value_get_object (value));
break;
case PROP_LABEL:
gtk_check_button_set_label (GTK_CHECK_BUTTON (object), g_value_get_string (value));
break;
case PROP_INCONSISTENT:
gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (object), g_value_get_boolean (value));
break;
case PROP_USE_UNDERLINE:
gtk_check_button_set_use_underline (GTK_CHECK_BUTTON (object), g_value_get_boolean (value));
break;
case PROP_ACTION_NAME:
gtk_check_button_set_action_name (GTK_ACTIONABLE (object), g_value_get_string (value));
break;
case PROP_ACTION_TARGET:
gtk_check_button_set_action_target_value (GTK_ACTIONABLE (object), g_value_get_variant (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_check_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (GTK_CHECK_BUTTON (object));
switch (prop_id)
{
case PROP_ACTIVE:
g_value_set_boolean (value, gtk_check_button_get_active (GTK_CHECK_BUTTON (object)));
break;
case PROP_LABEL:
g_value_set_string (value, gtk_check_button_get_label (GTK_CHECK_BUTTON (object)));
break;
case PROP_INCONSISTENT:
g_value_set_boolean (value, gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (object)));
break;
case PROP_USE_UNDERLINE:
g_value_set_boolean (value, gtk_check_button_get_use_underline (GTK_CHECK_BUTTON (object)));
break;
case PROP_ACTION_NAME:
g_value_set_string (value, gtk_action_helper_get_action_name (priv->action_helper));
break;
case PROP_ACTION_TARGET:
g_value_set_variant (value, gtk_action_helper_get_action_target_value (priv->action_helper));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static const char *
gtk_check_button_get_action_name (GtkActionable *actionable)
{
GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
return gtk_action_helper_get_action_name (priv->action_helper);
}
static GVariant *
gtk_check_button_get_action_target_value (GtkActionable *actionable)
{
GtkCheckButton *self = GTK_CHECK_BUTTON (actionable);
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
return gtk_action_helper_get_action_target_value (priv->action_helper);
}
static void
gtk_check_button_actionable_iface_init (GtkActionableInterface *iface)
{
iface->get_action_name = gtk_check_button_get_action_name;
iface->set_action_name = gtk_check_button_set_action_name;
iface->get_action_target_value = gtk_check_button_get_action_target_value;
iface->set_action_target_value = gtk_check_button_set_action_target_value;
}
static void
click_pressed_cb (GtkGestureClick *gesture,
guint n_press,
double x,
double y,
GtkWidget *widget)
{
if (gtk_widget_get_focus_on_click (widget) && !gtk_widget_has_focus (widget))
gtk_widget_grab_focus (widget);
}
static void
click_released_cb (GtkGestureClick *gesture,
guint n_press,
double x,
double y,
GtkWidget *widget)
{
GtkCheckButton *self = GTK_CHECK_BUTTON (widget);
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
if (priv->active && (priv->group_prev || priv->group_next))
return;
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
if (gtk_widget_is_sensitive (widget) &&
gtk_widget_contains (widget, x, y))
{
if (priv->action_helper)
gtk_action_helper_activate (priv->action_helper);
else
gtk_check_button_set_active (self, !priv->active);
}
}
static void
update_accessible_state (GtkCheckButton *check_button)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (check_button);
GtkAccessibleTristate checked_state;
if (priv->inconsistent)
checked_state = GTK_ACCESSIBLE_TRISTATE_MIXED;
else if (priv->active)
checked_state = GTK_ACCESSIBLE_TRISTATE_TRUE;
else
checked_state = GTK_ACCESSIBLE_TRISTATE_FALSE;
gtk_accessible_update_state (GTK_ACCESSIBLE (check_button),
GTK_ACCESSIBLE_STATE_CHECKED, checked_state,
-1);
}
static GtkCheckButton *
get_group_next (GtkCheckButton *self)
{
return ((GtkCheckButtonPrivate *)gtk_check_button_get_instance_private (self))->group_next;
}
static GtkCheckButton *
get_group_prev (GtkCheckButton *self)
{
return ((GtkCheckButtonPrivate *)gtk_check_button_get_instance_private (self))->group_prev;
}
static GtkCheckButton *
get_group_first (GtkCheckButton *self)
{
GtkCheckButton *group_first = NULL;
GtkCheckButton *iter;
/* Find first in group */
iter = self;
while (iter)
{
group_first = iter;
iter = get_group_prev (iter);
if (!iter)
break;
}
g_assert (group_first);
return group_first;
}
static GtkCheckButton *
get_group_active_button (GtkCheckButton *self)
{
GtkCheckButton *iter;
for (iter = get_group_first (self); iter; iter = get_group_next (iter))
{
if (gtk_check_button_get_active (iter))
return iter;
}
return NULL;
}
static void
gtk_check_button_state_flags_changed (GtkWidget *widget,
GtkStateFlags previous_flags)
{
GtkCheckButton *self = GTK_CHECK_BUTTON (widget);
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
GtkStateFlags state = gtk_widget_get_state_flags (GTK_WIDGET (self));
gtk_widget_set_state_flags (priv->indicator_widget, state, TRUE);
GTK_WIDGET_CLASS (gtk_check_button_parent_class)->state_flags_changed (widget, previous_flags);
}
static gboolean
gtk_check_button_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GtkCheckButton *self = GTK_CHECK_BUTTON (widget);
GtkCheckButton *active_button;
active_button = get_group_active_button (self);
if (gtk_widget_is_focus (widget))
{
GtkCheckButton *iter;
GPtrArray *child_array;
GtkWidget *new_focus = NULL;
guint index;
gboolean found;
guint i;
if (direction == GTK_DIR_TAB_FORWARD ||
direction == GTK_DIR_TAB_BACKWARD)
return FALSE;
child_array = g_ptr_array_new ();
for (iter = get_group_first (self); iter; iter = get_group_next (iter))
g_ptr_array_add (child_array, iter);
gtk_widget_focus_sort (widget, direction, child_array);
found = g_ptr_array_find (child_array, widget, &index);
if (found)
{
/* Start at the *next* widget in the list */
if (index < child_array->len - 1)
index ++;
}
else
{
/* Search from the start of the list */
index = 0;
}
for (i = index; i < child_array->len; i ++)
{
GtkWidget *child = g_ptr_array_index (child_array, i);
if (gtk_widget_get_mapped (child) && gtk_widget_is_sensitive (child))
{
new_focus = child;
break;
}
}
if (new_focus)
{
gtk_widget_grab_focus (new_focus);
gtk_check_button_set_active (GTK_CHECK_BUTTON (new_focus), TRUE);
if (active_button && active_button != (GtkCheckButton *)new_focus)
gtk_check_button_set_active (GTK_CHECK_BUTTON (active_button), FALSE);
}
g_ptr_array_free (child_array, TRUE);
return TRUE;
}
else
{
if (active_button && active_button != self)
return FALSE;
gtk_widget_grab_focus (widget);
return TRUE;
}
}
static void
gtk_check_button_real_activate (GtkCheckButton *self)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
if (priv->active && (priv->group_prev || priv->group_next))
return;
gtk_check_button_set_active (self, !gtk_check_button_get_active (self));
}
static void
gtk_check_button_class_init (GtkCheckButtonClass *class)
{
const guint activate_keyvals[] = {
GDK_KEY_space,
GDK_KEY_KP_Space,
GDK_KEY_Return,
GDK_KEY_ISO_Enter,
GDK_KEY_KP_Enter
};
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GtkShortcutAction *activate_action;
object_class->dispose = gtk_check_button_dispose;
object_class->set_property = gtk_check_button_set_property;
object_class->get_property = gtk_check_button_get_property;
widget_class->state_flags_changed = gtk_check_button_state_flags_changed;
widget_class->focus = gtk_check_button_focus;
class->activate = gtk_check_button_real_activate;
/**
* GtkCheckButton:active: (attributes org.gtk.Property.get=gtk_check_button_get_active org.gtk.Property.set=gtk_check_button_set_active)
*
* If the check button is active.
*
* Setting `active` to %TRUE will add the `:checked:` state to both
* the check button and the indicator CSS node.
*/
props[PROP_ACTIVE] =
g_param_spec_boolean ("active",
P_("Active"),
P_("If the toggle button should be pressed in"),
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkCheckButton:group: (attributes org.gtk.Property.set=gtk_check_button_set_group)
*
* The check button whose group this widget belongs to.
*/
props[PROP_GROUP] =
g_param_spec_object ("group",
P_("Group"),
P_("The check button whose group this widget belongs to."),
GTK_TYPE_CHECK_BUTTON,
GTK_PARAM_WRITABLE);
/**
* GtkCheckButton:label: (attributes org.gtk.Property.get=gtk_check_button_get_label org.gtk.Property.set=gtk_check_button_set_label)
*
* Text of the label inside the check button, if it contains a label widget.
*/
props[PROP_LABEL] =
g_param_spec_string ("label",
P_("Label"),
P_("Text of the label widget inside the button, if the button contains a label widget"),
NULL,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkCheckButton:inconsistent: (attributes org.gtk.Property.get=gtk_check_button_get_inconsistent org.gtk.Property.set=gtk_check_button_set_inconsistent)
*
* If the check button is in an “in between” state.
*
* The inconsistent state only affects visual appearance,
* not the semantics of the button.
*/
props[PROP_INCONSISTENT] =
g_param_spec_boolean ("inconsistent",
P_("Inconsistent"),
P_("If the check button is in an “in between” state"),
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkCheckButton:use-underline: (attributes org.gtk.Property.get=gtk_check_button_get_use_underline org.gtk.Property.set=gtk_check_button_set_use_underline)
*
* If set, an underline in the text indicates that the following
* character is to be used as mnemonic.
*/
props[PROP_USE_UNDERLINE] =
g_param_spec_boolean ("use-underline",
P_("Use underline"),
P_("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
FALSE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_PROP, props);
g_object_class_override_property (object_class, PROP_ACTION_NAME, "action-name");
g_object_class_override_property (object_class, PROP_ACTION_TARGET, "action-target");
/**
* GtkCheckButton::toggled:
*
* Emitted when the buttons's [property@Gtk.CheckButton:active]
* property changes.
*/
signals[TOGGLED] =
g_signal_new (I_("toggled"),
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkCheckButtonClass, toggled),
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
/**
* GtkCheckButton::activate:
* @widget: the object which received the signal.
*
* Emitted to when the check button is activated.
*
* The `::activate` signal on `GtkCheckButton` is an action signal and
* emitting it causes the button to animate press then release.
*
* Applications should never connect to this signal, but use the
* [signal@Gtk.CheckButton::toggled] signal.
*
* Since: 4.2
*/
signals[ACTIVATE] =
g_signal_new (I_ ("activate"),
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkCheckButtonClass, activate),
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
gtk_widget_class_set_activate_signal (widget_class, signals[ACTIVATE]);
activate_action = gtk_signal_action_new ("activate");
for (guint i = 0; i < G_N_ELEMENTS (activate_keyvals); i++)
{
GtkShortcut *activate_shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (activate_keyvals[i], 0),
g_object_ref (activate_action));
gtk_widget_class_add_shortcut (widget_class, activate_shortcut);
g_object_unref (activate_shortcut);
}
g_object_unref (activate_action);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("checkbutton"));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_CHECKBOX);
}
static void
gtk_check_button_init (GtkCheckButton *self)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
GtkGesture *gesture;
gtk_widget_set_receives_default (GTK_WIDGET (self), FALSE);
priv->indicator_widget = gtk_builtin_icon_new ("check");
gtk_widget_set_halign (priv->indicator_widget, GTK_ALIGN_CENTER);
gtk_widget_set_valign (priv->indicator_widget, GTK_ALIGN_CENTER);
gtk_widget_set_parent (priv->indicator_widget, GTK_WIDGET (self));
update_accessible_state (self);
gesture = gtk_gesture_click_new ();
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_PRIMARY);
g_signal_connect (gesture, "pressed", G_CALLBACK (click_pressed_cb), self);
g_signal_connect (gesture, "released", G_CALLBACK (click_released_cb), self);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
}
/**
* gtk_check_button_new:
*
* Creates a new `GtkCheckButton`.
*
* Returns: a new `GtkCheckButton`
*/
GtkWidget *
gtk_check_button_new (void)
{
return g_object_new (GTK_TYPE_CHECK_BUTTON, NULL);
}
/**
* gtk_check_button_new_with_label:
* @label: (nullable): the text for the check button.
*
* Creates a new `GtkCheckButton` with the given text.
*
* Returns: a new `GtkCheckButton`
*/
GtkWidget*
gtk_check_button_new_with_label (const char *label)
{
return g_object_new (GTK_TYPE_CHECK_BUTTON, "label", label, NULL);
}
/**
* gtk_check_button_new_with_mnemonic:
* @label: (nullable): The text of the button, with an underscore
* in front of the mnemonic character
*
* Creates a new `GtkCheckButton` with the given text and a mnemonic.
*
* Returns: a new `GtkCheckButton`
*/
GtkWidget*
gtk_check_button_new_with_mnemonic (const char *label)
{
return g_object_new (GTK_TYPE_CHECK_BUTTON,
"label", label,
"use-underline", TRUE,
NULL);
}
/**
* gtk_check_button_set_inconsistent: (attributes org.gtk.Method.set_property=inconsistent)
* @check_button: a `GtkCheckButton`
* @inconsistent: %TRUE if state is inconsistent
*
* Sets the `GtkCheckButton` to inconsistent state.
*
* You shoud turn off the inconsistent state again if the user checks
* the check button. This has to be done manually.
*/
void
gtk_check_button_set_inconsistent (GtkCheckButton *check_button,
gboolean inconsistent)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (check_button);
g_return_if_fail (GTK_IS_CHECK_BUTTON (check_button));
inconsistent = !!inconsistent;
if (priv->inconsistent != inconsistent)
{
priv->inconsistent = inconsistent;
if (inconsistent)
{
gtk_widget_set_state_flags (GTK_WIDGET (check_button), GTK_STATE_FLAG_INCONSISTENT, FALSE);
gtk_widget_set_state_flags (priv->indicator_widget, GTK_STATE_FLAG_INCONSISTENT, FALSE);
}
else
{
gtk_widget_unset_state_flags (GTK_WIDGET (check_button), GTK_STATE_FLAG_INCONSISTENT);
gtk_widget_unset_state_flags (priv->indicator_widget, GTK_STATE_FLAG_INCONSISTENT);
}
update_accessible_state (check_button);
g_object_notify_by_pspec (G_OBJECT (check_button), props[PROP_INCONSISTENT]);
}
}
/**
* gtk_check_button_get_inconsistent: (attributes org.gtk.Method.get_property=inconsistent)
* @check_button: a `GtkCheckButton`
*
* Returns whether the check button is in an inconsistent state.
*
* Returns: %TRUE if @check_button is currently in an inconsistent state
*/
gboolean
gtk_check_button_get_inconsistent (GtkCheckButton *check_button)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (check_button);
g_return_val_if_fail (GTK_IS_CHECK_BUTTON (check_button), FALSE);
return priv->inconsistent;
}
/**
* gtk_check_button_get_active: (attributes org.gtk.Method.get_property=active)
* @self: a `GtkCheckButton`
*
* Returns whether the check button is active.
*
* Returns: whether the check button is active
*/
gboolean
gtk_check_button_get_active (GtkCheckButton *self)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), FALSE);
return priv->active;
}
/**
* gtk_check_button_set_active: (attributes org.gtk.Method.set_property=active)
* @self: a `GtkCheckButton`
* @setting: the new value to set
*
* Changes the check buttons active state.
*/
void
gtk_check_button_set_active (GtkCheckButton *self,
gboolean setting)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
setting = !!setting;
if (setting == priv->active)
return;
if (setting)
{
gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_CHECKED, FALSE);
gtk_widget_set_state_flags (priv->indicator_widget, GTK_STATE_FLAG_CHECKED, FALSE);
}
else
{
gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_CHECKED);
gtk_widget_unset_state_flags (priv->indicator_widget, GTK_STATE_FLAG_CHECKED);
}
if (setting && (priv->group_prev || priv->group_next))
{
GtkCheckButton *group_first = NULL;
GtkCheckButton *iter;
group_first = get_group_first (self);
g_assert (group_first);
/* Set all buttons in group to !active */
for (iter = group_first; iter; iter = get_group_next (iter))
gtk_check_button_set_active (iter, FALSE);
/* ... and the next code block will set this one to active */
}
priv->active = setting;
update_accessible_state (self);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVE]);
g_signal_emit (self, signals[TOGGLED], 0);
}
/**
* gtk_check_button_get_label: (attributes org.gtk.Method.get_property=label)
* @self: a `GtkCheckButton`
*
* Returns the label of the check button.
*
* Returns: (nullable) (transfer none): The label @self shows next
* to the indicator. If no label is shown, %NULL will be returned.
*/
const char *
gtk_check_button_get_label (GtkCheckButton *self)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), "");
if (priv->label_widget)
return gtk_label_get_label (GTK_LABEL (priv->label_widget));
return NULL;
}
/**
* gtk_check_button_set_label: (attributes org.gtk.Method.set_property=label)
* @self: a `GtkCheckButton`
* @label: (nullable): The text shown next to the indicator, or %NULL
* to show no text
*
* Sets the text of @self.
*
* If [property@Gtk.CheckButton:use-underline] is %TRUE, an underscore
* in @label is interpreted as mnemonic indicator, see
* [method@Gtk.CheckButton.set_use_underline] for details on this behavior.
*/
void
gtk_check_button_set_label (GtkCheckButton *self,
const char *label)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
if (label == NULL || label[0] == '\0')
{
g_clear_pointer (&priv->label_widget, gtk_widget_unparent);
gtk_widget_remove_css_class (GTK_WIDGET (self), "text-button");
}
else
{
if (!priv->label_widget)
{
priv->label_widget = gtk_label_new (NULL);
gtk_widget_set_hexpand (priv->label_widget, TRUE);
gtk_label_set_xalign (GTK_LABEL (priv->label_widget), 0.0f);
gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), priv->use_underline);
gtk_widget_insert_after (priv->label_widget, GTK_WIDGET (self), priv->indicator_widget);
}
gtk_widget_add_css_class (GTK_WIDGET (self), "text-button");
gtk_label_set_label (GTK_LABEL (priv->label_widget), label);
}
gtk_accessible_update_property (GTK_ACCESSIBLE (self),
GTK_ACCESSIBLE_PROPERTY_LABEL, label,
-1);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LABEL]);
}
/**
* gtk_check_button_set_group: (attributes org.gtk.Method.set_property=group)
* @self: a `GtkCheckButton`
* @group: (nullable) (transfer none): another `GtkCheckButton` to
* form a group with
*
* Adds @self to the group of @group.
*
* In a group of multiple check buttons, only one button can be active
* at a time. The behavior of a checkbutton in a group is also commonly
* known as a *radio button*.
*
* Setting the group of a check button also changes the css name of the
* indicator widget's CSS node to 'radio'.
*
* Setting up groups in a cycle leads to undefined behavior.
*
* Note that the same effect can be achieved via the [iface@Gtk.Actionable]
* API, by using the same action with parameter type and state type 's'
* for all buttons in the group, and giving each button its own target
* value.
*/
void
gtk_check_button_set_group (GtkCheckButton *self,
GtkCheckButton *group)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
GtkCheckButtonPrivate *group_priv = gtk_check_button_get_instance_private (group);
g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
g_return_if_fail (self != group);
if (!group)
{
if (priv->group_prev)
{
GtkCheckButtonPrivate *p = gtk_check_button_get_instance_private (priv->group_prev);
p->group_next = priv->group_next;
}
if (priv->group_next)
{
GtkCheckButtonPrivate *p = gtk_check_button_get_instance_private (priv->group_next);
p->group_prev = priv->group_prev;
}
priv->group_next = NULL;
priv->group_prev = NULL;
if (priv->indicator_widget)
gtk_css_node_set_name (gtk_widget_get_css_node (priv->indicator_widget),
g_quark_from_static_string("check"));
return;
}
if (priv->group_next == group)
return;
priv->group_prev = NULL;
if (group_priv->group_prev)
{
GtkCheckButtonPrivate *prev = gtk_check_button_get_instance_private (group_priv->group_prev);
prev->group_next = self;
priv->group_prev = group_priv->group_prev;
}
group_priv->group_prev = self;
priv->group_next = group;
if (priv->indicator_widget)
gtk_css_node_set_name (gtk_widget_get_css_node (priv->indicator_widget),
g_quark_from_static_string("radio"));
gtk_css_node_set_name (gtk_widget_get_css_node (group_priv->indicator_widget),
g_quark_from_static_string("radio"));
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_GROUP]);
}
/**
* gtk_check_button_get_use_underline: (attributes org.gtk.Method.get_property=use-underline)
* @self: a `GtkCheckButton`
*
* Returns whether underlines in the label indicate mnemonics.
*
* Returns: The value of the [property@Gtk.CheckButton:use-underline] property.
* See [method@Gtk.CheckButton.set_use_underline] for details on how to set
* a new value.
*/
gboolean
gtk_check_button_get_use_underline (GtkCheckButton *self)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
g_return_val_if_fail (GTK_IS_CHECK_BUTTON (self), FALSE);
return priv->use_underline;
}
/**
* gtk_check_button_set_use_underline: (attributes org.gtk.Method.set_property=use-underline)
* @self: a `GtkCheckButton`
* @setting: the new value to set
*
* Sets whether underlines in the label indicate mnemonics.
*
* If @setting is %TRUE, an underscore character in @self's label
* indicates a mnemonic accelerator key. This behavior is similar
* to [property@Gtk.Label:use-underline].
*/
void
gtk_check_button_set_use_underline (GtkCheckButton *self,
gboolean setting)
{
GtkCheckButtonPrivate *priv = gtk_check_button_get_instance_private (self);
g_return_if_fail (GTK_IS_CHECK_BUTTON (self));
setting = !!setting;
if (setting == priv->use_underline)
return;
priv->use_underline = setting;
if (priv->label_widget)
gtk_label_set_use_underline (GTK_LABEL (priv->label_widget), priv->use_underline);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_UNDERLINE]);
}