gtkbutton.c 33.3 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Elliot Lee's avatar
Elliot Lee committed
2
3
4
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6
7
8
9
10
11
 * 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
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
17
18

/*
19
 * Modified by the GTK+ Team and others 1997-2001.  See the AUTHORS
20
21
22
23
24
 * 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/. 
 */

25
/**
Matthias Clasen's avatar
Matthias Clasen committed
26
 * GtkButton:
27
 *
Matthias Clasen's avatar
Matthias Clasen committed
28
 * The `GtkButton` widget is generally used to trigger a callback function that is
29
 * called when the button is pressed.
30
 *
Matthias Clasen's avatar
Matthias Clasen committed
31
32
 * ![An example GtkButton](button.png)
 *
Matthias Clasen's avatar
Matthias Clasen committed
33
 * The `GtkButton` widget can hold any valid child widget. That is, it can hold
Matthias Clasen's avatar
Matthias Clasen committed
34
35
 * almost any other standard `GtkWidget`. The most commonly used child is the
 * `GtkLabel`.
36
37
38
 *
 * # CSS nodes
 *
Matthias Clasen's avatar
Matthias Clasen committed
39
 * `GtkButton` has a single CSS node with name button. The node will get the
40
41
 * 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.
42
43
 * When activating a button via the keyboard, the button will temporarily
 * gain the .keyboard-activating style class.
44
 *
Matthias Clasen's avatar
Matthias Clasen committed
45
 * Other style classes that are commonly used with `GtkButton` include
46
47
 * .suggested-action and .destructive-action. In special cases, buttons
 * can be made round by adding the .circular style class.
48
 *
Matthias Clasen's avatar
Matthias Clasen committed
49
50
51
52
53
 * Button-like widgets like [class@Gtk.ToggleButton], [class@Gtk.MenuButton],
 * [class@Gtk.VolumeButton], [class@Gtk.LockButton], [class@Gtk.ColorButton]
 * or [class@Gtk.FontButton] use style classes such as .toggle, .popup, .scale,
 * .lock, .color on the button node to differentiate themselves from a plain
 * `GtkButton`.
54
55
56
 *
 * # Accessibility
 *
Matthias Clasen's avatar
Matthias Clasen committed
57
 * `GtkButton` uses the %GTK_ACCESSIBLE_ROLE_BUTTON role.
58
59
 */

60
#include "config.h"
61
62
63

#include "gtkbuttonprivate.h"

64
#include "gtkactionhelperprivate.h"
65
#include "gtkbuildable.h"
66
#include "gtkcheckbutton.h"
67
#include "gtkgestureclick.h"
68
#include "gtkeventcontrollerkey.h"
69
#include "gtkbinlayout.h"
70
71
#include "gtkimage.h"
#include "gtkintl.h"
Elliot Lee's avatar
Elliot Lee committed
72
73
#include "gtklabel.h"
#include "gtkmain.h"
74
#include "gtkmarshalers.h"
75
#include "gtkprivate.h"
76
#include "gtktypebuiltins.h"
77
#include "gtkwidgetprivate.h"
78
#include "gtkshortcuttrigger.h"
79
80

#include <string.h>
Elliot Lee's avatar
Elliot Lee committed
81

Matthias Clasen's avatar
Matthias Clasen committed
82
/* Time out before giving up on getting a key release when animating
83
84
85
 * the close button.
 */
#define ACTIVATE_TIMEOUT 250
Elliot Lee's avatar
Elliot Lee committed
86

Timm Bäder's avatar
Timm Bäder committed
87
88
struct _GtkButtonPrivate
{
89
90
  GtkWidget             *child;

Timm Bäder's avatar
Timm Bäder committed
91
92
93
94
95
96
97
98
99
100
  GtkActionHelper       *action_helper;

  GtkGesture            *gesture;

  guint                  activate_timeout;

  guint          button_down           : 1;
  guint          use_underline         : 1;
  guint          child_type            : 2;
};
101

Elliot Lee's avatar
Elliot Lee committed
102
103
enum {
  CLICKED,
104
  ACTIVATE,
Elliot Lee's avatar
Elliot Lee committed
105
106
  LAST_SIGNAL
};
107

108
enum {
109
110
  PROP_0,
  PROP_LABEL,
Matthias Clasen's avatar
Matthias Clasen committed
111
  PROP_HAS_FRAME,
112
  PROP_USE_UNDERLINE,
Timm Bäder's avatar
Timm Bäder committed
113
  PROP_ICON_NAME,
Matthias Clasen's avatar
Matthias Clasen committed
114
  PROP_CHILD,
115
116

  /* actionable properties */
117
118
  PROP_ACTION_NAME,
  PROP_ACTION_TARGET,
119
  LAST_PROP = PROP_ACTION_NAME
120
121
};

122
123
124
125
126
127
enum {
  LABEL_CHILD,
  ICON_CHILD,
  WIDGET_CHILD
};

128

129
static void gtk_button_dispose        (GObject            *object);
130
131
132
133
134
135
136
137
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);
138
139
static void gtk_button_unrealize (GtkWidget * widget);
static void gtk_real_button_clicked (GtkButton * button);
140
141
static void gtk_real_button_activate  (GtkButton          *button);
static void gtk_button_finish_activate (GtkButton         *button,
Timm Bäder's avatar
Timm Bäder committed
142
                                        gboolean           do_it);
Elliot Lee's avatar
Elliot Lee committed
143

144
145
static void gtk_button_state_flags_changed (GtkWidget     *widget,
                                            GtkStateFlags  previous_state);
146
147
static void gtk_button_do_release      (GtkButton             *button,
                                        gboolean               emit_clicked);
148
static void gtk_button_set_child_type (GtkButton *button, guint child_type);
149

150
static void gtk_button_buildable_iface_init      (GtkBuildableIface *iface);
151
static void gtk_button_actionable_iface_init     (GtkActionableInterface *iface);
152

153
static GParamSpec *props[LAST_PROP] = { NULL, };
154
static guint button_signals[LAST_SIGNAL] = { 0 };
Elliot Lee's avatar
Elliot Lee committed
155

156
G_DEFINE_TYPE_WITH_CODE (GtkButton, gtk_button, GTK_TYPE_WIDGET,
157
                         G_ADD_PRIVATE (GtkButton)
158
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_button_buildable_iface_init)
159
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, gtk_button_actionable_iface_init))
Elliot Lee's avatar
Elliot Lee committed
160

161
static void
162
163
164
gtk_button_compute_expand (GtkWidget *widget,
                           gboolean  *hexpand,
                           gboolean  *vexpand)
165
{
166
  GtkButtonPrivate *priv = gtk_button_get_instance_private (GTK_BUTTON (widget));
167

168
169
170
171
172
173
174
175
176
177
  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;
    }
178
179
}

180
181
static GtkSizeRequestMode
gtk_button_get_request_mode (GtkWidget *widget)
182
{
Timm Bäder's avatar
Timm Bäder committed
183
184
  GtkButtonPrivate *priv = gtk_button_get_instance_private (GTK_BUTTON (widget));

185
186
187
188
  if (priv->child)
    return gtk_widget_get_request_mode (priv->child);
  else
    return GTK_SIZE_REQUEST_CONSTANT_SIZE;
189
190
}

Elliot Lee's avatar
Elliot Lee committed
191
192
193
static void
gtk_button_class_init (GtkButtonClass *klass)
{
194
195
  const guint activate_keyvals[] = { GDK_KEY_space, GDK_KEY_KP_Space, GDK_KEY_Return,
                                     GDK_KEY_ISO_Enter, GDK_KEY_KP_Enter };
Manish Singh's avatar
Manish Singh committed
196
  GObjectClass *gobject_class;
Elliot Lee's avatar
Elliot Lee committed
197
  GtkWidgetClass *widget_class;
198
  GtkShortcutAction *activate_action;
Elliot Lee's avatar
Elliot Lee committed
199

Manish Singh's avatar
Manish Singh committed
200
  gobject_class = G_OBJECT_CLASS (klass);
Elliot Lee's avatar
Elliot Lee committed
201
  widget_class = (GtkWidgetClass*) klass;
202

203
  gobject_class->dispose      = gtk_button_dispose;
Manish Singh's avatar
Manish Singh committed
204
205
  gobject_class->set_property = gtk_button_set_property;
  gobject_class->get_property = gtk_button_get_property;
206

207
  widget_class->unrealize = gtk_button_unrealize;
208
  widget_class->state_flags_changed = gtk_button_state_flags_changed;
209
210
  widget_class->compute_expand = gtk_button_compute_expand;
  widget_class->get_request_mode = gtk_button_get_request_mode;
211

212
  klass->clicked = NULL;
213
  klass->activate = gtk_real_button_activate;
214

Matthias Clasen's avatar
Matthias Clasen committed
215
  /**
216
   * GtkButton:label: (attributes org.gtk.Property.get=gtk_button_get_label org.gtk.Property.set=gtk_button_set_label)
Matthias Clasen's avatar
Matthias Clasen committed
217
218
219
   *
   * Text of the label inside the button, if the button contains a label widget.
   */
220
  props[PROP_LABEL] =
221
    g_param_spec_string ("label", NULL, NULL,
222
                         NULL,
Timm Bäder's avatar
Timm Bäder committed
223
                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
Timm Bäder's avatar
Timm Bäder committed
224

Matthias Clasen's avatar
Matthias Clasen committed
225
  /**
226
   * GtkButton:use-underline: (attributes org.gtk.Property.get=gtk_button_get_use_underline org.gtk.Property.set=gtk_button_set_use_underline)
Matthias Clasen's avatar
Matthias Clasen committed
227
228
229
230
   *
   * If set, an underline in the text indicates that the following character is
   * to be used as mnemonic.
   */
231
  props[PROP_USE_UNDERLINE] =
232
    g_param_spec_boolean ("use-underline", NULL, NULL,
233
                          FALSE,
Timm Bäder's avatar
Timm Bäder committed
234
                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
Timm Bäder's avatar
Timm Bäder committed
235

Matthias Clasen's avatar
Matthias Clasen committed
236
  /**
237
   * GtkButton:has-frame: (attributes org.gtk.Property.get=gtk_button_get_has_frame org.gtk.Property.set=gtk_button_set_has_frame)
Matthias Clasen's avatar
Matthias Clasen committed
238
239
240
   *
   * Whether the button has a frame.
   */
Matthias Clasen's avatar
Matthias Clasen committed
241
  props[PROP_HAS_FRAME] =
242
    g_param_spec_boolean ("has-frame", NULL, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
243
244
                          TRUE,
                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
Elliot Lee's avatar
Elliot Lee committed
245

Matthias Clasen's avatar
Matthias Clasen committed
246
  /**
247
   * GtkButton:icon-name: (attributes org.gtk.Property.get=gtk_button_get_icon_name org.gtk.Property.set=gtk_button_set_icon_name)
Matthias Clasen's avatar
Matthias Clasen committed
248
249
250
   *
   * The name of the icon used to automatically populate the button.
   */
Timm Bäder's avatar
Timm Bäder committed
251
  props[PROP_ICON_NAME] =
252
    g_param_spec_string ("icon-name", NULL, NULL,
Timm Bäder's avatar
Timm Bäder committed
253
                         NULL,
254
                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
Matthias Clasen's avatar
Matthias Clasen committed
255

Matthias Clasen's avatar
Matthias Clasen committed
256
  /**
257
   * GtkButton:child: (attributes org.gtk.Property.get=gtk_button_get_child org.gtk.Property.set=gtk_button_set_child)
Matthias Clasen's avatar
Matthias Clasen committed
258
259
260
   *
   * The child widget.
   */
Matthias Clasen's avatar
Matthias Clasen committed
261
  props[PROP_CHILD] =
262
    g_param_spec_object ("child", NULL, NULL,
Matthias Clasen's avatar
Matthias Clasen committed
263
264
                         GTK_TYPE_WIDGET,
                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
265
266

  g_object_class_install_properties (gobject_class, LAST_PROP, props);
267

268
269
270
  g_object_class_override_property (gobject_class, PROP_ACTION_NAME, "action-name");
  g_object_class_override_property (gobject_class, PROP_ACTION_TARGET, "action-target");

271
272
273
274
275
  /**
   * GtkButton::clicked:
   * @button: the object that received the signal
   *
   * Emitted when the button has been activated (pressed and released).
276
   */
Elliot Lee's avatar
Elliot Lee committed
277
  button_signals[CLICKED] =
Matthias Clasen's avatar
Matthias Clasen committed
278
    g_signal_new (I_("clicked"),
Timm Bäder's avatar
Timm Bäder committed
279
280
281
282
283
284
                  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);
285

286
287
  /**
   * GtkButton::activate:
Matthias Clasen's avatar
Matthias Clasen committed
288
   * @widget: the object which received the signal.
289
   *
Matthias Clasen's avatar
Matthias Clasen committed
290
291
292
293
   * Emitted to animate press then release.
   *
   * This is an action signal. Applications should never connect
   * to this signal, but use the [signal@Gtk.Button::clicked] signal.
294
   */
295
  button_signals[ACTIVATE] =
Matthias Clasen's avatar
Matthias Clasen committed
296
    g_signal_new (I_("activate"),
Timm Bäder's avatar
Timm Bäder committed
297
298
299
300
301
302
                  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);
303

304
305
306
  gtk_widget_class_set_activate_signal (widget_class, button_signals[ACTIVATE]);

  activate_action = gtk_signal_action_new ("activate");
307
308
309
310
  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));
311

312
313
314
315
      gtk_widget_class_add_shortcut (widget_class, activate_shortcut);
      g_object_unref (activate_shortcut);
    }
  g_object_unref (activate_action);
316
317

  gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON);
318
319
  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
  gtk_widget_class_set_css_name (widget_class, I_("button"));
Elliot Lee's avatar
Elliot Lee committed
320
321
}

322
static void
323
324
click_pressed_cb (GtkGestureClick *gesture,
                  guint            n_press,
325
326
                  double           x,
                  double           y,
327
                  GtkWidget       *widget)
328
329
{
  GtkButton *button = GTK_BUTTON (widget);
Timm Bäder's avatar
Timm Bäder committed
330
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
331

332
  if (gtk_widget_get_focus_on_click (widget) && !gtk_widget_has_focus (widget))
333
    gtk_widget_grab_focus (widget);
334

335
  if (!priv->activate_timeout)
336
    priv->button_down = TRUE;
337
338
339
}

static void
340
341
click_released_cb (GtkGestureClick *gesture,
                   guint            n_press,
342
343
                   double           x,
                   double           y,
344
                   GtkWidget       *widget)
345
346
{
  GtkButton *button = GTK_BUTTON (widget);
347

348
  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
349
350
  gtk_button_do_release (button,
                         gtk_widget_is_sensitive (GTK_WIDGET (button)) &&
351
                         gtk_widget_contains (widget, x, y));
352
353
}

354
static void
355
356
357
click_gesture_cancel_cb (GtkGesture       *gesture,
                         GdkEventSequence *sequence,
                         GtkButton        *button)
358
{
359
360
361
362
363
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);

  if (priv->activate_timeout)
    gtk_button_finish_activate (button, FALSE);

364
365
366
  gtk_button_do_release (button, FALSE);
}

367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
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);
}

392
393
394
395
396
397
398
399
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;

400
401
402
403
404
405
406
407
408
409
  if (child_type == LABEL_CHILD)
    gtk_widget_add_css_class (GTK_WIDGET (button), "text-button");
  else
    gtk_widget_remove_css_class (GTK_WIDGET (button), "text-button");

  if (child_type == ICON_CHILD)
    gtk_widget_add_css_class (GTK_WIDGET (button), "image-button");
  else
    gtk_widget_remove_css_class (GTK_WIDGET (button), "image-button");

410
411
412
413
414
415
416
417
  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;
}

Elliot Lee's avatar
Elliot Lee committed
418
419
420
static void
gtk_button_init (GtkButton *button)
{
Timm Bäder's avatar
Timm Bäder committed
421
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
422
  GtkEventController *key_controller;
423

424
  gtk_widget_set_focusable (GTK_WIDGET (button), TRUE);
425
  gtk_widget_set_receives_default (GTK_WIDGET (button), TRUE);
Elliot Lee's avatar
Elliot Lee committed
426

427
428
  priv->button_down = FALSE;
  priv->use_underline = FALSE;
429
  priv->child_type = WIDGET_CHILD;
430

431
  priv->gesture = gtk_gesture_click_new ();
432
433
  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->gesture), FALSE);
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->gesture), GDK_BUTTON_PRIMARY);
434
435
436
  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);
437
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->gesture), GTK_PHASE_CAPTURE);
438
  gtk_widget_add_controller (GTK_WIDGET (button), GTK_EVENT_CONTROLLER (priv->gesture));
439

440
441
442
443
  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);
444
445
}

Matthias Clasen's avatar
Matthias Clasen committed
446
static void
447
448
449
gtk_button_dispose (GObject *object)
{
  GtkButton *button = GTK_BUTTON (object);
Timm Bäder's avatar
Timm Bäder committed
450
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
451

452
  g_clear_pointer (&priv->child, gtk_widget_unparent);
453
  g_clear_object (&priv->action_helper);
454

455
456
457
  G_OBJECT_CLASS (gtk_button_parent_class)->dispose (object);
}

458
459
static void
gtk_button_set_action_name (GtkActionable *actionable,
Benjamin Otte's avatar
Benjamin Otte committed
460
                            const char    *action_name)
461
462
{
  GtkButton *button = GTK_BUTTON (actionable);
Timm Bäder's avatar
Timm Bäder committed
463
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
464

Timm Bäder's avatar
Timm Bäder committed
465
466
  if (!priv->action_helper)
    priv->action_helper = gtk_action_helper_new (actionable);
467

468
469
470
  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);
471

Timm Bäder's avatar
Timm Bäder committed
472
  gtk_action_helper_set_action_name (priv->action_helper, action_name);
473
474
475
476
477
478
479
}

static void
gtk_button_set_action_target_value (GtkActionable *actionable,
                                    GVariant      *action_target)
{
  GtkButton *button = GTK_BUTTON (actionable);
Timm Bäder's avatar
Timm Bäder committed
480
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
481

Timm Bäder's avatar
Timm Bäder committed
482
483
  if (!priv->action_helper)
    priv->action_helper = gtk_action_helper_new (actionable);
484

Timm Bäder's avatar
Timm Bäder committed
485
  gtk_action_helper_set_action_target_value (priv->action_helper, action_target);
486
487
}

Elliot Lee's avatar
Elliot Lee committed
488
static void
489
490
491
492
gtk_button_set_property (GObject         *object,
                         guint            prop_id,
                         const GValue    *value,
                         GParamSpec      *pspec)
Elliot Lee's avatar
Elliot Lee committed
493
{
494
  GtkButton *button = GTK_BUTTON (object);
Elliot Lee's avatar
Elliot Lee committed
495

496
  switch (prop_id)
497
    {
498
    case PROP_LABEL:
499
      gtk_button_set_label (button, g_value_get_string (value));
500
      break;
Matthias Clasen's avatar
Matthias Clasen committed
501
502
    case PROP_HAS_FRAME:
      gtk_button_set_has_frame (button, g_value_get_boolean (value));
503
      break;
504
505
506
    case PROP_USE_UNDERLINE:
      gtk_button_set_use_underline (button, g_value_get_boolean (value));
      break;
Timm Bäder's avatar
Timm Bäder committed
507
508
    case PROP_ICON_NAME:
      gtk_button_set_icon_name (button, g_value_get_string (value));
509
      break;
Matthias Clasen's avatar
Matthias Clasen committed
510
511
512
    case PROP_CHILD:
      gtk_button_set_child (button, g_value_get_object (value));
      break;
513
514
515
516
517
518
    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;
519
    default:
520
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
521
522
523
524
525
      break;
    }
}

static void
526
527
528
529
gtk_button_get_property (GObject         *object,
                         guint            prop_id,
                         GValue          *value,
                         GParamSpec      *pspec)
530
{
531
  GtkButton *button = GTK_BUTTON (object);
Timm Bäder's avatar
Timm Bäder committed
532
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
533

534
  switch (prop_id)
535
    {
536
    case PROP_LABEL:
537
      g_value_set_string (value, gtk_button_get_label (button));
538
      break;
Matthias Clasen's avatar
Matthias Clasen committed
539
540
    case PROP_HAS_FRAME:
      g_value_set_boolean (value, gtk_button_get_has_frame (button));
541
      break;
542
    case PROP_USE_UNDERLINE:
543
      g_value_set_boolean (value, priv->use_underline);
544
      break;
Timm Bäder's avatar
Timm Bäder committed
545
546
    case PROP_ICON_NAME:
      g_value_set_string (value, gtk_button_get_icon_name (button));
547
      break;
Matthias Clasen's avatar
Matthias Clasen committed
548
    case PROP_CHILD:
549
      g_value_set_object (value, priv->child);
Matthias Clasen's avatar
Matthias Clasen committed
550
      break;
551
    case PROP_ACTION_NAME:
552
      g_value_set_string (value, gtk_action_helper_get_action_name (priv->action_helper));
553
554
      break;
    case PROP_ACTION_TARGET:
555
      g_value_set_variant (value, gtk_action_helper_get_action_target_value (priv->action_helper));
556
      break;
Tim Janik's avatar
Tim Janik committed
557
    default:
558
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Tim Janik's avatar
Tim Janik committed
559
      break;
Elliot Lee's avatar
Elliot Lee committed
560
561
562
    }
}

Benjamin Otte's avatar
Benjamin Otte committed
563
static const char *
564
565
566
gtk_button_get_action_name (GtkActionable *actionable)
{
  GtkButton *button = GTK_BUTTON (actionable);
Timm Bäder's avatar
Timm Bäder committed
567
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
568

Timm Bäder's avatar
Timm Bäder committed
569
  return gtk_action_helper_get_action_name (priv->action_helper);
570
571
572
573
574
575
}

static GVariant *
gtk_button_get_action_target_value (GtkActionable *actionable)
{
  GtkButton *button = GTK_BUTTON (actionable);
Timm Bäder's avatar
Timm Bäder committed
576
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
577

Timm Bäder's avatar
Timm Bäder committed
578
  return gtk_action_helper_get_action_target_value (priv->action_helper);
579
580
581
582
583
584
585
586
587
588
589
}

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;
}

590
591
592
593
594
595
static GtkBuildableIface *parent_buildable_iface;

static void
gtk_button_buildable_add_child (GtkBuildable *buildable,
                                GtkBuilder   *builder,
                                GObject      *child,
Benjamin Otte's avatar
Benjamin Otte committed
596
                                const char   *type)
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
{
  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;
}

612
613
614
/**
 * gtk_button_new:
 *
Matthias Clasen's avatar
Matthias Clasen committed
615
 * Creates a new `GtkButton` widget.
616
 *
Matthias Clasen's avatar
Matthias Clasen committed
617
618
619
 * To add a child widget to the button, use [method@Gtk.Button.set_child].
 *
 * Returns: The newly created `GtkButton` widget.
620
 */
Elliot Lee's avatar
Elliot Lee committed
621
GtkWidget*
622
gtk_button_new (void)
Elliot Lee's avatar
Elliot Lee committed
623
{
Manish Singh's avatar
Manish Singh committed
624
  return g_object_new (GTK_TYPE_BUTTON, NULL);
Elliot Lee's avatar
Elliot Lee committed
625
626
}

627
628
/**
 * gtk_button_new_with_label:
Matthias Clasen's avatar
Matthias Clasen committed
629
 * @label: The text you want the `GtkLabel` to hold
630
 *
Matthias Clasen's avatar
Matthias Clasen committed
631
 * Creates a `GtkButton` widget with a `GtkLabel` child.
632
 *
Matthias Clasen's avatar
Matthias Clasen committed
633
 * Returns: The newly created `GtkButton` widget
634
 */
635
GtkWidget*
Benjamin Otte's avatar
Benjamin Otte committed
636
gtk_button_new_with_label (const char *label)
637
{
638
  return g_object_new (GTK_TYPE_BUTTON, "label", label, NULL);
639
640
}

641
642
/**
 * gtk_button_new_from_icon_name:
Matthias Clasen's avatar
Matthias Clasen committed
643
 * @icon_name: (nullable): an icon name
644
 *
Matthias Clasen's avatar
Matthias Clasen committed
645
646
 * Creates a new button containing an icon from the current icon theme.
 *
647
 * If the icon name isn’t known, a “broken image” icon will be
648
649
 * displayed instead. If the current icon theme is changed, the icon
 * will be updated appropriately.
Matthias Clasen's avatar
Matthias Clasen committed
650
 *
Matthias Clasen's avatar
Matthias Clasen committed
651
 * Returns: a new `GtkButton` displaying the themed icon
652
 */
653
GtkWidget*
Benjamin Otte's avatar
Benjamin Otte committed
654
gtk_button_new_from_icon_name (const char *icon_name)
655
656
657
{
  GtkWidget *button;

Benjamin Otte's avatar
Benjamin Otte committed
658
659
660
  button = g_object_new (GTK_TYPE_BUTTON,
                         "icon-name", icon_name,
                         NULL);
661
662
663
664

  return button;
}

665
666
667
/**
 * gtk_button_new_with_mnemonic:
 * @label: The text of the button, with an underscore in front of the
Matthias Clasen's avatar
Matthias Clasen committed
668
 *   mnemonic character
669
 *
Matthias Clasen's avatar
Matthias Clasen committed
670
671
 * Creates a new `GtkButton` containing a label.
 *
672
 * If characters in @label are preceded by an underscore, they are underlined.
673
 * If you need a literal underscore character in a label, use “__” (two
Matthias Clasen's avatar
Matthias Clasen committed
674
 * underscores). The first underlined character represents a keyboard
Matthias Clasen's avatar
Matthias Clasen committed
675
 * accelerator called a mnemonic. Pressing Alt and that key activates the button.
Matthias Clasen's avatar
Matthias Clasen committed
676
 *
Matthias Clasen's avatar
Matthias Clasen committed
677
 * Returns: a new `GtkButton`
678
 */
679
GtkWidget*
Benjamin Otte's avatar
Benjamin Otte committed
680
gtk_button_new_with_mnemonic (const char *label)
681
{
682
  return g_object_new (GTK_TYPE_BUTTON, "label", label, "use-underline", TRUE,  NULL);
683
684
}

685
/**
686
 * gtk_button_set_has_frame: (attributes org.gtk.Method.set_property=has-frame)
Matthias Clasen's avatar
Matthias Clasen committed
687
 * @button: a `GtkButton`
Matthias Clasen's avatar
Matthias Clasen committed
688
 * @has_frame: whether the button should have a visible frame
689
 *
Matthias Clasen's avatar
Matthias Clasen committed
690
691
692
 * Sets the style of the button.
 *
 * Buttons can has a flat appearance or have a frame drawn around them.
693
 */
694
void
Matthias Clasen's avatar
Matthias Clasen committed
695
696
gtk_button_set_has_frame (GtkButton *button,
                          gboolean   has_frame)
697
{
698

699
700
  g_return_if_fail (GTK_IS_BUTTON (button));

Matthias Clasen's avatar
Matthias Clasen committed
701
702
  if (gtk_button_get_has_frame (button) == has_frame)
    return;
703

Matthias Clasen's avatar
Matthias Clasen committed
704
  if (has_frame)
Matthias Clasen's avatar
Matthias Clasen committed
705
    gtk_widget_remove_css_class (GTK_WIDGET (button), "flat");
Matthias Clasen's avatar
Matthias Clasen committed
706
  else
Matthias Clasen's avatar
Matthias Clasen committed
707
    gtk_widget_add_css_class (GTK_WIDGET (button), "flat");
Matthias Clasen's avatar
Matthias Clasen committed
708
709

  g_object_notify_by_pspec (G_OBJECT (button), props[PROP_HAS_FRAME]);
710
711
}

712
/**
713
 * gtk_button_get_has_frame: (attributes org.gtk.Method.get_property=has-frame)
Matthias Clasen's avatar
Matthias Clasen committed
714
 * @button: a `GtkButton`
715
 *
Matthias Clasen's avatar
Matthias Clasen committed
716
 * Returns whether the button has a frame.
717
 *
Matthias Clasen's avatar
Matthias Clasen committed
718
 * Returns: %TRUE if the button has a frame
719
 */
Matthias Clasen's avatar
Matthias Clasen committed
720
721
gboolean
gtk_button_get_has_frame (GtkButton *button)
722
{
Matthias Clasen's avatar
Matthias Clasen committed
723
  g_return_val_if_fail (GTK_IS_BUTTON (button), TRUE);
724

Matthias Clasen's avatar
Matthias Clasen committed
725
  return !gtk_widget_has_css_class (GTK_WIDGET (button), "flat");
726
727
}

728
729
730
731
static void
gtk_button_unrealize (GtkWidget *widget)
{
  GtkButton *button = GTK_BUTTON (widget);
Timm Bäder's avatar
Timm Bäder committed
732
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
733

734
  if (priv->activate_timeout)
735
    gtk_button_finish_activate (button, FALSE);
736

Matthias Clasen's avatar
Matthias Clasen committed
737
  GTK_WIDGET_CLASS (gtk_button_parent_class)->unrealize (widget);
738
739
}

Benjamin Otte's avatar
Benjamin Otte committed
740
741
742
743
static void
gtk_button_do_release (GtkButton *button,
                       gboolean   emit_clicked)
{
Timm Bäder's avatar
Timm Bäder committed
744
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
Benjamin Otte's avatar
Benjamin Otte committed
745
746
747
748
749
750

  if (priv->button_down)
    {
      priv->button_down = FALSE;

      if (priv->activate_timeout)
Timm Bäder's avatar
Timm Bäder committed
751
        return;
Benjamin Otte's avatar
Benjamin Otte committed
752
753

      if (emit_clicked)
754
        g_signal_emit (button, button_signals[CLICKED], 0);
Benjamin Otte's avatar
Benjamin Otte committed
755
756
757
    }
}

Elliot Lee's avatar
Elliot Lee committed
758
static void
759
gtk_real_button_clicked (GtkButton *button)
760
{
Timm Bäder's avatar
Timm Bäder committed
761
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
762

763
764
  if (priv->action_helper)
    gtk_action_helper_activate (priv->action_helper);
765
766
}

767
768
769
770
771
772
773
774
775
776
777
778
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);
Timm Bäder's avatar
Timm Bäder committed
779
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
780

781
  if (gtk_widget_get_realized (widget) && !priv->activate_timeout)
782
    {
783
      priv->activate_timeout = g_timeout_add (ACTIVATE_TIMEOUT, button_activate_timeout, button);
784
      gdk_source_set_static_name_by_id (priv->activate_timeout, "[gtk] button_activate_timeout");
785
786

      gtk_widget_add_css_class (GTK_WIDGET (button), "keyboard-activating");
787
      priv->button_down = TRUE;
788
789
790
791
792
    }
}

static void
gtk_button_finish_activate (GtkButton *button,
Timm Bäder's avatar
Timm Bäder committed
793
                            gboolean   do_it)
794
{
Timm Bäder's avatar
Timm Bäder committed
795
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
796

797
798
  gtk_widget_remove_css_class (GTK_WIDGET (button), "keyboard-activating");

799
800
  g_source_remove (priv->activate_timeout);
  priv->activate_timeout = 0;
801

802
  priv->button_down = FALSE;
803

804
  if (do_it)
805
    g_signal_emit (button, button_signals[CLICKED], 0);
806
807
}

808
/**
809
 * gtk_button_set_label: (attributes org.gtk.Method.set_property=label)
Matthias Clasen's avatar
Matthias Clasen committed
810
 * @button: a `GtkButton`
811
812
 * @label: a string
 *
Timm Bäder's avatar
Timm Bäder committed
813
 * Sets the text of the label of the button to @label.
814
815
 *
 * This will also clear any previously set labels.
816
 */
817
818
void
gtk_button_set_label (GtkButton   *button,
Benjamin Otte's avatar
Benjamin Otte committed
819
                      const char *label)
820
{
Timm Bäder's avatar
Timm Bäder committed
821
822
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);
  GtkWidget *child;
823

824
825
  g_return_if_fail (GTK_IS_BUTTON (button));

826
  if (priv->child_type != LABEL_CHILD || priv->child == NULL)
Timm Bäder's avatar
Timm Bäder committed
827
    {
828
829
      child = gtk_label_new (NULL);
      if (priv->use_underline)
Timm Bäder's avatar
Timm Bäder committed
830
        {
831
832
          gtk_label_set_use_underline (GTK_LABEL (child), priv->use_underline);
          gtk_label_set_mnemonic_widget (GTK_LABEL (child), GTK_WIDGET (button));
Timm Bäder's avatar
Timm Bäder committed
833
        }
834
      if (GTK_IS_CHECK_BUTTON (button))
835
836
837
        gtk_label_set_xalign (GTK_LABEL (child), 0.0);

      gtk_button_set_child (button,  child);
Timm Bäder's avatar
Timm Bäder committed
838
    }
839

840
  gtk_label_set_label (GTK_LABEL (priv->child), label);
841
  gtk_button_set_child_type (button, LABEL_CHILD);
842

843
844
845
846
  gtk_accessible_update_property (GTK_ACCESSIBLE (button),
                                  GTK_ACCESSIBLE_PROPERTY_LABEL, label,
                                  -1);

847
  g_object_notify_by_pspec (G_OBJECT (button), props[PROP_LABEL]);
848
849
850
}

/**
851
 * gtk_button_get_label: (attributes org.gtk.Method.get_property=label)
Matthias Clasen's avatar
Matthias Clasen committed
852
 * @button: a `GtkButton`
853
 *
Matthias Clasen's avatar
Matthias Clasen committed
854
855
856
857
858
 * Fetches the text from the label of the button.
 *
 * If the label text has not been set with [method@Gtk.Button.set_label]
 * the return value will be %NULL. This will be the case if you create
 * an empty button with [ctor@Gtk.Button.new] to use as a container.
859
 *
Timm Bäder's avatar
Timm Bäder committed
860
 * Returns: (nullable): The text of the label widget. This string is owned
Soren Sandmann's avatar
Soren Sandmann committed
861
 * by the widget and must not be modified or freed.
862
 */
Benjamin Otte's avatar
Benjamin Otte committed
863
const char *
864
865
gtk_button_get_label (GtkButton *button)
{
866
867
  GtkButtonPrivate *priv = gtk_button_get_instance_private (button);

868
  g_return_val_if_fail (GTK_IS_BUTTON (button), NULL);
869