gtk2/gtk/gtkbutton.c
Matthias Clasen 6fafc8b811 button: Claim click events
This fixes issues with double activation on clicks.

Fixes: #3054
2020-08-09 22:58:22 -04:00

1062 lines
32 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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-2001. 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/.
*/
/**
* SECTION:gtkbutton
* @Short_description: A widget that emits a signal when clicked on
* @Title: GtkButton
*
* The #GtkButton widget is generally used to trigger a callback function that is
* called when the button is pressed. The various signals and how to use them
* are outlined below.
*
* The #GtkButton widget can hold any valid child widget. That is, it can hold
* almost any other standard #GtkWidget. The most commonly used child is the
* #GtkLabel.
*
* # CSS nodes
*
* GtkButton has a single CSS node with name button. The node will get the
* style classes .image-button or .text-button, if the content is just an
* image or label, respectively. It may also receive the .flat style class.
*
* Other style classes that are commonly used with GtkButton include
* .suggested-action and .destructive-action. In special cases, buttons
* can be made round by adding the .circular style class.
*
* Button-like widgets like #GtkToggleButton, #GtkMenuButton, #GtkVolumeButton,
* #GtkLockButton, #GtkColorButton or #GtkFontButton use style classes such as
* .toggle, .popup, .scale, .lock, .color on the button node
* to differentiate themselves from a plain GtkButton.
*
* # Accessibility
*
* GtkButton uses the #GTK_ACCESSIBLE_ROLE_BUTTON role.
*/
#include "config.h"
#include "gtkbuttonprivate.h"
#include "gtkactionhelperprivate.h"
#include "gtkbuildable.h"
#include "gtkcheckbutton.h"
#include "gtkgestureclick.h"
#include "gtkeventcontrollerkey.h"
#include "gtkbinlayout.h"
#include "gtkimage.h"
#include "gtkintl.h"
#include "gtklabel.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkstylecontext.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include <string.h>
/* Time out before giving up on getting a key release when animating
* the close button.
*/
#define ACTIVATE_TIMEOUT 250
struct _GtkButtonPrivate
{
GtkWidget *child;
GtkActionHelper *action_helper;
GtkGesture *gesture;
guint activate_timeout;
guint button_down : 1;
guint in_button : 1;
guint use_underline : 1;
guint child_type : 2;
};
enum {
CLICKED,
ACTIVATE,
LAST_SIGNAL
};
enum {
PROP_0,
PROP_LABEL,
PROP_HAS_FRAME,
PROP_USE_UNDERLINE,
PROP_ICON_NAME,
PROP_CHILD,
/* actionable properties */
PROP_ACTION_NAME,
PROP_ACTION_TARGET,
LAST_PROP = PROP_ACTION_NAME
};
enum {
LABEL_CHILD,
ICON_CHILD,
WIDGET_CHILD
};
static void gtk_button_dispose (GObject *object);
static void gtk_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void gtk_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void gtk_button_unrealize (GtkWidget * widget);
static void gtk_real_button_clicked (GtkButton * button);
static void gtk_real_button_activate (GtkButton *button);
static void gtk_button_finish_activate (GtkButton *button,
gboolean do_it);
static void gtk_button_state_flags_changed (GtkWidget *widget,
GtkStateFlags previous_state);
static void gtk_button_do_release (GtkButton *button,
gboolean emit_clicked);
static void gtk_button_set_child_type (GtkButton *button, guint child_type);
static void gtk_button_buildable_iface_init (GtkBuildableIface *iface);
static void gtk_button_actionable_iface_init (GtkActionableInterface *iface);
static GParamSpec *props[LAST_PROP] = { NULL, };
static guint button_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE_WITH_CODE (GtkButton, gtk_button, GTK_TYPE_WIDGET,
G_ADD_PRIVATE (GtkButton)
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_button_buildable_iface_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, gtk_button_actionable_iface_init))
static void
gtk_button_unmap (GtkWidget *widget)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (GTK_BUTTON (widget));
priv->in_button = FALSE;
GTK_WIDGET_CLASS (gtk_button_parent_class)->unmap (widget);
}
static void
gtk_button_compute_expand (GtkWidget *widget,
gboolean *hexpand,
gboolean *vexpand)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (GTK_BUTTON (widget));
if (priv->child)
{
*hexpand = gtk_widget_compute_expand (priv->child, GTK_ORIENTATION_HORIZONTAL);
*vexpand = gtk_widget_compute_expand (priv->child, GTK_ORIENTATION_VERTICAL);
}
else
{
*hexpand = FALSE;
*vexpand = FALSE;
}
}
static GtkSizeRequestMode
gtk_button_get_request_mode (GtkWidget *widget)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (GTK_BUTTON (widget));
if (priv->child)
return gtk_widget_get_request_mode (priv->child);
else
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
}
static void
gtk_button_class_init (GtkButtonClass *klass)
{
GObjectClass *gobject_class;
GtkWidgetClass *widget_class;
gobject_class = G_OBJECT_CLASS (klass);
widget_class = (GtkWidgetClass*) klass;
gobject_class->dispose = gtk_button_dispose;
gobject_class->set_property = gtk_button_set_property;
gobject_class->get_property = gtk_button_get_property;
widget_class->unrealize = gtk_button_unrealize;
widget_class->state_flags_changed = gtk_button_state_flags_changed;
widget_class->unmap = gtk_button_unmap;
widget_class->compute_expand = gtk_button_compute_expand;
widget_class->get_request_mode = gtk_button_get_request_mode;
klass->clicked = NULL;
klass->activate = gtk_real_button_activate;
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);
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);
props[PROP_HAS_FRAME] =
g_param_spec_boolean ("has-frame",
P_("Has Frame"),
P_("Whether the button has a frame"),
TRUE,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
props[PROP_ICON_NAME] =
g_param_spec_string ("icon-name",
P_("Icon Name"),
P_("The name of the icon used to automatically populate the button"),
NULL,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
props[PROP_CHILD] =
g_param_spec_object ("child",
P_("Child"),
P_("The child widget"),
GTK_TYPE_WIDGET,
GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, LAST_PROP, props);
g_object_class_override_property (gobject_class, PROP_ACTION_NAME, "action-name");
g_object_class_override_property (gobject_class, PROP_ACTION_TARGET, "action-target");
/**
* GtkButton::clicked:
* @button: the object that received the signal
*
* Emitted when the button has been activated (pressed and released).
*/
button_signals[CLICKED] =
g_signal_new (I_("clicked"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkButtonClass, clicked),
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
/**
* GtkButton::activate:
* @widget: the object which received the signal.
*
* The ::activate signal on GtkButton 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
* #GtkButton::clicked signal.
*/
button_signals[ACTIVATE] =
g_signal_new (I_("activate"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkButtonClass, activate),
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
widget_class->activate_signal = button_signals[ACTIVATE];
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
gtk_widget_class_set_css_name (widget_class, I_("button"));
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_space, 0,
"activate", NULL);
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Space, 0,
"activate", NULL);
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, 0,
"activate", NULL);
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, 0,
"activate", NULL);
gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, 0,
"activate", NULL);
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON);
}
static void
click_pressed_cb (GtkGestureClick *gesture,
guint n_press,
double x,
double y,
GtkWidget *widget)
{
GtkButton *button = GTK_BUTTON (widget);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
GdkEventSequence *sequence;
if (gtk_widget_get_focus_on_click (widget) && !gtk_widget_has_focus (widget))
gtk_widget_grab_focus (widget);
priv->in_button = TRUE;
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), sequence, GTK_EVENT_SEQUENCE_CLAIMED);
if (!priv->activate_timeout)
priv->button_down = TRUE;
}
static gboolean
touch_release_in_button (GtkGestureClick *gesture,
GtkWidget *widget,
double x,
double y)
{
GdkEvent *event;
event = gtk_event_controller_get_current_event (GTK_EVENT_CONTROLLER (gesture));
if (!event)
return FALSE;
if (gdk_event_get_event_type (event) != GDK_TOUCH_END)
return FALSE;
if (!gtk_widget_contains (widget, x, y))
return FALSE;
return TRUE;
}
static void
click_released_cb (GtkGestureClick *gesture,
guint n_press,
double x,
double y,
GtkWidget *widget)
{
GtkButton *button = GTK_BUTTON (widget);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
GdkEventSequence *sequence;
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
gtk_button_do_release (button,
gtk_widget_is_sensitive (GTK_WIDGET (button)) &&
(priv->in_button ||
touch_release_in_button (gesture, widget, x, y)));
sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
if (sequence)
priv->in_button = FALSE;
}
static void
click_gesture_cancel_cb (GtkGesture *gesture,
GdkEventSequence *sequence,
GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (priv->activate_timeout)
gtk_button_finish_activate (button, FALSE);
gtk_button_do_release (button, FALSE);
}
static gboolean
key_controller_key_pressed_cb (GtkEventControllerKey *controller,
guint keyval,
guint keycode,
guint modifiers,
GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
return priv->activate_timeout != 0;
}
static void
key_controller_key_released_cb (GtkEventControllerKey *controller,
guint keyval,
guint keycode,
guint modifiers,
GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (priv->activate_timeout)
gtk_button_finish_activate (button, TRUE);
}
static void
gtk_button_set_child_type (GtkButton *button, guint child_type)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (priv->child_type == child_type)
return;
if (child_type != LABEL_CHILD)
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_LABEL]);
else if (child_type != ICON_CHILD)
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ICON_NAME]);
priv->child_type = child_type;
}
static void
gtk_button_init (GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
GtkEventController *key_controller;
gtk_widget_set_focusable (GTK_WIDGET (button), TRUE);
gtk_widget_set_receives_default (GTK_WIDGET (button), TRUE);
priv->in_button = FALSE;
priv->button_down = FALSE;
priv->use_underline = FALSE;
priv->child_type = WIDGET_CHILD;
priv->gesture = gtk_gesture_click_new ();
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->gesture), FALSE);
gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->gesture), TRUE);
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->gesture), GDK_BUTTON_PRIMARY);
g_signal_connect (priv->gesture, "pressed", G_CALLBACK (click_pressed_cb), button);
g_signal_connect (priv->gesture, "released", G_CALLBACK (click_released_cb), button);
g_signal_connect (priv->gesture, "cancel", G_CALLBACK (click_gesture_cancel_cb), button);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->gesture), GTK_PHASE_CAPTURE);
gtk_widget_add_controller (GTK_WIDGET (button), GTK_EVENT_CONTROLLER (priv->gesture));
key_controller = gtk_event_controller_key_new ();
g_signal_connect (key_controller, "key-pressed", G_CALLBACK (key_controller_key_pressed_cb), button);
g_signal_connect (key_controller, "key-released", G_CALLBACK (key_controller_key_released_cb), button);
gtk_widget_add_controller (GTK_WIDGET (button), key_controller);
}
static void
gtk_button_dispose (GObject *object)
{
GtkButton *button = GTK_BUTTON (object);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_clear_pointer (&priv->child, gtk_widget_unparent);
g_clear_object (&priv->action_helper);
G_OBJECT_CLASS (gtk_button_parent_class)->dispose (object);
}
static void
gtk_button_set_action_name (GtkActionable *actionable,
const char *action_name)
{
GtkButton *button = GTK_BUTTON (actionable);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (!priv->action_helper)
priv->action_helper = gtk_action_helper_new (actionable);
g_signal_handlers_disconnect_by_func (button, gtk_real_button_clicked, NULL);
if (action_name)
g_signal_connect_after (button, "clicked", G_CALLBACK (gtk_real_button_clicked), NULL);
gtk_action_helper_set_action_name (priv->action_helper, action_name);
}
static void
gtk_button_set_action_target_value (GtkActionable *actionable,
GVariant *action_target)
{
GtkButton *button = GTK_BUTTON (actionable);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (!priv->action_helper)
priv->action_helper = gtk_action_helper_new (actionable);
gtk_action_helper_set_action_target_value (priv->action_helper, action_target);
}
static void
gtk_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkButton *button = GTK_BUTTON (object);
switch (prop_id)
{
case PROP_LABEL:
gtk_button_set_label (button, g_value_get_string (value));
break;
case PROP_HAS_FRAME:
gtk_button_set_has_frame (button, g_value_get_boolean (value));
break;
case PROP_USE_UNDERLINE:
gtk_button_set_use_underline (button, g_value_get_boolean (value));
break;
case PROP_ICON_NAME:
gtk_button_set_icon_name (button, g_value_get_string (value));
break;
case PROP_CHILD:
gtk_button_set_child (button, g_value_get_object (value));
break;
case PROP_ACTION_NAME:
gtk_button_set_action_name (GTK_ACTIONABLE (button), g_value_get_string (value));
break;
case PROP_ACTION_TARGET:
gtk_button_set_action_target_value (GTK_ACTIONABLE (button), g_value_get_variant (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkButton *button = GTK_BUTTON (object);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
switch (prop_id)
{
case PROP_LABEL:
g_value_set_string (value, gtk_button_get_label (button));
break;
case PROP_HAS_FRAME:
g_value_set_boolean (value, gtk_button_get_has_frame (button));
break;
case PROP_USE_UNDERLINE:
g_value_set_boolean (value, priv->use_underline);
break;
case PROP_ICON_NAME:
g_value_set_string (value, gtk_button_get_icon_name (button));
break;
case PROP_CHILD:
g_value_set_object (value, priv->child);
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_button_get_action_name (GtkActionable *actionable)
{
GtkButton *button = GTK_BUTTON (actionable);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
return gtk_action_helper_get_action_name (priv->action_helper);
}
static GVariant *
gtk_button_get_action_target_value (GtkActionable *actionable)
{
GtkButton *button = GTK_BUTTON (actionable);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
return gtk_action_helper_get_action_target_value (priv->action_helper);
}
static void
gtk_button_actionable_iface_init (GtkActionableInterface *iface)
{
iface->get_action_name = gtk_button_get_action_name;
iface->set_action_name = gtk_button_set_action_name;
iface->get_action_target_value = gtk_button_get_action_target_value;
iface->set_action_target_value = gtk_button_set_action_target_value;
}
static GtkBuildableIface *parent_buildable_iface;
static void
gtk_button_buildable_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *type)
{
if (GTK_IS_WIDGET (child))
gtk_button_set_child (GTK_BUTTON (buildable), GTK_WIDGET (child));
else
parent_buildable_iface->add_child (buildable, builder, child, type);
}
static void
gtk_button_buildable_iface_init (GtkBuildableIface *iface)
{
parent_buildable_iface = g_type_interface_peek_parent (iface);
iface->add_child = gtk_button_buildable_add_child;
}
/**
* gtk_button_new:
*
* Creates a new #GtkButton widget. To add a child widget to the button,
* use gtk_button_set_child().
*
* Returns: The newly created #GtkButton widget.
*/
GtkWidget*
gtk_button_new (void)
{
return g_object_new (GTK_TYPE_BUTTON, NULL);
}
/**
* gtk_button_new_with_label:
* @label: The text you want the #GtkLabel to hold.
*
* Creates a #GtkButton widget with a #GtkLabel child containing the given
* text.
*
* Returns: The newly created #GtkButton widget.
*/
GtkWidget*
gtk_button_new_with_label (const char *label)
{
return g_object_new (GTK_TYPE_BUTTON, "label", label, NULL);
}
/**
* gtk_button_new_from_icon_name:
* @icon_name: (nullable): an icon name or %NULL
*
* Creates a new button containing an icon from the current icon theme.
*
* If the icon name isnt known, a “broken image” icon will be
* displayed instead. If the current icon theme is changed, the icon
* will be updated appropriately.
*
* Returns: a new #GtkButton displaying the themed icon
*/
GtkWidget*
gtk_button_new_from_icon_name (const char *icon_name)
{
GtkWidget *button;
button = g_object_new (GTK_TYPE_BUTTON,
"icon-name", icon_name,
NULL);
return button;
}
/**
* gtk_button_new_with_mnemonic:
* @label: The text of the button, with an underscore in front of the
* mnemonic character
*
* Creates a new #GtkButton containing a label.
* If characters in @label are preceded by an underscore, they are underlined.
* If you need a literal underscore character in a label, use “__” (two
* underscores). The first underlined character represents a keyboard
* accelerator called a mnemonic.
* Pressing Alt and that key activates the button.
*
* Returns: a new #GtkButton
*/
GtkWidget*
gtk_button_new_with_mnemonic (const char *label)
{
return g_object_new (GTK_TYPE_BUTTON, "label", label, "use-underline", TRUE, NULL);
}
/**
* gtk_button_set_has_frame:
* @button: a #GtkButton
* @has_frame: whether the button should have a visible frame
*
* Sets the style of the button. Buttons can has a flat appearance
* or have a frame drawn around them.
*/
void
gtk_button_set_has_frame (GtkButton *button,
gboolean has_frame)
{
g_return_if_fail (GTK_IS_BUTTON (button));
if (gtk_button_get_has_frame (button) == has_frame)
return;
if (has_frame)
gtk_widget_remove_css_class (GTK_WIDGET (button), GTK_STYLE_CLASS_FLAT);
else
gtk_widget_add_css_class (GTK_WIDGET (button), GTK_STYLE_CLASS_FLAT);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_HAS_FRAME]);
}
/**
* gtk_button_get_has_frame:
* @button: a #GtkButton
*
* Returns whether the button has a frame.
*
* Returns: %TRUE if the button has a frame
*/
gboolean
gtk_button_get_has_frame (GtkButton *button)
{
g_return_val_if_fail (GTK_IS_BUTTON (button), TRUE);
return !gtk_widget_has_css_class (GTK_WIDGET (button), GTK_STYLE_CLASS_FLAT);
}
static void
gtk_button_unrealize (GtkWidget *widget)
{
GtkButton *button = GTK_BUTTON (widget);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (priv->activate_timeout)
gtk_button_finish_activate (button, FALSE);
GTK_WIDGET_CLASS (gtk_button_parent_class)->unrealize (widget);
}
static void
gtk_button_do_release (GtkButton *button,
gboolean emit_clicked)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (priv->button_down)
{
priv->button_down = FALSE;
if (priv->activate_timeout)
return;
if (emit_clicked)
g_signal_emit (button, button_signals[CLICKED], 0);
}
}
static void
gtk_real_button_clicked (GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (priv->action_helper)
gtk_action_helper_activate (priv->action_helper);
}
static gboolean
button_activate_timeout (gpointer data)
{
gtk_button_finish_activate (data, TRUE);
return FALSE;
}
static void
gtk_real_button_activate (GtkButton *button)
{
GtkWidget *widget = GTK_WIDGET (button);
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
if (gtk_widget_get_realized (widget) && !priv->activate_timeout)
{
priv->activate_timeout = g_timeout_add (ACTIVATE_TIMEOUT, button_activate_timeout, button);
g_source_set_name_by_id (priv->activate_timeout, "[gtk] button_activate_timeout");
priv->button_down = TRUE;
}
}
static void
gtk_button_finish_activate (GtkButton *button,
gboolean do_it)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_source_remove (priv->activate_timeout);
priv->activate_timeout = 0;
priv->button_down = FALSE;
if (do_it)
g_signal_emit (button, button_signals[CLICKED], 0);
}
/**
* gtk_button_set_label:
* @button: a #GtkButton
* @label: a string
*
* Sets the text of the label of the button to @label.
*
* This will also clear any previously set labels.
*/
void
gtk_button_set_label (GtkButton *button,
const char *label)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
GtkWidget *child;
g_return_if_fail (GTK_IS_BUTTON (button));
if (priv->child_type != LABEL_CHILD || priv->child == NULL)
{
child = gtk_label_new (NULL);
if (priv->use_underline)
{
gtk_label_set_use_underline (GTK_LABEL (child), priv->use_underline);
gtk_label_set_mnemonic_widget (GTK_LABEL (child), GTK_WIDGET (button));
}
if (GTK_IS_CHECK_BUTTON (button))
gtk_label_set_xalign (GTK_LABEL (child), 0.0);
gtk_button_set_child (button, child);
gtk_widget_remove_css_class (GTK_WIDGET (button), "image-button");
gtk_widget_add_css_class (GTK_WIDGET (button), "text-button");
}
gtk_label_set_label (GTK_LABEL (priv->child), label);
gtk_button_set_child_type (button, LABEL_CHILD);
gtk_accessible_update_property (GTK_ACCESSIBLE (button),
GTK_ACCESSIBLE_PROPERTY_LABEL, label,
-1);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_LABEL]);
}
/**
* gtk_button_get_label:
* @button: a #GtkButton
*
* Fetches the text from the label of the button, as set by
* gtk_button_set_label(). If the label text has not
* been set the return value will be %NULL. This will be the
* case if you create an empty button with gtk_button_new() to
* use as a container.
*
* Returns: (nullable): The text of the label widget. This string is owned
* by the widget and must not be modified or freed.
*/
const char *
gtk_button_get_label (GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_BUTTON (button), NULL);
if (priv->child_type == LABEL_CHILD)
return gtk_label_get_label (GTK_LABEL (priv->child));
return NULL;
}
/**
* gtk_button_set_use_underline:
* @button: a #GtkButton
* @use_underline: %TRUE if underlines in the text indicate mnemonics
*
* If true, an underline in the text of the button label indicates
* the next character should be used for the mnemonic accelerator key.
*/
void
gtk_button_set_use_underline (GtkButton *button,
gboolean use_underline)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_return_if_fail (GTK_IS_BUTTON (button));
use_underline = use_underline != FALSE;
if (use_underline != priv->use_underline)
{
if (priv->child_type == LABEL_CHILD)
{
gtk_label_set_use_underline (GTK_LABEL (priv->child), use_underline);
gtk_label_set_mnemonic_widget (GTK_LABEL (priv->child), GTK_WIDGET (button));
}
priv->use_underline = use_underline;
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_USE_UNDERLINE]);
}
}
/**
* gtk_button_get_use_underline:
* @button: a #GtkButton
*
* Returns whether an embedded underline in the button label indicates a
* mnemonic. See gtk_button_set_use_underline().
*
* Returns: %TRUE if an embedded underline in the button label
* indicates the mnemonic accelerator keys.
*/
gboolean
gtk_button_get_use_underline (GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_BUTTON (button), FALSE);
return priv->use_underline;
}
static void
gtk_button_state_flags_changed (GtkWidget *widget,
GtkStateFlags previous_state)
{
GtkButton *button = GTK_BUTTON (widget);
if (!gtk_widget_is_sensitive (widget))
gtk_button_do_release (button, FALSE);
}
/**
* gtk_button_set_icon_name:
* @button: A #GtkButton
* @icon_name: An icon name
*
* Adds a #GtkImage with the given icon name as a child. If @button already
* contains a child widget, that child widget will be removed and replaced
* with the image.
*/
void
gtk_button_set_icon_name (GtkButton *button,
const char *icon_name)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_return_if_fail (GTK_IS_BUTTON (button));
g_return_if_fail (icon_name != NULL);
if (priv->child_type != ICON_CHILD || priv->child == NULL)
{
GtkWidget *child = gtk_image_new_from_icon_name (icon_name);
gtk_button_set_child (GTK_BUTTON (button), child);
gtk_widget_set_valign (child, GTK_ALIGN_CENTER);
gtk_widget_remove_css_class (GTK_WIDGET (button), "text-button");
gtk_widget_add_css_class (GTK_WIDGET (button), "image-button");
}
else
{
gtk_image_set_from_icon_name (GTK_IMAGE (priv->child), icon_name);
}
gtk_button_set_child_type (button, ICON_CHILD);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ICON_NAME]);
}
/**
* gtk_button_get_icon_name:
* @button: A #GtkButton
*
* Returns the icon name set via gtk_button_set_icon_name().
*
* Returns: (nullable): The icon name set via gtk_button_set_icon_name()
*/
const char *
gtk_button_get_icon_name (GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_BUTTON (button), NULL);
if (priv->child_type == ICON_CHILD)
return gtk_image_get_icon_name (GTK_IMAGE (priv->child));
return NULL;
}
GtkGesture *
gtk_button_get_gesture (GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
return priv->gesture;
}
/**
* gtk_button_set_child:
* @button: a #GtkButton
* @child: (allow-none): the child widget
*
* Sets the child widget of @button.
*/
void
gtk_button_set_child (GtkButton *button,
GtkWidget *child)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_return_if_fail (GTK_IS_BUTTON (button));
g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
g_clear_pointer (&priv->child, gtk_widget_unparent);
priv->child = child;
if (priv->child)
gtk_widget_set_parent (priv->child, GTK_WIDGET (button));
gtk_button_set_child_type (button, WIDGET_CHILD);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_CHILD]);
}
/**
* gtk_button_get_child:
* @button: a #GtkButton
*
* Gets the child widget of @button.
*
* Returns: (nullable) (transfer none): the child widget of @button
*/
GtkWidget *
gtk_button_get_child (GtkButton *button)
{
GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_BUTTON (button), NULL);
return priv->child;
}