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
10 #include "adw-view-switcher-title.h"
11 #include "adw-squeezer.h"
12 #include "adw-window-title.h"
13
14 /**
15 * SECTION:adwviewswitchertitle
16 * @short_description: A view switcher title.
17 * @title: AdwViewSwitcherTitle
18 * @See_also: #AdwHeaderBar, #AdwViewSwitcher, #AdwViewSwitcherBar
19 *
20 * A widget letting you switch between multiple views offered by a #GtkStack,
21 * via an #AdwViewSwitcher. It is designed to be used as the title widget of a
22 * #AdwHeaderBar, 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 #AdwViewSwitcherBar:reveal property to
27 * #AdwViewSwitcherTitle: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="AdwHeaderBar">
35 * <property name="centering-policy">strict</property>
36 * <child type="title">
37 * <object class="AdwViewSwitcherTitle"
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="AdwViewSwitcherBar">
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 * #AdwViewSwitcherTitle 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 _AdwViewSwitcherTitle
82 {
83 GtkWidget parent_instance;
84
85 AdwSqueezer *squeezer;
86 AdwWindowTitle *title_widget;
87 AdwViewSwitcher *view_switcher;
88
89 gboolean view_switcher_enabled;
90 GtkSelectionModel *pages;
91 };
92
93 static GParamSpec *props[LAST_PROP];
94
G_DEFINE_TYPE(AdwViewSwitcherTitle,adw_view_switcher_title,GTK_TYPE_WIDGET)95 G_DEFINE_TYPE (AdwViewSwitcherTitle, adw_view_switcher_title, GTK_TYPE_WIDGET)
96
97 static void
98 update_view_switcher_visible (AdwViewSwitcherTitle *self)
99 {
100 AdwSqueezerPage *switcher_page;
101 int count = 0;
102
103 if (!self->squeezer)
104 return;
105
106 if (self->view_switcher_enabled && self->pages) {
107 guint i, n;
108
109 n = g_list_model_get_n_items (G_LIST_MODEL (self->pages));
110 for (i = 0; i < n; i++) {
111 GtkStackPage *page = g_list_model_get_item (G_LIST_MODEL (self->pages), i);
112
113 if (gtk_stack_page_get_visible (page))
114 count++;
115 }
116 }
117
118 switcher_page = adw_squeezer_get_page (self->squeezer, GTK_WIDGET (self->view_switcher));
119 adw_squeezer_page_set_enabled (switcher_page, count > 1);
120 }
121
122 static void
notify_squeezer_visible_child_cb(GObject * self)123 notify_squeezer_visible_child_cb (GObject *self)
124 {
125 g_object_notify_by_pspec (self, props[PROP_TITLE_VISIBLE]);
126 }
127
128 static void
adw_view_switcher_title_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)129 adw_view_switcher_title_get_property (GObject *object,
130 guint prop_id,
131 GValue *value,
132 GParamSpec *pspec)
133 {
134 AdwViewSwitcherTitle *self = ADW_VIEW_SWITCHER_TITLE (object);
135
136 switch (prop_id) {
137 case PROP_POLICY:
138 g_value_set_enum (value, adw_view_switcher_title_get_policy (self));
139 break;
140 case PROP_STACK:
141 g_value_set_object (value, adw_view_switcher_title_get_stack (self));
142 break;
143 case PROP_TITLE:
144 g_value_set_string (value, adw_view_switcher_title_get_title (self));
145 break;
146 case PROP_SUBTITLE:
147 g_value_set_string (value, adw_view_switcher_title_get_subtitle (self));
148 break;
149 case PROP_VIEW_SWITCHER_ENABLED:
150 g_value_set_boolean (value, adw_view_switcher_title_get_view_switcher_enabled (self));
151 break;
152 case PROP_TITLE_VISIBLE:
153 g_value_set_boolean (value, adw_view_switcher_title_get_title_visible (self));
154 break;
155 default:
156 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
157 break;
158 }
159 }
160
161 static void
adw_view_switcher_title_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)162 adw_view_switcher_title_set_property (GObject *object,
163 guint prop_id,
164 const GValue *value,
165 GParamSpec *pspec)
166 {
167 AdwViewSwitcherTitle *self = ADW_VIEW_SWITCHER_TITLE (object);
168
169 switch (prop_id) {
170 case PROP_POLICY:
171 adw_view_switcher_title_set_policy (self, g_value_get_enum (value));
172 break;
173 case PROP_STACK:
174 adw_view_switcher_title_set_stack (self, g_value_get_object (value));
175 break;
176 case PROP_TITLE:
177 adw_view_switcher_title_set_title (self, g_value_get_string (value));
178 break;
179 case PROP_SUBTITLE:
180 adw_view_switcher_title_set_subtitle (self, g_value_get_string (value));
181 break;
182 case PROP_VIEW_SWITCHER_ENABLED:
183 adw_view_switcher_title_set_view_switcher_enabled (self, g_value_get_boolean (value));
184 break;
185 default:
186 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
187 break;
188 }
189 }
190
191 static void
adw_view_switcher_title_dispose(GObject * object)192 adw_view_switcher_title_dispose (GObject *object) {
193 AdwViewSwitcherTitle *self = (AdwViewSwitcherTitle *)object;
194
195 if (self->pages)
196 g_signal_handlers_disconnect_by_func (self->pages, G_CALLBACK (update_view_switcher_visible), self);
197
198 if (self->squeezer)
199 gtk_widget_unparent (GTK_WIDGET (self->squeezer));
200
201 G_OBJECT_CLASS (adw_view_switcher_title_parent_class)->dispose (object);
202 }
203
204 static void
adw_view_switcher_title_class_init(AdwViewSwitcherTitleClass * klass)205 adw_view_switcher_title_class_init (AdwViewSwitcherTitleClass *klass)
206 {
207 GObjectClass *object_class = G_OBJECT_CLASS (klass);
208 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
209
210 object_class->dispose = adw_view_switcher_title_dispose;
211 object_class->get_property = adw_view_switcher_title_get_property;
212 object_class->set_property = adw_view_switcher_title_set_property;
213
214 /**
215 * AdwViewSwitcherTitle:policy:
216 *
217 * The #AdwViewSwitcherPolicy the #AdwViewSwitcher should use to determine
218 * which mode to use.
219 *
220 * Since: 1.0
221 */
222 props[PROP_POLICY] =
223 g_param_spec_enum ("policy",
224 "Policy",
225 "The policy to determine the mode to use",
226 ADW_TYPE_VIEW_SWITCHER_POLICY, ADW_VIEW_SWITCHER_POLICY_AUTO,
227 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
228
229 /**
230 * AdwViewSwitcherTitle:stack:
231 *
232 * The #GtkStack the #AdwViewSwitcher controls.
233 *
234 * Since: 1.0
235 */
236 props[PROP_STACK] =
237 g_param_spec_object ("stack",
238 "Stack",
239 "Stack",
240 GTK_TYPE_STACK,
241 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
242
243 /**
244 * AdwViewSwitcherTitle:title:
245 *
246 * The title of the #AdwViewSwitcher.
247 *
248 * Since: 1.0
249 */
250 props[PROP_TITLE] =
251 g_param_spec_string ("title",
252 "Title",
253 "The title to display",
254 NULL,
255 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
256
257 /**
258 * AdwViewSwitcherTitle:subtitle:
259 *
260 * The subtitle of the #AdwViewSwitcher.
261 *
262 * Since: 1.0
263 */
264 props[PROP_SUBTITLE] =
265 g_param_spec_string ("subtitle",
266 "Subtitle",
267 "The subtitle to display",
268 NULL,
269 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
270
271 /**
272 * AdwViewSwitcherTitle:view-switcher-enabled:
273 *
274 * Whether the bar should be revealed or hidden.
275 *
276 * Since: 1.0
277 */
278 props[PROP_VIEW_SWITCHER_ENABLED] =
279 g_param_spec_boolean ("view-switcher-enabled",
280 "View switcher enabled",
281 "Whether the view switcher is enabled",
282 TRUE,
283 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
284
285 /**
286 * AdwViewSwitcherTitle:title-visible:
287 *
288 * Whether the bar should be revealed or hidden.
289 *
290 * Since: 1.0
291 */
292 props[PROP_TITLE_VISIBLE] =
293 g_param_spec_boolean ("title-visible",
294 "Title visible",
295 "Whether the title label is visible",
296 TRUE,
297 G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
298
299 g_object_class_install_properties (object_class, LAST_PROP, props);
300
301 gtk_widget_class_set_css_name (widget_class, "viewswitchertitle");
302 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
303
304 gtk_widget_class_set_template_from_resource (widget_class,
305 "/org/gnome/Adwaita/ui/adw-view-switcher-title.ui");
306 gtk_widget_class_bind_template_child (widget_class, AdwViewSwitcherTitle, squeezer);
307 gtk_widget_class_bind_template_child (widget_class, AdwViewSwitcherTitle, title_widget);
308 gtk_widget_class_bind_template_child (widget_class, AdwViewSwitcherTitle, view_switcher);
309 gtk_widget_class_bind_template_callback (widget_class, notify_squeezer_visible_child_cb);
310 }
311
312 static void
adw_view_switcher_title_init(AdwViewSwitcherTitle * self)313 adw_view_switcher_title_init (AdwViewSwitcherTitle *self)
314 {
315 /* This must be initialized before the template so the embedded view switcher
316 * can pick up the correct default value.
317 */
318 self->view_switcher_enabled = TRUE;
319
320 gtk_widget_init_template (GTK_WIDGET (self));
321
322 update_view_switcher_visible (self);
323 }
324
325 /**
326 * adw_view_switcher_title_new:
327 *
328 * Creates a new #AdwViewSwitcherTitle widget.
329 *
330 * Returns: a new #AdwViewSwitcherTitle
331 *
332 * Since: 1.0
333 */
334 GtkWidget *
adw_view_switcher_title_new(void)335 adw_view_switcher_title_new (void)
336 {
337 return g_object_new (ADW_TYPE_VIEW_SWITCHER_TITLE, NULL);
338 }
339
340 /**
341 * adw_view_switcher_title_get_policy:
342 * @self: a #AdwViewSwitcherTitle
343 *
344 * Gets the policy of @self.
345 *
346 * Returns: the policy of @self
347 *
348 * Since: 1.0
349 */
350 AdwViewSwitcherPolicy
adw_view_switcher_title_get_policy(AdwViewSwitcherTitle * self)351 adw_view_switcher_title_get_policy (AdwViewSwitcherTitle *self)
352 {
353 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self), ADW_VIEW_SWITCHER_POLICY_NARROW);
354
355 return adw_view_switcher_get_policy (self->view_switcher);
356 }
357
358 /**
359 * adw_view_switcher_title_set_policy:
360 * @self: a #AdwViewSwitcherTitle
361 * @policy: the new policy
362 *
363 * Sets the policy of @self.
364 *
365 * Since: 1.0
366 */
367 void
adw_view_switcher_title_set_policy(AdwViewSwitcherTitle * self,AdwViewSwitcherPolicy policy)368 adw_view_switcher_title_set_policy (AdwViewSwitcherTitle *self,
369 AdwViewSwitcherPolicy policy)
370 {
371 g_return_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self));
372
373 if (adw_view_switcher_get_policy (self->view_switcher) == policy)
374 return;
375
376 adw_view_switcher_set_policy (self->view_switcher, policy);
377
378 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]);
379
380 gtk_widget_queue_resize (GTK_WIDGET (self));
381 }
382
383 /**
384 * adw_view_switcher_title_get_stack:
385 * @self: a #AdwViewSwitcherTitle
386 *
387 * Get the #GtkStack being controlled by the #AdwViewSwitcher.
388 *
389 * Returns: (nullable) (transfer none): the #GtkStack, or %NULL if none has been set
390 *
391 * Since: 1.0
392 */
393 GtkStack *
adw_view_switcher_title_get_stack(AdwViewSwitcherTitle * self)394 adw_view_switcher_title_get_stack (AdwViewSwitcherTitle *self)
395 {
396 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self), NULL);
397
398 return adw_view_switcher_get_stack (self->view_switcher);
399 }
400
401 /**
402 * adw_view_switcher_title_set_stack:
403 * @self: a #AdwViewSwitcherTitle
404 * @stack: (nullable): a #GtkStack
405 *
406 * Sets the #GtkStack to control.
407 *
408 * Since: 1.0
409 */
410 void
adw_view_switcher_title_set_stack(AdwViewSwitcherTitle * self,GtkStack * stack)411 adw_view_switcher_title_set_stack (AdwViewSwitcherTitle *self,
412 GtkStack *stack)
413 {
414 GtkStack *previous_stack;
415
416 g_return_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self));
417 g_return_if_fail (stack == NULL || GTK_IS_STACK (stack));
418
419 previous_stack = adw_view_switcher_get_stack (self->view_switcher);
420
421 if (previous_stack == stack)
422 return;
423
424 if (previous_stack) {
425 g_signal_handlers_disconnect_by_func (self->pages, G_CALLBACK (update_view_switcher_visible), self);
426 g_clear_object (&self->pages);
427 }
428
429 adw_view_switcher_set_stack (self->view_switcher, stack);
430
431 if (stack) {
432 self->pages = gtk_stack_get_pages (stack);
433
434 g_signal_connect_swapped (self->pages, "items-changed", G_CALLBACK (update_view_switcher_visible), self);
435 }
436
437 update_view_switcher_visible (self);
438
439 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]);
440 }
441
442 /**
443 * adw_view_switcher_title_get_title:
444 * @self: a #AdwViewSwitcherTitle
445 *
446 * Gets the title of @self. See adw_view_switcher_title_set_title().
447 *
448 * Returns: (transfer none) (nullable): the title of @self, or %NULL.
449 *
450 * Since: 1.0
451 */
452 const char *
adw_view_switcher_title_get_title(AdwViewSwitcherTitle * self)453 adw_view_switcher_title_get_title (AdwViewSwitcherTitle *self)
454 {
455 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self), NULL);
456
457 return adw_window_title_get_title (self->title_widget);
458 }
459
460 /**
461 * adw_view_switcher_title_set_title:
462 * @self: a #AdwViewSwitcherTitle
463 * @title: (nullable): a title, or %NULL
464 *
465 * Sets the title of @self. The title should give a user additional details. A
466 * good title should not include the application name.
467 *
468 * Since: 1.0
469 */
470 void
adw_view_switcher_title_set_title(AdwViewSwitcherTitle * self,const char * title)471 adw_view_switcher_title_set_title (AdwViewSwitcherTitle *self,
472 const char *title)
473 {
474 g_return_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self));
475
476 if (g_strcmp0 (adw_window_title_get_title (self->title_widget), title) == 0)
477 return;
478
479 adw_window_title_set_title (self->title_widget, title);
480
481 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]);
482 }
483
484 /**
485 * adw_view_switcher_title_get_subtitle:
486 * @self: a #AdwViewSwitcherTitle
487 *
488 * Gets the subtitle of @self. See adw_view_switcher_title_set_subtitle().
489 *
490 * Returns: (transfer none) (nullable): the subtitle of @self, or %NULL.
491 *
492 * Since: 1.0
493 */
494 const char *
adw_view_switcher_title_get_subtitle(AdwViewSwitcherTitle * self)495 adw_view_switcher_title_get_subtitle (AdwViewSwitcherTitle *self)
496 {
497 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self), NULL);
498
499 return adw_window_title_get_subtitle (self->title_widget);
500 }
501
502 /**
503 * adw_view_switcher_title_set_subtitle:
504 * @self: a #AdwViewSwitcherTitle
505 * @subtitle: (nullable): a subtitle, or %NULL
506 *
507 * Sets the subtitle of @self. The subtitle should give a user additional
508 * details.
509 *
510 * Since: 1.0
511 */
512 void
adw_view_switcher_title_set_subtitle(AdwViewSwitcherTitle * self,const char * subtitle)513 adw_view_switcher_title_set_subtitle (AdwViewSwitcherTitle *self,
514 const char *subtitle)
515 {
516 g_return_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self));
517
518 if (g_strcmp0 (adw_window_title_get_subtitle (self->title_widget), subtitle) == 0)
519 return;
520
521 adw_window_title_set_subtitle (self->title_widget, subtitle);
522
523 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]);
524 }
525
526 /**
527 * adw_view_switcher_title_get_view_switcher_enabled:
528 * @self: a #AdwViewSwitcherTitle
529 *
530 * Gets whether @self's view switcher is enabled.
531 *
532 * See adw_view_switcher_title_set_view_switcher_enabled().
533 *
534 * Returns: %TRUE if the view switcher is enabled, %FALSE otherwise.
535 *
536 * Since: 1.0
537 */
538 gboolean
adw_view_switcher_title_get_view_switcher_enabled(AdwViewSwitcherTitle * self)539 adw_view_switcher_title_get_view_switcher_enabled (AdwViewSwitcherTitle *self)
540 {
541 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self), FALSE);
542
543 return self->view_switcher_enabled;
544 }
545
546 /**
547 * adw_view_switcher_title_set_view_switcher_enabled:
548 * @self: a #AdwViewSwitcherTitle
549 * @enabled: %TRUE to enable the view switcher, %FALSE to disable it
550 *
551 * Make @self enable or disable its view switcher. If it is disabled, the title
552 * will be displayed instead. This allows to programmatically and prematurely
553 * hide the view switcher of @self even if it fits in the available space.
554 *
555 * This can be used e.g. to ensure the view switcher is hidden below a certain
556 * window width, or any other constraint you find suitable.
557 *
558 * Since: 1.0
559 */
560 void
adw_view_switcher_title_set_view_switcher_enabled(AdwViewSwitcherTitle * self,gboolean enabled)561 adw_view_switcher_title_set_view_switcher_enabled (AdwViewSwitcherTitle *self,
562 gboolean enabled)
563 {
564 g_return_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self));
565
566 enabled = !!enabled;
567
568 if (self->view_switcher_enabled == enabled)
569 return;
570
571 self->view_switcher_enabled = enabled;
572 update_view_switcher_visible (self);
573
574 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VIEW_SWITCHER_ENABLED]);
575 }
576
577 /**
578 * adw_view_switcher_title_get_title_visible:
579 * @self: a #AdwViewSwitcherTitle
580 *
581 * Get whether the title label of @self is visible.
582 *
583 * Returns: %TRUE if the title label of @self is visible, %FALSE if not.
584 *
585 * Since: 1.0
586 */
587 gboolean
adw_view_switcher_title_get_title_visible(AdwViewSwitcherTitle * self)588 adw_view_switcher_title_get_title_visible (AdwViewSwitcherTitle *self)
589 {
590 g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_TITLE (self), FALSE);
591
592 return adw_squeezer_get_visible_child (self->squeezer) == GTK_WIDGET (self->title_widget);
593 }
594