hdy-preferences-group.c 16.2 KB
Newer Older
Adrien Plazas's avatar
Adrien Plazas committed
1
2
3
/*
 * Copyright (C) 2019 Purism SPC
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
Adrien Plazas's avatar
Adrien Plazas committed
5
6
7
8
9
10
11
 */

#include "config.h"
#include <glib/gi18n-lib.h>

#include "hdy-preferences-group-private.h"

Adrien Plazas's avatar
Adrien Plazas committed
12
#include "hdy-css-private.h"
Adrien Plazas's avatar
Adrien Plazas committed
13
14
15
#include "hdy-preferences-row.h"

/**
16
 * HdyPreferencesGroup:
Adrien Plazas's avatar
Adrien Plazas committed
17
 *
18
19
20
21
 * A group of preference rows.
 *
 * A `HdyPreferencesGroup` represents a group or tightly related preferences,
 * which in turn are represented by [class@PreferencesRow].
Adrien Plazas's avatar
Adrien Plazas committed
22
23
 *
 * To summarize the role of the preferences it gathers, a group can have both a
24
25
 * title and a description. The title will be used by [class@PreferencesWindow]
 * to let the user look for a preference.
Adrien Plazas's avatar
Adrien Plazas committed
26
 *
27
 * ## CSS nodes
28
 *
29
 * `HdyPreferencesGroup` has a single CSS node with name `preferencesgroup`.
30
 *
31
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
32
33
34
35
 */

typedef struct
{
36
  GtkWidget *box;
Adrien Plazas's avatar
Adrien Plazas committed
37
38
39
40
41
42
  GtkLabel *description;
  GtkListBox *listbox;
  GtkBox *listbox_box;
  GtkLabel *title;
} HdyPreferencesGroupPrivate;

43
G_DEFINE_TYPE_WITH_PRIVATE (HdyPreferencesGroup, hdy_preferences_group, GTK_TYPE_BIN)
Adrien Plazas's avatar
Adrien Plazas committed
44
45
46
47
48

enum {
  PROP_0,
  PROP_DESCRIPTION,
  PROP_TITLE,
49
  PROP_USE_MARKUP,
Adrien Plazas's avatar
Adrien Plazas committed
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
  LAST_PROP,
};

static GParamSpec *props[LAST_PROP];

static void
update_title_visibility (HdyPreferencesGroup *self)
{
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);

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

static void
update_description_visibility (HdyPreferencesGroup *self)
{
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);

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

static void
update_listbox_visibility (HdyPreferencesGroup *self)
{
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);
  g_autoptr(GList) children = NULL;

81
  /* We must wait until the listbox has been built and added. */
Adrien Plazas's avatar
Adrien Plazas committed
82
83
84
85
86
87
88
89
  if (priv->listbox == NULL)
    return;

  children = gtk_container_get_children (GTK_CONTAINER (priv->listbox));

  gtk_widget_set_visible (GTK_WIDGET (priv->listbox), children != NULL);
}

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
static gboolean
listbox_keynav_failed_cb (HdyPreferencesGroup *self,
                          GtkDirectionType     direction)
{
  GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));

  if (!toplevel)
    return FALSE;

  if (direction != GTK_DIR_UP && direction != GTK_DIR_DOWN)
    return FALSE;

  return gtk_widget_child_focus (toplevel, direction == GTK_DIR_UP ?
                                 GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD);
}

Adrien Plazas's avatar
Adrien Plazas committed
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
typedef struct {
  HdyPreferencesGroup *group;
  GtkCallback callback;
  gpointer callback_data;
} ForallData;

static void
for_non_internal_child (GtkWidget *widget,
                        gpointer   callback_data)
{
  ForallData *data = callback_data;
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (data->group);

  if (widget != (GtkWidget *) priv->listbox)
    data->callback (widget, data->callback_data);
}

static void
hdy_preferences_group_forall (GtkContainer *container,
                              gboolean      include_internals,
                              GtkCallback   callback,
                              gpointer      callback_data)
{
  HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (container);
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);
  ForallData data;

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

    return;
  }

  data.group = self;
  data.callback = callback;
  data.callback_data = callback_data;

  if (priv->listbox)
    GTK_CONTAINER_GET_CLASS (priv->listbox)->forall (GTK_CONTAINER (priv->listbox), include_internals, callback, callback_data);
  if (priv->listbox_box)
    GTK_CONTAINER_GET_CLASS (priv->listbox_box)->forall (GTK_CONTAINER (priv->listbox_box), include_internals, for_non_internal_child, &data);
}

static void
hdy_preferences_group_get_property (GObject    *object,
                                    guint       prop_id,
                                    GValue     *value,
                                    GParamSpec *pspec)
{
  HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (object);

  switch (prop_id) {
  case PROP_DESCRIPTION:
    g_value_set_string (value, hdy_preferences_group_get_description (self));
    break;
  case PROP_TITLE:
    g_value_set_string (value, hdy_preferences_group_get_title (self));
    break;
164
165
166
  case PROP_USE_MARKUP:
    g_value_set_boolean (value, hdy_preferences_group_get_use_markup (self));
    break;
Adrien Plazas's avatar
Adrien Plazas committed
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
hdy_preferences_group_set_property (GObject      *object,
                                    guint         prop_id,
                                    const GValue *value,
                                    GParamSpec   *pspec)
{
  HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (object);

  switch (prop_id) {
  case PROP_DESCRIPTION:
    hdy_preferences_group_set_description (self, g_value_get_string (value));
    break;
  case PROP_TITLE:
    hdy_preferences_group_set_title (self, g_value_get_string (value));
    break;
187
188
189
  case PROP_USE_MARKUP:
    hdy_preferences_group_set_use_markup (self, g_value_get_boolean (value));
    break;
Adrien Plazas's avatar
Adrien Plazas committed
190
191
192
193
194
195
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
196
hdy_preferences_group_destroy (GtkWidget *widget)
Adrien Plazas's avatar
Adrien Plazas committed
197
{
198
  HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (widget);
Adrien Plazas's avatar
Adrien Plazas committed
199
200
201
202
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);

  /*
   * Since we overload forall(), the inherited destroy() won't work as normal.
203
   * Remove internal widgets ourselves.
Adrien Plazas's avatar
Adrien Plazas committed
204
   */
205
  g_clear_pointer (&priv->box, gtk_widget_destroy);
206
207
208
209
  priv->description = NULL;
  priv->listbox = NULL;
  priv->listbox_box = NULL;
  priv->title = NULL;
Adrien Plazas's avatar
Adrien Plazas committed
210

211
  GTK_WIDGET_CLASS (hdy_preferences_group_parent_class)->destroy (widget);
Adrien Plazas's avatar
Adrien Plazas committed
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
}

static void
hdy_preferences_group_add (GtkContainer *container,
                           GtkWidget    *child)
{
  HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (container);
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);

  if (priv->title == NULL || priv->description == NULL || priv->listbox_box == NULL) {
    GTK_CONTAINER_CLASS (hdy_preferences_group_parent_class)->add (container, child);

    return;
  }

  if (HDY_IS_PREFERENCES_ROW (child))
    gtk_container_add (GTK_CONTAINER (priv->listbox), child);
  else
    gtk_container_add (GTK_CONTAINER (priv->listbox_box), child);
}

233
234
235
236
237
238
239
static void
hdy_preferences_group_remove (GtkContainer *container,
                              GtkWidget    *child)
{
  HdyPreferencesGroup *self = HDY_PREFERENCES_GROUP (container);
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);

240
  if (child == priv->box)
241
242
243
244
245
246
247
    GTK_CONTAINER_CLASS (hdy_preferences_group_parent_class)->remove (container, child);
  else if (HDY_IS_PREFERENCES_ROW (child))
    gtk_container_remove (GTK_CONTAINER (priv->listbox), child);
  else if (child != GTK_WIDGET (priv->listbox))
    gtk_container_remove (GTK_CONTAINER (priv->listbox_box), child);
}

Adrien Plazas's avatar
Adrien Plazas committed
248
249
250
251
252
253
254
255
256
static void
hdy_preferences_group_class_init (HdyPreferencesGroupClass *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_preferences_group_get_property;
  object_class->set_property = hdy_preferences_group_set_property;
257
258

  widget_class->destroy = hdy_preferences_group_destroy;
Adrien Plazas's avatar
Adrien Plazas committed
259

Adrien Plazas's avatar
Adrien Plazas committed
260
261
262
263
264
265
266
  widget_class->size_allocate = hdy_css_size_allocate_bin;
  widget_class->get_preferred_height = hdy_css_get_preferred_height;
  widget_class->get_preferred_height_for_width = hdy_css_get_preferred_height_for_width;
  widget_class->get_preferred_width = hdy_css_get_preferred_width;
  widget_class->get_preferred_width_for_height = hdy_css_get_preferred_width_for_height;
  widget_class->draw = hdy_css_draw_bin;

Adrien Plazas's avatar
Adrien Plazas committed
267
  container_class->add = hdy_preferences_group_add;
268
  container_class->remove = hdy_preferences_group_remove;
Adrien Plazas's avatar
Adrien Plazas committed
269
270
271
  container_class->forall = hdy_preferences_group_forall;

  /**
272
   * HdyPreferencesGroup:description: (attributes org.gtk.Property.get=hdy_preferences_group_get_description org.gtk.Property.set=hdy_preferences_group_set_description)
Adrien Plazas's avatar
Adrien Plazas committed
273
274
275
   *
   * The description for this group of preferences.
   *
276
   * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
277
278
279
280
281
282
283
284
285
   */
  props[PROP_DESCRIPTION] =
    g_param_spec_string ("description",
                         _("Description"),
                         _("Description"),
                         "",
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
286
   * HdyPreferencesGroup:title: (attributes org.gtk.Property.get=hdy_preferences_group_get_title org.gtk.Property.set=hdy_preferences_group_set_title)
Adrien Plazas's avatar
Adrien Plazas committed
287
288
289
   *
   * The title for this group of preferences.
   *
290
   * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
291
292
293
294
295
296
297
298
   */
  props[PROP_TITLE] =
    g_param_spec_string ("title",
                         _("Title"),
                         _("Title"),
                         "",
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

299
  /**
300
   * HdyPreferencesGroup:use-markup: (attributes org.gtk.Property.get=hdy_preferences_group_get_use_markup org.gtk.Property.set=hdy_preferences_group_set_use_markup)
301
302
303
304
305
306
307
308
   *
   * Whether to use markup for the title and description.
   *
   * Since: 1.4
   */
  props[PROP_USE_MARKUP] =
    g_param_spec_boolean ("use-markup",
                          _("Use markup"),
309
                          _("Whether to use markup for the title and description"),
310
311
312
                          FALSE,
                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

Adrien Plazas's avatar
Adrien Plazas committed
313
314
  g_object_class_install_properties (object_class, LAST_PROP, props);

315
  gtk_widget_class_set_css_name (widget_class, "preferencesgroup");
Adrien Plazas's avatar
Adrien Plazas committed
316
317
  gtk_widget_class_set_template_from_resource (widget_class,
                                               "/sm/puri/handy/ui/hdy-preferences-group.ui");
318
  gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, box);
Adrien Plazas's avatar
Adrien Plazas committed
319
320
321
322
323
  gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, description);
  gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, listbox);
  gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, listbox_box);
  gtk_widget_class_bind_template_child_private (widget_class, HdyPreferencesGroup, title);
  gtk_widget_class_bind_template_callback (widget_class, update_listbox_visibility);
324
  gtk_widget_class_bind_template_callback (widget_class, listbox_keynav_failed_cb);
Adrien Plazas's avatar
Adrien Plazas committed
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
}

static void
hdy_preferences_group_init (HdyPreferencesGroup *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));

  update_description_visibility (self);
  update_title_visibility (self);
  update_listbox_visibility (self);
}

/**
 * hdy_preferences_group_new:
 *
340
 * Creates a new `HdyPreferencesGroup`.
Adrien Plazas's avatar
Adrien Plazas committed
341
 *
342
 * Returns: the newly created `HdyPreferencesGroup`
Adrien Plazas's avatar
Adrien Plazas committed
343
 *
344
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
345
 */
Ujjwal Kumar's avatar
Ujjwal Kumar committed
346
GtkWidget *
Adrien Plazas's avatar
Adrien Plazas committed
347
348
349
350
351
352
hdy_preferences_group_new (void)
{
  return g_object_new (HDY_TYPE_PREFERENCES_GROUP, NULL);
}

/**
353
354
 * hdy_preferences_group_get_title: (attributes org.gtk.Method.get_property=title)
 * @self: a preferences group
Adrien Plazas's avatar
Adrien Plazas committed
355
356
357
 *
 * Gets the title of @self.
 *
358
 * Returns: the title of @self
Adrien Plazas's avatar
Adrien Plazas committed
359
 *
360
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
361
362
363
364
365
366
367
368
369
370
371
372
373
374
 */
const gchar *
hdy_preferences_group_get_title (HdyPreferencesGroup *self)
{
  HdyPreferencesGroupPrivate *priv;

  g_return_val_if_fail (HDY_IS_PREFERENCES_GROUP (self), NULL);

  priv = hdy_preferences_group_get_instance_private (self);

  return gtk_label_get_text (priv->title);
}

/**
375
376
 * hdy_preferences_group_set_title: (attributes org.gtk.Method.set_property=title)
 * @self: a preferences group
Adrien Plazas's avatar
Adrien Plazas committed
377
378
379
380
 * @title: the title
 *
 * Sets the title for @self.
 *
381
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
 */
void
hdy_preferences_group_set_title (HdyPreferencesGroup *self,
                                 const gchar         *title)
{
  HdyPreferencesGroupPrivate *priv;

  g_return_if_fail (HDY_IS_PREFERENCES_GROUP (self));

  priv = hdy_preferences_group_get_instance_private (self);

  if (g_strcmp0 (gtk_label_get_label (priv->title), title) == 0)
    return;

  gtk_label_set_label (priv->title, title);
  update_title_visibility (self);

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

/**
403
404
 * hdy_preferences_group_get_description: (attributes org.gtk.Method.get_property=description)
 * @self: a preferences group
Adrien Plazas's avatar
Adrien Plazas committed
405
 *
406
 * Returns: the description of @self
Adrien Plazas's avatar
Adrien Plazas committed
407
 *
408
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
409
410
411
412
413
414
415
416
417
418
419
420
421
422
 */
const gchar *
hdy_preferences_group_get_description (HdyPreferencesGroup *self)
{
  HdyPreferencesGroupPrivate *priv;

  g_return_val_if_fail (HDY_IS_PREFERENCES_GROUP (self), NULL);

  priv = hdy_preferences_group_get_instance_private (self);

  return gtk_label_get_text (priv->description);
}

/**
423
424
 * hdy_preferences_group_set_description: (attributes org.gtk.Method.set_property=description)
 * @self: a preferences group
Adrien Plazas's avatar
Adrien Plazas committed
425
426
427
428
 * @description: the description
 *
 * Sets the description for @self.
 *
429
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
 */
void
hdy_preferences_group_set_description (HdyPreferencesGroup *self,
                                       const gchar         *description)
{
  HdyPreferencesGroupPrivate *priv;

  g_return_if_fail (HDY_IS_PREFERENCES_GROUP (self));

  priv = hdy_preferences_group_get_instance_private (self);

  if (g_strcmp0 (gtk_label_get_label (priv->description), description) == 0)
    return;

  gtk_label_set_label (priv->description, description);
  update_description_visibility (self);

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

450
/**
451
452
 * hdy_preferences_group_get_use_markup: (attributes org.gtk.Method.get_property=use-markup)
 * @self: a preferences group
453
454
455
 *
 * Gets whether @self uses markup for the title and description.
 *
456
 * Returns: whether @self uses markup for its labels
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
 *
 * Since: 1.4
 */
gboolean
hdy_preferences_group_get_use_markup (HdyPreferencesGroup *self)
{
  HdyPreferencesGroupPrivate *priv;

  g_return_val_if_fail (HDY_IS_PREFERENCES_GROUP (self), FALSE);

  priv = hdy_preferences_group_get_instance_private (self);

  return gtk_label_get_use_markup (priv->title);
}

/**
473
474
 * hdy_preferences_group_set_use_markup: (attributes org.gtk.Method.set_property=use-markup)
 * @self: a preferences group
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
 * @use_markup: whether to use markup
 *
 * Sets whether @self uses markup for the title and description.
 *
 * Since: 1.4
 */
void
hdy_preferences_group_set_use_markup (HdyPreferencesGroup *self,
                                      gboolean             use_markup)
{
  HdyPreferencesGroupPrivate *priv;

  g_return_if_fail (HDY_IS_PREFERENCES_GROUP (self));

  priv = hdy_preferences_group_get_instance_private (self);

  use_markup = !!use_markup;

  if (gtk_label_get_use_markup (priv->title) == use_markup)
    return;

  gtk_label_set_use_markup (priv->title, use_markup);
  gtk_label_set_use_markup (priv->description, use_markup);

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

Adrien Plazas's avatar
Adrien Plazas committed
502
503
504
505
static void
add_preferences_to_model (HdyPreferencesRow *row,
                          GListStore        *model)
{
506
507
  const gchar *title;

Adrien Plazas's avatar
Adrien Plazas committed
508
509
510
  g_assert (HDY_IS_PREFERENCES_ROW (row));
  g_assert (G_IS_LIST_STORE (model));

511
512
513
  if (!gtk_widget_get_visible (GTK_WIDGET (row)))
    return;

514
515
516
517
518
  title = hdy_preferences_row_get_title (row);

  if (!title || !*title)
    return;

Adrien Plazas's avatar
Adrien Plazas committed
519
520
521
  g_list_store_append (model, row);
}

522
/*< private >
Adrien Plazas's avatar
Adrien Plazas committed
523
 * hdy_preferences_group_add_preferences_to_model: (skip)
524
 * @self: a preferences group
Adrien Plazas's avatar
Adrien Plazas committed
525
526
527
528
 * @model: the model
 *
 * Add preferences from @self to the model.
 *
529
 * Since: 1.0
Adrien Plazas's avatar
Adrien Plazas committed
530
531
532
533
534
535
536
537
538
539
 */
void
hdy_preferences_group_add_preferences_to_model (HdyPreferencesGroup *self,
                                                GListStore          *model)
{
  HdyPreferencesGroupPrivate *priv = hdy_preferences_group_get_instance_private (self);

  g_return_if_fail (HDY_IS_PREFERENCES_GROUP (self));
  g_return_if_fail (G_IS_LIST_STORE (model));

540
541
542
  if (!gtk_widget_get_visible (GTK_WIDGET (self)))
    return;

Adrien Plazas's avatar
Adrien Plazas committed
543
544
  gtk_container_foreach (GTK_CONTAINER (priv->listbox), (GtkCallback) add_preferences_to_model, model);
}