1 /*
2 * Copyright (C) 2019 Zander Brown <zbrown@gnome.org>
3 * Copyright (C) 2019 Purism SPC
4 *
5 * SPDX-License-Identifier: LGPL-2.1+
6 */
7
8 #include "config.h"
9 #include <glib/gi18n-lib.h>
10
11 #include "hdy-view-switcher-title.h"
12 #include "hdy-squeezer.h"
13
14 /**
15 * SECTION:hdy-view-switcher-title
16 * @short_description: A view switcher title.
17 * @title: HdyViewSwitcherTitle
18 * @See_also: #HdyHeaderBar, #HdyViewSwitcher, #HdyViewSwitcherBar
19 *
20 * A widget letting you switch between multiple views offered by a #GtkStack,
21 * via an #HdyViewSwitcher. It is designed to be used as the title widget of a
22 * #HdyHeaderBar, and will display the window's title when the window is too
23 * narrow to fit the view switcher e.g. on mobile phones, or if there are less
24 * than two views.
25 *
26 * You can conveniently bind the #HdyViewSwitcherBar:reveal property to
27 * #HdyViewSwitcherTitle:title-visible to automatically reveal the view switcher
28 * bar when the title label is displayed in place of the view switcher.
29 *
30 * An example of the UI definition for a common use case:
31 * |[
32 * <object class="GtkWindow"/>
33 * <child type="titlebar">
34 * <object class="HdyHeaderBar">
35 * <property name="centering-policy">strict</property>
36 * <child type="title">
37 * <object class="HdyViewSwitcherTitle"
38 * id="view_switcher_title">
39 * <property name="stack">stack</property>
40 * </object>
41 * </child>
42 * </object>
43 * </child>
44 * <child>
45 * <object class="GtkBox">
46 * <child>
47 * <object class="GtkStack" id="stack"/>
48 * </child>
49 * <child>
50 * <object class="HdyViewSwitcherBar">
51 * <property name="stack">stack</property>
52 * <property name="reveal"
53 * bind-source="view_switcher_title"
54 * bind-property="title-visible"
55 * bind-flags="sync-create"/>
56 * </object>
57 * </child>
58 * </object>
59 * </child>
60 * </object>
61 * ]|
62 *
63 * # CSS nodes
64 *
65 * #HdyViewSwitcherTitle has a single CSS node with name viewswitchertitle.
66 *
67 * Since: 1.0
68 */
69
70 enum {
71 PROP_0,
72 PROP_POLICY,
73 PROP_STACK,
74 PROP_TITLE,
75 PROP_SUBTITLE,
76 PROP_VIEW_SWITCHER_ENABLED,
77 PROP_TITLE_VISIBLE,
78 LAST_PROP,
79 };
80
81 struct _HdyViewSwitcherTitle
82 {
83 GtkBin parent_instance;
84
85 HdySqueezer *squeezer;
86 GtkLabel *subtitle_label;
87 GtkBox *title_box;
88 GtkLabel *title_label;
89 HdyViewSwitcher *view_switcher;
90
91 gboolean view_switcher_enabled;
92 };
93
94 static GParamSpec *props[LAST_PROP];
95
G_DEFINE_TYPE(HdyViewSwitcherTitle,hdy_view_switcher_title,GTK_TYPE_BIN)96 G_DEFINE_TYPE (HdyViewSwitcherTitle, hdy_view_switcher_title, GTK_TYPE_BIN)
97
98 static void
99 update_subtitle_label (HdyViewSwitcherTitle *self)
100 {
101 const gchar *subtitle = gtk_label_get_label (self->subtitle_label);
102
103 gtk_widget_set_visible (GTK_WIDGET (self->subtitle_label), subtitle && subtitle[0]);
104
105 gtk_widget_queue_resize (GTK_WIDGET (self));
106 }
107
108 static void
count_children_cb(GtkWidget * widget,gint * count)109 count_children_cb (GtkWidget *widget,
110 gint *count)
111 {
112 (*count)++;
113 }
114
115 static void
update_view_switcher_visible(HdyViewSwitcherTitle * self)116 update_view_switcher_visible (HdyViewSwitcherTitle *self)
117 {
118 GtkStack *stack = hdy_view_switcher_get_stack (self->view_switcher);
119 gint count = 0;
120
121 if (self->view_switcher_enabled && stack)
122 gtk_container_foreach (GTK_CONTAINER (stack), (GtkCallback) count_children_cb, &count);
123
124 hdy_squeezer_set_child_enabled (self->squeezer, GTK_WIDGET (self->view_switcher), count > 1);
125 }
126
127 static void
notify_squeezer_visible_child_cb(GObject * self)128 notify_squeezer_visible_child_cb (GObject *self)
129 {
130 g_object_notify_by_pspec (self, props[PROP_TITLE_VISIBLE]);
131 }
132
133 static void
hdy_view_switcher_title_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)134 hdy_view_switcher_title_get_property (GObject *object,
135 guint prop_id,
136 GValue *value,
137 GParamSpec *pspec)
138 {
139 HdyViewSwitcherTitle *self = HDY_VIEW_SWITCHER_TITLE (object);
140
141 switch (prop_id) {
142 case PROP_POLICY:
143 g_value_set_enum (value, hdy_view_switcher_title_get_policy (self));
144 break;
145 case PROP_STACK:
146 g_value_set_object (value, hdy_view_switcher_title_get_stack (self));
147 break;
148 case PROP_TITLE:
149 g_value_set_string (value, hdy_view_switcher_title_get_title (self));
150 break;
151 case PROP_SUBTITLE:
152 g_value_set_string (value, hdy_view_switcher_title_get_subtitle (self));
153 break;
154 case PROP_VIEW_SWITCHER_ENABLED:
155 g_value_set_boolean (value, hdy_view_switcher_title_get_view_switcher_enabled (self));
156 break;
157 case PROP_TITLE_VISIBLE:
158 g_value_set_boolean (value, hdy_view_switcher_title_get_title_visible (self));
159 break;
160 default:
161 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
162 break;
163 }
164 }
165
166 static void
hdy_view_switcher_title_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)167 hdy_view_switcher_title_set_property (GObject *object,
168 guint prop_id,
169 const GValue *value,
170 GParamSpec *pspec)
171 {
172 HdyViewSwitcherTitle *self = HDY_VIEW_SWITCHER_TITLE (object);
173
174 switch (prop_id) {
175 case PROP_POLICY:
176 hdy_view_switcher_title_set_policy (self, g_value_get_enum (value));
177 break;
178 case PROP_STACK:
179 hdy_view_switcher_title_set_stack (self, g_value_get_object (value));
180 break;
181 case PROP_TITLE:
182 hdy_view_switcher_title_set_title (self, g_value_get_string (value));
183 break;
184 case PROP_SUBTITLE:
185 hdy_view_switcher_title_set_subtitle (self, g_value_get_string (value));
186 break;
187 case PROP_VIEW_SWITCHER_ENABLED:
188 hdy_view_switcher_title_set_view_switcher_enabled (self, g_value_get_boolean (value));
189 break;
190 default:
191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
192 break;
193 }
194 }
195
196 static void
hdy_view_switcher_title_dispose(GObject * object)197 hdy_view_switcher_title_dispose (GObject *object) {
198 HdyViewSwitcherTitle *self = (HdyViewSwitcherTitle *)object;
199
200 if (self->view_switcher) {
201 GtkStack *stack = hdy_view_switcher_get_stack (self->view_switcher);
202
203 if (stack)
204 g_signal_handlers_disconnect_by_func (stack, G_CALLBACK (update_view_switcher_visible), self);
205 }
206
207 G_OBJECT_CLASS (hdy_view_switcher_title_parent_class)->dispose (object);
208 }
209
210 static void
hdy_view_switcher_title_class_init(HdyViewSwitcherTitleClass * klass)211 hdy_view_switcher_title_class_init (HdyViewSwitcherTitleClass *klass)
212 {
213 GObjectClass *object_class = G_OBJECT_CLASS (klass);
214 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
215
216 object_class->dispose = hdy_view_switcher_title_dispose;
217 object_class->get_property = hdy_view_switcher_title_get_property;
218 object_class->set_property = hdy_view_switcher_title_set_property;
219
220 /**
221 * HdyViewSwitcherTitle:policy:
222 *
223 * The #HdyViewSwitcherPolicy the #HdyViewSwitcher should use to determine
224 * which mode to use.
225 *
226 * Since: 1.0
227 */
228 props[PROP_POLICY] =
229 g_param_spec_enum ("policy",
230 _("Policy"),
231 _("The policy to determine the mode to use"),
232 HDY_TYPE_VIEW_SWITCHER_POLICY, HDY_VIEW_SWITCHER_POLICY_AUTO,
233 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
234
235 /**
236 * HdyViewSwitcherTitle:stack:
237 *
238 * The #GtkStack the #HdyViewSwitcher controls.
239 *
240 * Since: 1.0
241 */
242 props[PROP_STACK] =
243 g_param_spec_object ("stack",
244 _("Stack"),
245 _("Stack"),
246 GTK_TYPE_STACK,
247 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
248
249 /**
250 * HdyViewSwitcherTitle:title:
251 *
252 * The title of the #HdyViewSwitcher.
253 *
254 * Since: 1.0
255 */
256 props[PROP_TITLE] =
257 g_param_spec_string ("title",
258 _("Title"),
259 _("The title to display"),
260 NULL,
261 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
262
263 /**
264 * HdyViewSwitcherTitle:subtitle:
265 *
266 * The subtitle of the #HdyViewSwitcher.
267 *
268 * Since: 1.0
269 */
270 props[PROP_SUBTITLE] =
271 g_param_spec_string ("subtitle",
272 _("Subtitle"),
273 _("The subtitle to display"),
274 NULL,
275 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
276
277 /**
278 * HdyViewSwitcherTitle:view-switcher-enabled:
279 *
280 * Whether the bar should be revealed or hidden.
281 *
282 * Since: 1.0
283 */
284 props[PROP_VIEW_SWITCHER_ENABLED] =
285 g_param_spec_boolean ("view-switcher-enabled",
286 _("View switcher enabled"),
287 _("Whether the view switcher is enabled"),
288 TRUE,
289 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
290
291 /**
292 * HdyViewSwitcherTitle:title-visible:
293 *
294 * Whether the bar should be revealed or hidden.
295 *
296 * Since: 1.0
297 */
298 props[PROP_TITLE_VISIBLE] =
299 g_param_spec_boolean ("title-visible",
300 _("Title visible"),
301 _("Whether the title label is visible"),
302 TRUE,
303 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
304
305 g_object_class_install_properties (object_class, LAST_PROP, props);
306
307 gtk_widget_class_set_css_name (widget_class, "viewswitchertitle");
308
309 gtk_widget_class_set_template_from_resource (widget_class,
310 "/sm/puri/handy/ui/hdy-view-switcher-title.ui");
311 gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherTitle, squeezer);
312 gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherTitle, subtitle_label);
313 gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherTitle, title_box);
314 gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherTitle, title_label);
315 gtk_widget_class_bind_template_child (widget_class, HdyViewSwitcherTitle, view_switcher);
316 gtk_widget_class_bind_template_callback (widget_class, notify_squeezer_visible_child_cb);
317 }
318
319 static void
hdy_view_switcher_title_init(HdyViewSwitcherTitle * self)320 hdy_view_switcher_title_init (HdyViewSwitcherTitle *self)
321 {
322 /* This must be initialized before the template so the embedded view switcher
323 * can pick up the correct default value.
324 */
325 self->view_switcher_enabled = TRUE;
326
327 gtk_widget_init_template (GTK_WIDGET (self));
328
329 update_subtitle_label (self);
330 update_view_switcher_visible (self);
331 }
332
333 /**
334 * hdy_view_switcher_title_new:
335 *
336 * Creates a new #HdyViewSwitcherTitle widget.
337 *
338 * Returns: a new #HdyViewSwitcherTitle
339 *
340 * Since: 1.0
341 */
342 HdyViewSwitcherTitle *
hdy_view_switcher_title_new(void)343 hdy_view_switcher_title_new (void)
344 {
345 return g_object_new (HDY_TYPE_VIEW_SWITCHER_TITLE, NULL);
346 }
347
348 /**
349 * hdy_view_switcher_title_get_policy:
350 * @self: a #HdyViewSwitcherTitle
351 *
352 * Gets the policy of @self.
353 *
354 * Returns: the policy of @self
355 *
356 * Since: 1.0
357 */
358 HdyViewSwitcherPolicy
hdy_view_switcher_title_get_policy(HdyViewSwitcherTitle * self)359 hdy_view_switcher_title_get_policy (HdyViewSwitcherTitle *self)
360 {
361 g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self), HDY_VIEW_SWITCHER_POLICY_NARROW);
362
363 return hdy_view_switcher_get_policy (self->view_switcher);
364 }
365
366 /**
367 * hdy_view_switcher_title_set_policy:
368 * @self: a #HdyViewSwitcherTitle
369 * @policy: the new policy
370 *
371 * Sets the policy of @self.
372 *
373 * Since: 1.0
374 */
375 void
hdy_view_switcher_title_set_policy(HdyViewSwitcherTitle * self,HdyViewSwitcherPolicy policy)376 hdy_view_switcher_title_set_policy (HdyViewSwitcherTitle *self,
377 HdyViewSwitcherPolicy policy)
378 {
379 g_return_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self));
380
381 if (hdy_view_switcher_get_policy (self->view_switcher) == policy)
382 return;
383
384 hdy_view_switcher_set_policy (self->view_switcher, policy);
385
386 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]);
387
388 gtk_widget_queue_resize (GTK_WIDGET (self));
389 }
390
391 /**
392 * hdy_view_switcher_title_get_stack:
393 * @self: a #HdyViewSwitcherTitle
394 *
395 * Get the #GtkStack being controlled by the #HdyViewSwitcher.
396 *
397 * Returns: (nullable) (transfer none): the #GtkStack, or %NULL if none has been set
398 *
399 * Since: 1.0
400 */
401 GtkStack *
hdy_view_switcher_title_get_stack(HdyViewSwitcherTitle * self)402 hdy_view_switcher_title_get_stack (HdyViewSwitcherTitle *self)
403 {
404 g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self), NULL);
405
406 return hdy_view_switcher_get_stack (self->view_switcher);
407 }
408
409 /**
410 * hdy_view_switcher_title_set_stack:
411 * @self: a #HdyViewSwitcherTitle
412 * @stack: (nullable): a #GtkStack
413 *
414 * Sets the #GtkStack to control.
415 *
416 * Since: 1.0
417 */
418 void
hdy_view_switcher_title_set_stack(HdyViewSwitcherTitle * self,GtkStack * stack)419 hdy_view_switcher_title_set_stack (HdyViewSwitcherTitle *self,
420 GtkStack *stack)
421 {
422 GtkStack *previous_stack;
423
424 g_return_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self));
425 g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
426
427 previous_stack = hdy_view_switcher_get_stack (self->view_switcher);
428
429 if (previous_stack == stack)
430 return;
431
432 if (previous_stack)
433 g_signal_handlers_disconnect_by_func (previous_stack, G_CALLBACK (update_view_switcher_visible), self);
434
435 hdy_view_switcher_set_stack (self->view_switcher, stack);
436
437 if (stack) {
438 g_signal_connect_swapped (stack, "add", G_CALLBACK (update_view_switcher_visible), self);
439 g_signal_connect_swapped (stack, "remove", G_CALLBACK (update_view_switcher_visible), self);
440 }
441
442 update_view_switcher_visible (self);
443
444 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]);
445 }
446
447 /**
448 * hdy_view_switcher_title_get_title:
449 * @self: a #HdyViewSwitcherTitle
450 *
451 * Gets the title of @self. See hdy_view_switcher_title_set_title().
452 *
453 * Returns: (transfer none) (nullable): the title of @self, or %NULL.
454 *
455 * Since: 1.0
456 */
457 const gchar *
hdy_view_switcher_title_get_title(HdyViewSwitcherTitle * self)458 hdy_view_switcher_title_get_title (HdyViewSwitcherTitle *self)
459 {
460 g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self), NULL);
461
462 return gtk_label_get_label (self->title_label);
463 }
464
465 /**
466 * hdy_view_switcher_title_set_title:
467 * @self: a #HdyViewSwitcherTitle
468 * @title: (nullable): a title, or %NULL
469 *
470 * Sets the title of @self. The title should give a user additional details. A
471 * good title should not include the application name.
472 *
473 * Since: 1.0
474 */
475 void
hdy_view_switcher_title_set_title(HdyViewSwitcherTitle * self,const gchar * title)476 hdy_view_switcher_title_set_title (HdyViewSwitcherTitle *self,
477 const gchar *title)
478 {
479 g_return_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self));
480
481 if (g_strcmp0 (gtk_label_get_label (self->title_label), title) == 0)
482 return;
483
484 gtk_label_set_label (self->title_label, title);
485 gtk_widget_set_visible (GTK_WIDGET (self->title_label), title && title[0]);
486
487 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]);
488 }
489
490 /**
491 * hdy_view_switcher_title_get_subtitle:
492 * @self: a #HdyViewSwitcherTitle
493 *
494 * Gets the subtitle of @self. See hdy_view_switcher_title_set_subtitle().
495 *
496 * Returns: (transfer none) (nullable): the subtitle of @self, or %NULL.
497 *
498 * Since: 1.0
499 */
500 const gchar *
hdy_view_switcher_title_get_subtitle(HdyViewSwitcherTitle * self)501 hdy_view_switcher_title_get_subtitle (HdyViewSwitcherTitle *self)
502 {
503 g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self), NULL);
504
505 return gtk_label_get_label (self->subtitle_label);
506 }
507
508 /**
509 * hdy_view_switcher_title_set_subtitle:
510 * @self: a #HdyViewSwitcherTitle
511 * @subtitle: (nullable): a subtitle, or %NULL
512 *
513 * Sets the subtitle of @self. The subtitle should give a user additional
514 * details.
515 *
516 * Since: 1.0
517 */
518 void
hdy_view_switcher_title_set_subtitle(HdyViewSwitcherTitle * self,const gchar * subtitle)519 hdy_view_switcher_title_set_subtitle (HdyViewSwitcherTitle *self,
520 const gchar *subtitle)
521 {
522 g_return_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self));
523
524 if (g_strcmp0 (gtk_label_get_label (self->subtitle_label), subtitle) == 0)
525 return;
526
527 gtk_label_set_label (self->subtitle_label, subtitle);
528 update_subtitle_label (self);
529
530 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]);
531 }
532
533 /**
534 * hdy_view_switcher_title_get_view_switcher_enabled:
535 * @self: a #HdyViewSwitcherTitle
536 *
537 * Gets whether @self's view switcher is enabled.
538 *
539 * See hdy_view_switcher_title_set_view_switcher_enabled().
540 *
541 * Returns: %TRUE if the view switcher is enabled, %FALSE otherwise.
542 *
543 * Since: 1.0
544 */
545 gboolean
hdy_view_switcher_title_get_view_switcher_enabled(HdyViewSwitcherTitle * self)546 hdy_view_switcher_title_get_view_switcher_enabled (HdyViewSwitcherTitle *self)
547 {
548 g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self), FALSE);
549
550 return self->view_switcher_enabled;
551 }
552
553 /**
554 * hdy_view_switcher_title_set_view_switcher_enabled:
555 * @self: a #HdyViewSwitcherTitle
556 * @enabled: %TRUE to enable the view switcher, %FALSE to disable it
557 *
558 * Make @self enable or disable its view switcher. If it is disabled, the title
559 * will be displayed instead. This allows to programmatically and prematurely
560 * hide the view switcher of @self even if it fits in the available space.
561 *
562 * This can be used e.g. to ensure the view switcher is hidden below a certain
563 * window width, or any other constraint you find suitable.
564 *
565 * Since: 1.0
566 */
567 void
hdy_view_switcher_title_set_view_switcher_enabled(HdyViewSwitcherTitle * self,gboolean enabled)568 hdy_view_switcher_title_set_view_switcher_enabled (HdyViewSwitcherTitle *self,
569 gboolean enabled)
570 {
571 g_return_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self));
572
573 enabled = !!enabled;
574
575 if (self->view_switcher_enabled == enabled)
576 return;
577
578 self->view_switcher_enabled = enabled;
579 update_view_switcher_visible (self);
580
581 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VIEW_SWITCHER_ENABLED]);
582 }
583
584 /**
585 * hdy_view_switcher_title_get_title_visible:
586 * @self: a #HdyViewSwitcherTitle
587 *
588 * Get whether the title label of @self is visible.
589 *
590 * Returns: %TRUE if the title label of @self is visible, %FALSE if not.
591 *
592 * Since: 1.0
593 */
594 gboolean
hdy_view_switcher_title_get_title_visible(HdyViewSwitcherTitle * self)595 hdy_view_switcher_title_get_title_visible (HdyViewSwitcherTitle *self)
596 {
597 g_return_val_if_fail (HDY_IS_VIEW_SWITCHER_TITLE (self), FALSE);
598
599 return hdy_squeezer_get_visible_child (self->squeezer) == (GtkWidget *) self->title_box;
600 }
601