hdy-action-row.c 28 KB
Newer Older
Adrien Plazas's avatar
Adrien Plazas committed
1
2
3
/*
 * Copyright (C) 2018 Purism SPC
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
Adrien Plazas's avatar
Adrien Plazas committed
5
6
 */

Bastien Nocera's avatar
Bastien Nocera committed
7
#include "config.h"
Adrien Plazas's avatar
Adrien Plazas committed
8
9
#include "hdy-action-row.h"

Bastien Nocera's avatar
Bastien Nocera committed
10
#include <glib/gi18n-lib.h>
Adrien Plazas's avatar
Adrien Plazas committed
11
12

/**
13
 * HdyActionRow:
Adrien Plazas's avatar
Adrien Plazas committed
14
 *
15
16
17
 * A [class@Gtk.ListBoxRow] used to present actions.
 *
 * The `HdyActionRow` widget can have a title, a subtitle and an icon. The row
18
 * can receive additional widgets at its end, or prefix widgets at its start.
Adrien Plazas's avatar
Adrien Plazas committed
19
 *
20
 * It is convenient to present a preference and its related actions.
Adrien Plazas's avatar
Adrien Plazas committed
21
 *
22
 * `HdyActionRow` is unactivatable by default, giving it an activatable widget
23
24
25
 * will automatically make it activatable, but unsetting it won't change the
 * row's activatability.
 *
26
 * ## HdyActionRow as GtkBuildable
Adrien Plazas's avatar
Adrien Plazas committed
27
 *
28
29
30
 * The `HdyActionRow` implementation of the [iface@Gtk.Buildable] interface
 * supports adding a child at its end by specifying “suffix” or omitting the
 * “type” attribute of a <child> element.
Adrien Plazas's avatar
Adrien Plazas committed
31
 *
32
33
 * It also supports adding a child as a prefix widget by specifying “prefix” as
 * the “type” attribute of a <child> element.
Guido Günther's avatar
Guido Günther committed
34
 *
35
 * ## CSS nodes
36
 *
37
 * `HdyActionRow` has a main CSS node with name `row`.
38
 *
39
40
 * It contains the subnode `box.header` for its main horizontal box, and
 * `box.title` for the vertical box containing the title and subtitle labels.
41
 *
42
43
 * It contains subnodes `label.title` and `label.subtitle` representing
 * respectively the title label and subtitle label.
44
 *
45
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
46
47
48
49
50
51
52
53
 */

typedef struct
{
  GtkBox *header;
  GtkImage *image;
  GtkBox *prefixes;
  GtkLabel *subtitle;
54
  GtkBox *suffixes;
Adrien Plazas's avatar
Adrien Plazas committed
55
56
57
58
59
60
  GtkLabel *title;
  GtkBox *title_box;

  GtkWidget *previous_parent;

  gboolean use_underline;
61
62
  gint title_lines;
  gint subtitle_lines;
63
  GtkWidget *activatable_widget;
Adrien Plazas's avatar
Adrien Plazas committed
64
65
66
67
} HdyActionRowPrivate;

static void hdy_action_row_buildable_init (GtkBuildableIface *iface);

68
G_DEFINE_TYPE_WITH_CODE (HdyActionRow, hdy_action_row, HDY_TYPE_PREFERENCES_ROW,
Adrien Plazas's avatar
Adrien Plazas committed
69
70
71
72
73
74
75
76
77
                         G_ADD_PRIVATE (HdyActionRow)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                         hdy_action_row_buildable_init))

static GtkBuildableIface *parent_buildable_iface;

enum {
  PROP_0,
  PROP_ICON_NAME,
78
  PROP_ACTIVATABLE_WIDGET,
Adrien Plazas's avatar
Adrien Plazas committed
79
80
  PROP_SUBTITLE,
  PROP_USE_UNDERLINE,
81
82
  PROP_TITLE_LINES,
  PROP_SUBTITLE_LINES,
Adrien Plazas's avatar
Adrien Plazas committed
83
84
85
86
87
  LAST_PROP,
};

static GParamSpec *props[LAST_PROP];

88
89
90
91
92
93
94
enum {
  SIGNAL_ACTIVATED,
  SIGNAL_LAST_SIGNAL,
};

static guint signals[SIGNAL_LAST_SIGNAL];

Adrien Plazas's avatar
Adrien Plazas committed
95
static void
96
row_activated_cb (HdyActionRow  *self,
Adrien Plazas's avatar
Adrien Plazas committed
97
98
                  GtkListBoxRow *row)
{
99
100
  /* No need to use GTK_LIST_BOX_ROW() for a pointer comparison. */
  if ((GtkListBoxRow *) self == row)
Adrien Plazas's avatar
Adrien Plazas committed
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    hdy_action_row_activate (self);
}

static void
parent_cb (HdyActionRow *self)
{
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);
  GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self));

  if (priv->previous_parent != NULL) {
    g_signal_handlers_disconnect_by_func (priv->previous_parent, G_CALLBACK (row_activated_cb), self);
    priv->previous_parent = NULL;
  }

  if (parent == NULL || !GTK_IS_LIST_BOX (parent))
    return;

  priv->previous_parent = parent;
  g_signal_connect_swapped (parent, "row-activated", G_CALLBACK (row_activated_cb), self);
}

static void
update_subtitle_visibility (HdyActionRow *self)
{
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

  gtk_widget_set_visible (GTK_WIDGET (priv->subtitle),
                          gtk_label_get_text (priv->subtitle) != NULL &&
                          g_strcmp0 (gtk_label_get_text (priv->subtitle), "") != 0);
}

static void
hdy_action_row_get_property (GObject    *object,
                             guint       prop_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  HdyActionRow *self = HDY_ACTION_ROW (object);

  switch (prop_id) {
  case PROP_ICON_NAME:
    g_value_set_string (value, hdy_action_row_get_icon_name (self));
    break;
144
145
146
  case PROP_ACTIVATABLE_WIDGET:
    g_value_set_object (value, (GObject *) hdy_action_row_get_activatable_widget (self));
    break;
Adrien Plazas's avatar
Adrien Plazas committed
147
148
149
  case PROP_SUBTITLE:
    g_value_set_string (value, hdy_action_row_get_subtitle (self));
    break;
150
151
152
153
154
155
  case PROP_SUBTITLE_LINES:
    g_value_set_int (value, hdy_action_row_get_subtitle_lines (self));
    break;
  case PROP_TITLE_LINES:
    g_value_set_int (value, hdy_action_row_get_title_lines (self));
    break;
Adrien Plazas's avatar
Adrien Plazas committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  case PROP_USE_UNDERLINE:
    g_value_set_boolean (value, hdy_action_row_get_use_underline (self));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
hdy_action_row_set_property (GObject      *object,
                             guint         prop_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  HdyActionRow *self = HDY_ACTION_ROW (object);

  switch (prop_id) {
  case PROP_ICON_NAME:
    hdy_action_row_set_icon_name (self, g_value_get_string (value));
    break;
176
177
178
  case PROP_ACTIVATABLE_WIDGET:
    hdy_action_row_set_activatable_widget (self, (GtkWidget*) g_value_get_object (value));
    break;
Adrien Plazas's avatar
Adrien Plazas committed
179
180
181
  case PROP_SUBTITLE:
    hdy_action_row_set_subtitle (self, g_value_get_string (value));
    break;
182
183
184
185
186
187
  case PROP_SUBTITLE_LINES:
    hdy_action_row_set_subtitle_lines (self, g_value_get_int (value));
    break;
  case PROP_TITLE_LINES:
    hdy_action_row_set_title_lines (self, g_value_get_int (value));
    break;
Adrien Plazas's avatar
Adrien Plazas committed
188
189
190
191
192
193
194
195
196
197
198
199
200
201
  case PROP_USE_UNDERLINE:
    hdy_action_row_set_use_underline (self, g_value_get_boolean (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
hdy_action_row_dispose (GObject *object)
{
  HdyActionRow *self = HDY_ACTION_ROW (object);
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

202
203
204
205
  if (priv->previous_parent != NULL) {
    g_signal_handlers_disconnect_by_func (priv->previous_parent, G_CALLBACK (row_activated_cb), self);
    priv->previous_parent = NULL;
  }
Adrien Plazas's avatar
Adrien Plazas committed
206

207
  G_OBJECT_CLASS (hdy_action_row_parent_class)->dispose (object);
Adrien Plazas's avatar
Adrien Plazas committed
208
209
}

Adrien Plazas's avatar
Adrien Plazas committed
210
211
212
213
214
215
216
217
218
219
220
221
222
static void
hdy_action_row_show_all (GtkWidget *widget)
{
  HdyActionRow *self = HDY_ACTION_ROW (widget);
  HdyActionRowPrivate *priv;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));

  priv = hdy_action_row_get_instance_private (self);

  gtk_container_foreach (GTK_CONTAINER (priv->prefixes),
                         (GtkCallback) gtk_widget_show_all,
                         NULL);
223
224
225
226
227

  gtk_container_foreach (GTK_CONTAINER (priv->suffixes),
                         (GtkCallback) gtk_widget_show_all,
                         NULL);

Adrien Plazas's avatar
Adrien Plazas committed
228
229
230
  GTK_WIDGET_CLASS (hdy_action_row_parent_class)->show_all (widget);
}

231
232
233
234
static void
hdy_action_row_destroy (GtkWidget *widget)
{
  HdyActionRow *self = HDY_ACTION_ROW (widget);
235
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);
236

237
238
239
  if (priv->header) {
    gtk_widget_destroy (GTK_WIDGET (priv->header));
    priv->header = NULL;
240
241
  }

242
243
  hdy_action_row_set_activatable_widget (self, NULL);

244
  priv->prefixes = NULL;
245
  priv->suffixes = NULL;
246

247
248
249
  GTK_WIDGET_CLASS (hdy_action_row_parent_class)->destroy (widget);
}

Adrien Plazas's avatar
Adrien Plazas committed
250
251
252
253
254
255
256
257
258
259
static void
hdy_action_row_add (GtkContainer *container,
                    GtkWidget    *child)
{
  HdyActionRow *self = HDY_ACTION_ROW (container);
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

  /* When constructing the widget, we want the box to be added as the child of
   * the GtkListBoxRow, as an implementation detail.
   */
260
  if (priv->header == NULL)
Adrien Plazas's avatar
Adrien Plazas committed
261
    GTK_CONTAINER_CLASS (hdy_action_row_parent_class)->add (container, child);
262
  else {
263
    gtk_container_add (GTK_CONTAINER (priv->suffixes), child);
264
265
    gtk_widget_show (GTK_WIDGET (priv->suffixes));
  }
Adrien Plazas's avatar
Adrien Plazas committed
266
267
}

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
static void
hdy_action_row_remove (GtkContainer *container,
                       GtkWidget    *child)
{
  HdyActionRow *self = HDY_ACTION_ROW (container);
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

  if (child == GTK_WIDGET (priv->header))
    GTK_CONTAINER_CLASS (hdy_action_row_parent_class)->remove (container, child);
  else if (gtk_widget_get_parent (child) == GTK_WIDGET (priv->prefixes))
    gtk_container_remove (GTK_CONTAINER (priv->prefixes), child);
  else
    gtk_container_remove (GTK_CONTAINER (priv->suffixes), child);
}

Adrien Plazas's avatar
Adrien Plazas committed
283
284
285
286
287
288
289
290
291
292
293
294
295
typedef struct {
  HdyActionRow *row;
  GtkCallback callback;
  gpointer callback_data;
} ForallData;

static void
for_non_internal_child (GtkWidget *widget,
                        gpointer   callback_data)
{
  ForallData *data = callback_data;
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (data->row);

296
  if (widget != (GtkWidget *) priv->image &&
Adrien Plazas's avatar
Adrien Plazas committed
297
      widget != (GtkWidget *) priv->prefixes &&
298
      widget != (GtkWidget *) priv->suffixes &&
Adrien Plazas's avatar
Adrien Plazas committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
      widget != (GtkWidget *) priv->title_box)
    data->callback (widget, data->callback_data);
}

static void
hdy_action_row_forall (GtkContainer *container,
                       gboolean      include_internals,
                       GtkCallback   callback,
                       gpointer      callback_data)
{
  HdyActionRow *self = HDY_ACTION_ROW (container);
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);
  ForallData data;

  if (include_internals) {
    GTK_CONTAINER_CLASS (hdy_action_row_parent_class)->forall (GTK_CONTAINER (self), include_internals, callback, callback_data);

    return;
  }

  data.row = self;
  data.callback = callback;
  data.callback_data = callback_data;

323
324
  if (priv->prefixes)
    GTK_CONTAINER_GET_CLASS (priv->prefixes)->forall (GTK_CONTAINER (priv->prefixes), include_internals, for_non_internal_child, &data);
325
326
  if (priv->suffixes)
    GTK_CONTAINER_GET_CLASS (priv->suffixes)->forall (GTK_CONTAINER (priv->suffixes), include_internals, for_non_internal_child, &data);
327
328
  if (priv->header)
    GTK_CONTAINER_GET_CLASS (priv->header)->forall (GTK_CONTAINER (priv->header), include_internals, for_non_internal_child, &data);
Adrien Plazas's avatar
Adrien Plazas committed
329
330
331
332
333
}

static void
hdy_action_row_activate_real (HdyActionRow *self)
{
334
335
336
337
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

  if (priv->activatable_widget)
    gtk_widget_mnemonic_activate (priv->activatable_widget, FALSE);
338
339

  g_signal_emit (self, signals[SIGNAL_ACTIVATED], 0);
Adrien Plazas's avatar
Adrien Plazas committed
340
341
342
343
344
345
346
347
348
349
350
351
352
}

static void
hdy_action_row_class_init (HdyActionRowClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);

  object_class->get_property = hdy_action_row_get_property;
  object_class->set_property = hdy_action_row_set_property;
  object_class->dispose = hdy_action_row_dispose;

353
  widget_class->destroy = hdy_action_row_destroy;
Adrien Plazas's avatar
Adrien Plazas committed
354
355
  widget_class->show_all = hdy_action_row_show_all;

Adrien Plazas's avatar
Adrien Plazas committed
356
  container_class->add = hdy_action_row_add;
357
  container_class->remove = hdy_action_row_remove;
Adrien Plazas's avatar
Adrien Plazas committed
358
359
360
361
362
  container_class->forall = hdy_action_row_forall;

  klass->activate = hdy_action_row_activate_real;

  /**
363
   * HdyActionRow:icon-name: (attributes org.gtk.Property.get=hdy_action_row_get_icon_name org.gtk.Property.set=hdy_action_row_set_icon_name)
Adrien Plazas's avatar
Adrien Plazas committed
364
365
   *
   * The icon name for this row.
366
   *
367
   * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
368
369
370
371
372
373
374
375
   */
  props[PROP_ICON_NAME] =
    g_param_spec_string ("icon-name",
                         _("Icon name"),
                         _("Icon name"),
                         "",
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

376
  /**
377
   * HdyActionRow:activatable-widget: (attributes org.gtk.Property.get=hdy_action_row_get_activatable_widget org.gtk.Property.set=hdy_action_row_set_activatable_widget)
378
379
380
   *
   * The activatable widget for this row.
   *
381
382
383
384
385
386
387
388
   * The widget is activated, either by clicking on it, by calling
   * [method@ActionRow.activate], or via mnemonics in the title or the subtitle.
   * See the [property@ActionRow:use-underline] property to enable mnemonics.
   *
   * The target widget will be activated by emitting the
   * [signal@Gtk.Widget::mnemonic-activate] signal on it.
   *
   * Since: 1.0
389
390
391
392
393
394
395
396
   */
  props[PROP_ACTIVATABLE_WIDGET] =
      g_param_spec_object ("activatable-widget",
                           _("Activatable widget"),
                           _("The widget to be activated when the row is activated"),
                           GTK_TYPE_WIDGET,
                           G_PARAM_READWRITE);

Adrien Plazas's avatar
Adrien Plazas committed
397
  /**
398
   * HdyActionRow:subtitle: (attributes org.gtk.Property.get=hdy_action_row_get_subtitle org.gtk.Property.set=hdy_action_row_set_subtitle)
Adrien Plazas's avatar
Adrien Plazas committed
399
400
   *
   * The subtitle for this row.
401
   *
402
   * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
403
404
405
406
407
408
409
410
   */
  props[PROP_SUBTITLE] =
    g_param_spec_string ("subtitle",
                         _("Subtitle"),
                         _("Subtitle"),
                         "",
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

411
  /**
412
413
414
   * HdyActionRow:use-underline: (attributes org.gtk.Property.get=hdy_action_row_get_use_underline org.gtk.Property.set=hdy_action_row_set_use_underline)
   *
   * Whether embedded underlines in the title or subtitle indicates a mnemonic.
415
   *
416
417
   * If true, an underline in the text of the title or subtitle labels indicates
   * the next character should be used for the mnemonic accelerator key.
418
   *
419
   * Since: 1.0
420
   */
Adrien Plazas's avatar
Adrien Plazas committed
421
422
423
424
425
426
427
  props[PROP_USE_UNDERLINE] =
    g_param_spec_boolean ("use-underline",
                          _("Use underline"),
                          _("If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key"),
                          FALSE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

428
  /**
429
   * HdyActionRow:title-lines: (attributes org.gtk.Property.get=hdy_action_row_get_title_lines org.gtk.Property.set=hdy_action_row_set_title_lines)
430
431
   *
   * The number of lines at the end of which the title label will be ellipsized.
432
433
   *
   * If the value is 0, the number of lines won't be limited.
434
   *
Adrien Plazas's avatar
Adrien Plazas committed
435
   * Since: 1.2
436
437
438
439
440
441
442
443
444
445
   */
  props[PROP_TITLE_LINES] =
    g_param_spec_int ("title-lines",
                      _("Number of title lines"),
                      _("The desired number of title lines"),
                      0, G_MAXINT,
                      1,
                      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  /**
446
   * HdyActionRow:subtitle-lines: (attributes org.gtk.Property.get=hdy_action_row_get_subtitle_lines org.gtk.Property.set=hdy_action_row_set_subtitle_lines)
447
448
449
   *
   * The number of lines at the end of which the subtitle label will be
   * ellipsized.
450
451
   *
   * If the value is 0, the number of lines won't be limited.
452
   *
Adrien Plazas's avatar
Adrien Plazas committed
453
   * Since: 1.2
454
455
456
457
458
459
460
461
462
   */
  props[PROP_SUBTITLE_LINES] =
    g_param_spec_int ("subtitle-lines",
                      _("Number of subtitle lines"),
                      _("The desired number of subtitle lines"),
                      0, G_MAXINT,
                      1,
                      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

Adrien Plazas's avatar
Adrien Plazas committed
463
464
  g_object_class_install_properties (object_class, LAST_PROP, props);

465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
  /**
   * HdyActionRow::activated:
   *
   * This signal is emitted after the row has been activated.
   *
   * Since: 1.0
   */
  signals[SIGNAL_ACTIVATED] =
    g_signal_new ("activated",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE,
                  0);

Adrien Plazas's avatar
Adrien Plazas committed
481
  gtk_widget_class_set_template_from_resource (widget_class,
482
                                               "/sm/puri/handy/ui/hdy-action-row.ui");
Adrien Plazas's avatar
Adrien Plazas committed
483
484
485
486
  gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, header);
  gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, image);
  gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, prefixes);
  gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, subtitle);
487
  gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, suffixes);
Adrien Plazas's avatar
Adrien Plazas committed
488
489
490
491
  gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, title);
  gtk_widget_class_bind_template_child_private (widget_class, HdyActionRow, title_box);
}

492
493
494
495
496
497
498
499
500
501
502
503
504
static gboolean
string_is_not_empty (GBinding     *binding,
                     const GValue *from_value,
                     GValue       *to_value,
                     gpointer      user_data)
{
  const gchar *string = g_value_get_string (from_value);

  g_value_set_boolean (to_value, string != NULL && g_strcmp0 (string, "") != 0);

  return TRUE;
}

Adrien Plazas's avatar
Adrien Plazas committed
505
506
507
static void
hdy_action_row_init (HdyActionRow *self)
{
508
509
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

510
511
512
  priv->title_lines = 1;
  priv->subtitle_lines = 1;

Adrien Plazas's avatar
Adrien Plazas committed
513
514
  gtk_widget_init_template (GTK_WIDGET (self));

515
516
517
  g_object_bind_property_full (self, "title", priv->title, "visible", G_BINDING_SYNC_CREATE,
                               string_is_not_empty, NULL, NULL, NULL);

Adrien Plazas's avatar
Adrien Plazas committed
518
519
520
521
522
523
524
525
526
527
528
529
  update_subtitle_visibility (self);

  g_signal_connect (self, "notify::parent", G_CALLBACK (parent_cb), NULL);

}

static void
hdy_action_row_buildable_add_child (GtkBuildable *buildable,
                                    GtkBuilder   *builder,
                                    GObject      *child,
                                    const gchar  *type)
{
530
531
532
  HdyActionRow *self = HDY_ACTION_ROW (buildable);
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

533
  if (priv->header == NULL || !type)
534
    gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (child));
Adrien Plazas's avatar
Adrien Plazas committed
535
  else if (type && strcmp (type, "prefix") == 0)
536
    hdy_action_row_add_prefix (self, GTK_WIDGET (child));
Adrien Plazas's avatar
Adrien Plazas committed
537
  else
538
    GTK_BUILDER_WARN_INVALID_CHILD_TYPE (self, type);
Adrien Plazas's avatar
Adrien Plazas committed
539
540
541
542
543
544
545
546
547
548
549
550
}

static void
hdy_action_row_buildable_init (GtkBuildableIface *iface)
{
  parent_buildable_iface = g_type_interface_peek_parent (iface);
  iface->add_child = hdy_action_row_buildable_add_child;
}

/**
 * hdy_action_row_new:
 *
551
 * Creates a new `HdyActionRow`.
Adrien Plazas's avatar
Adrien Plazas committed
552
 *
553
 * Returns: the newly created `HdyActionRow`
Guido Günther's avatar
Guido Günther committed
554
 *
555
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
556
 */
Ujjwal Kumar's avatar
Ujjwal Kumar committed
557
GtkWidget *
Adrien Plazas's avatar
Adrien Plazas committed
558
559
560
561
562
563
hdy_action_row_new (void)
{
  return g_object_new (HDY_TYPE_ACTION_ROW, NULL);
}

/**
564
565
 * hdy_action_row_get_subtitle: (attributes org.gtk.Method.get_property=subtitle)
 * @self: an action row
Adrien Plazas's avatar
Adrien Plazas committed
566
567
568
 *
 * Gets the subtitle for @self.
 *
569
 * Returns: (transfer none) (nullable): the subtitle for @self
Guido Günther's avatar
Guido Günther committed
570
 *
571
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
572
573
574
575
576
577
578
579
580
581
582
583
584
585
 */
const gchar *
hdy_action_row_get_subtitle (HdyActionRow *self)
{
  HdyActionRowPrivate *priv;

  g_return_val_if_fail (HDY_IS_ACTION_ROW (self), NULL);

  priv = hdy_action_row_get_instance_private (self);

  return gtk_label_get_text (priv->subtitle);
}

/**
586
587
 * hdy_action_row_set_subtitle: (attributes org.gtk.Method.set_property=subtitle)
 * @self: an action row
588
 * @subtitle: (nullable): the subtitle
Adrien Plazas's avatar
Adrien Plazas committed
589
590
 *
 * Sets the subtitle for @self.
Guido Günther's avatar
Guido Günther committed
591
 *
592
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
 */
void
hdy_action_row_set_subtitle (HdyActionRow *self,
                             const gchar  *subtitle)
{
  HdyActionRowPrivate *priv;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));

  priv = hdy_action_row_get_instance_private (self);

  if (g_strcmp0 (gtk_label_get_text (priv->subtitle), subtitle) == 0)
    return;

  gtk_label_set_text (priv->subtitle, subtitle);
  gtk_widget_set_visible (GTK_WIDGET (priv->subtitle),
                          subtitle != NULL && g_strcmp0 (subtitle, "") != 0);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]);
}

/**
615
616
 * hdy_action_row_get_icon_name: (attributes org.gtk.Method.get_property=icon-name)
 * @self: an action row
Adrien Plazas's avatar
Adrien Plazas committed
617
618
619
 *
 * Gets the icon name for @self.
 *
620
 * Returns: (transfer none): the icon name for @self
Guido Günther's avatar
Guido Günther committed
621
 *
622
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
 */
const gchar *
hdy_action_row_get_icon_name (HdyActionRow *self)
{
  HdyActionRowPrivate *priv;
  const gchar *icon_name;

  g_return_val_if_fail (HDY_IS_ACTION_ROW (self), NULL);

  priv = hdy_action_row_get_instance_private (self);

  gtk_image_get_icon_name (priv->image, &icon_name, NULL);

  return icon_name;
}

/**
640
641
 * hdy_action_row_set_icon_name: (attributes org.gtk.Method.set_property=icon-name)
 * @self: an action row
Adrien Plazas's avatar
Adrien Plazas committed
642
643
644
 * @icon_name: the icon name
 *
 * Sets the icon name for @self.
Guido Günther's avatar
Guido Günther committed
645
 *
646
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
 */
void
hdy_action_row_set_icon_name (HdyActionRow *self,
                              const gchar  *icon_name)
{
  HdyActionRowPrivate *priv;
  const gchar *old_icon_name;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));

  priv = hdy_action_row_get_instance_private (self);

  gtk_image_get_icon_name (priv->image, &old_icon_name, NULL);
  if (g_strcmp0 (old_icon_name, icon_name) == 0)
    return;

  gtk_image_set_from_icon_name (priv->image, icon_name, GTK_ICON_SIZE_INVALID);
  gtk_widget_set_visible (GTK_WIDGET (priv->image),
                          icon_name != NULL && g_strcmp0 (icon_name, "") != 0);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
}

670
/**
671
672
 * hdy_action_row_get_activatable_widget: (attributes org.gtk.Method.get_property=activatable-widget)
 * @self: an action row
673
674
675
 *
 * Gets the widget activated when @self is activated.
 *
676
 * Returns: (nullable) (transfer none): the activatable widget for @self
677
 *
678
 * Since: 1.0
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
 */
GtkWidget *
hdy_action_row_get_activatable_widget (HdyActionRow *self)
{
  HdyActionRowPrivate *priv;

  g_return_val_if_fail (HDY_IS_ACTION_ROW (self), NULL);

  priv = hdy_action_row_get_instance_private (self);

  return priv->activatable_widget;
}

static void
activatable_widget_weak_notify (gpointer  data,
                                GObject  *where_the_object_was)
{
  HdyActionRow *self = HDY_ACTION_ROW (data);
  HdyActionRowPrivate *priv = hdy_action_row_get_instance_private (self);

  priv->activatable_widget = NULL;

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVATABLE_WIDGET]);
}

/**
705
706
707
 * hdy_action_row_set_activatable_widget: (attributes org.gtk.Method.set_property=activatable-widget)
 * @self: an action row
 * @widget: (nullable): the target widget
708
 *
709
 * Sets the widget to activate when @self is activated.
710
 *
711
 * Since: 1.0
712
713
714
715
716
717
718
719
 */
void
hdy_action_row_set_activatable_widget (HdyActionRow *self,
                                       GtkWidget    *widget)
{
  HdyActionRowPrivate *priv;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));
720
  g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
721
722
723
724
725
726
727
728
729
730
731
732
733

  priv = hdy_action_row_get_instance_private (self);

  if (priv->activatable_widget == widget)
    return;

  if (priv->activatable_widget)
    g_object_weak_unref (G_OBJECT (priv->activatable_widget),
                         activatable_widget_weak_notify,
                         self);

  priv->activatable_widget = widget;

734
  if (priv->activatable_widget != NULL) {
735
736
737
    g_object_weak_ref (G_OBJECT (priv->activatable_widget),
                       activatable_widget_weak_notify,
                       self);
738
739
    gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), TRUE);
  }
740
741
742
743

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVATABLE_WIDGET]);
}

Adrien Plazas's avatar
Adrien Plazas committed
744
/**
745
746
 * hdy_action_row_get_use_underline: (attributes org.gtk.Method.get_property=use-underline)
 * @self: an action row
Adrien Plazas's avatar
Adrien Plazas committed
747
 *
748
749
 * Gets whether an embedded underline in the title or subtitle indicates a
 * mnemonic.
Adrien Plazas's avatar
Adrien Plazas committed
750
 *
751
752
 * Returns: whether an embedded underline in the title or subtitle indicates a
 *   mnemonic
Guido Günther's avatar
Guido Günther committed
753
 *
754
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
755
756
757
758
759
760
761
762
763
764
765
766
767
768
 */
gboolean
hdy_action_row_get_use_underline (HdyActionRow *self)
{
  HdyActionRowPrivate *priv;

  g_return_val_if_fail (HDY_IS_ACTION_ROW (self), FALSE);

  priv = hdy_action_row_get_instance_private (self);

  return priv->use_underline;
}

/**
769
770
771
 * hdy_action_row_set_use_underline: (attributes org.gtk.Method.set_property=use-underline)
 * @self: an action row
 * @use_underline: `TRUE` if underlines in the text indicate mnemonics
Adrien Plazas's avatar
Adrien Plazas committed
772
 *
773
774
 * Sets whether an embedded underline in the title or subtitle indicates a
 * mnemonic.
Guido Günther's avatar
Guido Günther committed
775
 *
776
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
777
778
779
780
781
782
783
784
785
786
787
 */
void
hdy_action_row_set_use_underline (HdyActionRow *self,
                                  gboolean      use_underline)
{
  HdyActionRowPrivate *priv;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));

  priv = hdy_action_row_get_instance_private (self);

788
789
790
  use_underline = !!use_underline;

  if (priv->use_underline == use_underline)
Adrien Plazas's avatar
Adrien Plazas committed
791
792
    return;

793
  priv->use_underline = use_underline;
794
  hdy_preferences_row_set_use_underline (HDY_PREFERENCES_ROW (self), priv->use_underline);
Adrien Plazas's avatar
Adrien Plazas committed
795
796
797
798
799
800
801
802
  gtk_label_set_use_underline (priv->title, priv->use_underline);
  gtk_label_set_use_underline (priv->subtitle, priv->use_underline);
  gtk_label_set_mnemonic_widget (priv->title, GTK_WIDGET (self));
  gtk_label_set_mnemonic_widget (priv->subtitle, GTK_WIDGET (self));

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_UNDERLINE]);
}

803
/**
804
805
 * hdy_action_row_get_title_lines: (attributes org.gtk.Method.get_property=title-lines)
 * @self: an action row
806
807
808
 *
 * Gets the number of lines at the end of which the title label will be
 * ellipsized.
809
 *
810
811
812
 * If the value is 0, the number of lines won't be limited.
 *
 * Returns: the number of lines at the end of which the title label will be
813
 *   ellipsized
814
 *
Adrien Plazas's avatar
Adrien Plazas committed
815
 * Since: 1.2
816
817
818
819
820
821
822
823
824
825
826
827
828
829
 */
gint
hdy_action_row_get_title_lines (HdyActionRow *self)
{
  HdyActionRowPrivate *priv;

  g_return_val_if_fail (HDY_IS_ACTION_ROW (self), 0);

  priv = hdy_action_row_get_instance_private (self);

  return priv->title_lines;
}

/**
830
831
 * hdy_action_row_set_title_lines: (attributes org.gtk.Method.set_property=title-lines)
 * @self: an action row
832
833
834
835
 * @title_lines: the number of lines at the end of which the title label will be ellipsized
 *
 * Sets the number of lines at the end of which the title label will be
 * ellipsized.
836
 *
837
838
 * If the value is 0, the number of lines won't be limited.
 *
Adrien Plazas's avatar
Adrien Plazas committed
839
 * Since: 1.2
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
 */
void
hdy_action_row_set_title_lines (HdyActionRow *self,
                                gint          title_lines)
{
  HdyActionRowPrivate *priv;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));
  g_return_if_fail (title_lines >= 0);

  priv = hdy_action_row_get_instance_private (self);

  if (priv->title_lines == title_lines)
    return;

  priv->title_lines = title_lines;

  gtk_label_set_lines (priv->title, title_lines);
  gtk_label_set_ellipsize (priv->title, title_lines == 0 ? PANGO_ELLIPSIZE_NONE : PANGO_ELLIPSIZE_END);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE_LINES]);
}

/**
864
865
 * hdy_action_row_get_subtitle_lines: (attributes org.gtk.Method.get_property=subtitle-lines)
 * @self: an action row
866
867
868
 *
 * Gets the number of lines at the end of which the subtitle label will be
 * ellipsized.
869
 *
870
871
872
 * If the value is 0, the number of lines won't be limited.
 *
 * Returns: the number of lines at the end of which the subtitle label will be
873
 *   ellipsized
874
 *
Adrien Plazas's avatar
Adrien Plazas committed
875
 * Since: 1.2
876
877
878
879
880
881
882
883
884
885
886
887
888
889
 */
gint
hdy_action_row_get_subtitle_lines (HdyActionRow *self)
{
  HdyActionRowPrivate *priv;

  g_return_val_if_fail (HDY_IS_ACTION_ROW (self), 0);

  priv = hdy_action_row_get_instance_private (self);

  return priv->subtitle_lines;
}

/**
890
891
 * hdy_action_row_set_subtitle_lines: (attributes org.gtk.Method.set_property=subtitle-lines)
 * @self: an action row
892
893
894
895
 * @subtitle_lines: the number of lines at the end of which the subtitle label will be ellipsized
 *
 * Sets the number of lines at the end of which the subtitle label will be
 * ellipsized.
896
 *
897
898
 * If the value is 0, the number of lines won't be limited.
 *
Adrien Plazas's avatar
Adrien Plazas committed
899
 * Since: 1.2
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
 */
void
hdy_action_row_set_subtitle_lines (HdyActionRow *self,
                                   gint          subtitle_lines)
{
  HdyActionRowPrivate *priv;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));
  g_return_if_fail (subtitle_lines >= 0);

  priv = hdy_action_row_get_instance_private (self);

  if (priv->subtitle_lines == subtitle_lines)
    return;

  priv->subtitle_lines = subtitle_lines;

  gtk_label_set_lines (priv->subtitle, subtitle_lines);
  gtk_label_set_ellipsize (priv->subtitle, subtitle_lines == 0 ? PANGO_ELLIPSIZE_NONE : PANGO_ELLIPSIZE_END);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE_LINES]);
}

Adrien Plazas's avatar
Adrien Plazas committed
923
924
/**
 * hdy_action_row_add_prefix:
925
 * @self: an action row
926
 * @widget: the prefix widget
Adrien Plazas's avatar
Adrien Plazas committed
927
928
 *
 * Adds a prefix widget to @self.
Guido Günther's avatar
Guido Günther committed
929
 *
930
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
931
932
933
934
935
936
937
938
 */
void
hdy_action_row_add_prefix (HdyActionRow *self,
                           GtkWidget    *widget)
{
  HdyActionRowPrivate *priv;

  g_return_if_fail (HDY_IS_ACTION_ROW (self));
939
  g_return_if_fail (GTK_IS_WIDGET (self));
Adrien Plazas's avatar
Adrien Plazas committed
940
941
942
943
944
945
946

  priv = hdy_action_row_get_instance_private (self);

  gtk_box_pack_start (priv->prefixes, widget, FALSE, TRUE, 0);
  gtk_widget_show (GTK_WIDGET (priv->prefixes));
}

947
948
949
950
951
952
953
954
/**
 * hdy_action_row_activate:
 * @self: an action row
 *
 * Activates @self.
 *
 * Since: 1.0
 */
Adrien Plazas's avatar
Adrien Plazas committed
955
956
957
958
959
960
961
void
hdy_action_row_activate (HdyActionRow *self)
{
  g_return_if_fail (HDY_IS_ACTION_ROW (self));

  HDY_ACTION_ROW_GET_CLASS (self)->activate (self);
}