1 /*
2  * Copyright (C) 2019 Zander Brown <zbrown@gnome.org>
3  * Copyright (C) 2019 Purism SPC
4  *
5  * SPDX-License-Identifier: LGPL-2.1-or-later
6  */
7 
8 #include "config.h"
9 
10 #include "adw-enums.h"
11 #include "adw-view-switcher-bar.h"
12 
13 /**
14  * AdwViewSwitcherBar:
15  *
16  * A view switcher action bar.
17  *
18  * An action bar letting you switch between multiple views contained in a
19  * [class@Adw.ViewStack], via an [class@Adw.ViewSwitcher]. It is designed to be put
20  * at the bottom of a window and to be revealed only on really narrow windows,
21  * e.g. on mobile phones. It can't be revealed if there are less than two pages.
22  *
23  * You can conveniently bind the [property@Adw.ViewSwitcherBar:reveal] property
24  * to [property@Adw.ViewSwitcherTitle:title-visible] to automatically reveal the
25  * view switcher bar when the title label is displayed in place of the view
26  * switcher.
27  *
28  * An example of the UI definition for a common use case:
29  *
30  * ```xml
31  * <object class="GtkWindow"/>
32  *   <child type="titlebar">
33  *     <object class="AdwHeaderBar">
34  *       <property name="centering-policy">strict</property>
35  *       <child type="title">
36  *         <object class="AdwViewSwitcherTitle" id="title">
37  *           <property name="stack">stack</property>
38  *         </object>
39  *       </child>
40  *     </object>
41  *   </child>
42  *   <child>
43  *     <object class="GtkBox">
44  *       <child>
45  *         <object class="AdwViewStack" id="stack"/>
46  *       </child>
47  *       <child>
48  *         <object class="AdwViewSwitcherBar">
49  *           <property name="stack">stack</property>
50  *           <binding name="reveal">
51  *             <lookup name="title-visible">title</lookup>
52  *           </binding>
53  *         </object>
54  *       </child>
55  *     </object>
56  *   </child>
57  * </object>
58  * ```
59  *
60  * ## CSS nodes
61  *
62  * `AdwViewSwitcherBar` has a single CSS node with name` viewswitcherbar`.
63  *
64  * Since: 1.0
65  */
66 
67 enum {
68   PROP_0,
69   PROP_POLICY,
70   PROP_STACK,
71   PROP_REVEAL,
72   LAST_PROP,
73 };
74 
75 struct _AdwViewSwitcherBar
76 {
77   GtkWidget parent_instance;
78 
79   GtkWidget *action_bar;
80   GtkRevealer *revealer;
81   AdwViewSwitcher *view_switcher;
82 
83   AdwViewSwitcherPolicy policy;
84   GtkSelectionModel *pages;
85   gboolean reveal;
86 };
87 
88 static GParamSpec *props[LAST_PROP];
89 
G_DEFINE_TYPE(AdwViewSwitcherBar,adw_view_switcher_bar,GTK_TYPE_WIDGET)90 G_DEFINE_TYPE (AdwViewSwitcherBar, adw_view_switcher_bar, GTK_TYPE_WIDGET)
91 
92 static void
93 update_bar_revealed (AdwViewSwitcherBar *self) {
94   int count = 0;
95 
96   if (!self->revealer)
97     return;
98 
99   if (self->reveal && self->pages) {
100     guint i, n;
101 
102     n = g_list_model_get_n_items (G_LIST_MODEL (self->pages));
103     for (i = 0; i < n; i++) {
104       AdwViewStackPage *page = g_list_model_get_item (G_LIST_MODEL (self->pages), i);
105 
106       if (adw_view_stack_page_get_visible (page))
107         count++;
108     }
109   }
110 
111   gtk_revealer_set_reveal_child (self->revealer, count > 1);
112 }
113 
114 static void
adw_view_switcher_bar_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)115 adw_view_switcher_bar_get_property (GObject    *object,
116                                     guint       prop_id,
117                                     GValue     *value,
118                                     GParamSpec *pspec)
119 {
120   AdwViewSwitcherBar *self = ADW_VIEW_SWITCHER_BAR (object);
121 
122   switch (prop_id) {
123   case PROP_POLICY:
124     g_value_set_enum (value, adw_view_switcher_bar_get_policy (self));
125     break;
126   case PROP_STACK:
127     g_value_set_object (value, adw_view_switcher_bar_get_stack (self));
128     break;
129   case PROP_REVEAL:
130     g_value_set_boolean (value, adw_view_switcher_bar_get_reveal (self));
131     break;
132   default:
133     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
134     break;
135   }
136 }
137 
138 static void
adw_view_switcher_bar_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)139 adw_view_switcher_bar_set_property (GObject      *object,
140                                     guint         prop_id,
141                                     const GValue *value,
142                                     GParamSpec   *pspec)
143 {
144   AdwViewSwitcherBar *self = ADW_VIEW_SWITCHER_BAR (object);
145 
146   switch (prop_id) {
147   case PROP_POLICY:
148     adw_view_switcher_bar_set_policy (self, g_value_get_enum (value));
149     break;
150   case PROP_STACK:
151     adw_view_switcher_bar_set_stack (self, g_value_get_object (value));
152     break;
153   case PROP_REVEAL:
154     adw_view_switcher_bar_set_reveal (self, g_value_get_boolean (value));
155     break;
156   default:
157     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
158     break;
159   }
160 }
161 
162 static void
adw_view_switcher_bar_dispose(GObject * object)163 adw_view_switcher_bar_dispose (GObject *object)
164 {
165   AdwViewSwitcherBar *self = ADW_VIEW_SWITCHER_BAR (object);
166 
167   gtk_widget_unparent (self->action_bar);
168   self->revealer = NULL;
169 
170   G_OBJECT_CLASS (adw_view_switcher_bar_parent_class)->dispose (object);
171 }
172 
173 static void
adw_view_switcher_bar_class_init(AdwViewSwitcherBarClass * klass)174 adw_view_switcher_bar_class_init (AdwViewSwitcherBarClass *klass)
175 {
176   GObjectClass *object_class = G_OBJECT_CLASS (klass);
177   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
178 
179   object_class->get_property = adw_view_switcher_bar_get_property;
180   object_class->set_property = adw_view_switcher_bar_set_property;
181   object_class->dispose = adw_view_switcher_bar_dispose;
182 
183   /**
184    * AdwViewSwitcherBar:policy: (attributes org.gtk.Property.get=adw_view_switcher_bar_get_policy org.gtk.Property.set=adw_view_switcher_bar_set_policy)
185    *
186    * The policy to determine which mode to use.
187    *
188    * Since: 1.0
189    */
190   props[PROP_POLICY] =
191     g_param_spec_enum ("policy",
192                        "Policy",
193                        "The policy to determine the mode to use",
194                        ADW_TYPE_VIEW_SWITCHER_POLICY,
195                        ADW_VIEW_SWITCHER_POLICY_NARROW,
196                        G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
197 
198   /**
199    * AdwViewSwitcherBar:stack: (attributes org.gtk.Property.get=adw_view_switcher_bar_get_stack org.gtk.Property.set=adw_view_switcher_bar_set_stack)
200    *
201    * The stack the view switcher controls.
202    *
203    * Since: 1.0
204    */
205   props[PROP_STACK] =
206     g_param_spec_object ("stack",
207                          "Stack",
208                          "The stack the view switcher controls",
209                          ADW_TYPE_VIEW_STACK,
210                          G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
211 
212   /**
213    * AdwViewSwitcherBar:reveal: (attributes org.gtk.Property.get=adw_view_switcher_bar_get_reveal org.gtk.Property.set=adw_view_switcher_bar_set_reveal)
214    *
215    * Whether the bar should be revealed or hidden.
216    *
217    * Since: 1.0
218    */
219   props[PROP_REVEAL] =
220     g_param_spec_boolean ("reveal",
221                          "Reveal",
222                          "Whether the bar should be revealed or hidden",
223                          FALSE,
224                          G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
225 
226   g_object_class_install_properties (object_class, LAST_PROP, props);
227 
228   gtk_widget_class_set_css_name (widget_class, "viewswitcherbar");
229   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
230 
231   gtk_widget_class_set_template_from_resource (widget_class,
232                                                "/org/gnome/Adwaita/ui/adw-view-switcher-bar.ui");
233   gtk_widget_class_bind_template_child (widget_class, AdwViewSwitcherBar, action_bar);
234   gtk_widget_class_bind_template_child (widget_class, AdwViewSwitcherBar, view_switcher);
235 }
236 
237 static void
adw_view_switcher_bar_init(AdwViewSwitcherBar * self)238 adw_view_switcher_bar_init (AdwViewSwitcherBar *self)
239 {
240   /* This must be initialized before the template so the embedded view switcher
241    * can pick up the correct default value.
242    */
243   self->policy = ADW_VIEW_SWITCHER_POLICY_NARROW;
244 
245   gtk_widget_init_template (GTK_WIDGET (self));
246 
247   self->revealer = GTK_REVEALER (gtk_widget_get_first_child (GTK_WIDGET (self->action_bar)));
248   update_bar_revealed (self);
249   gtk_revealer_set_transition_type (self->revealer, GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
250 }
251 
252 /**
253  * adw_view_switcher_bar_new:
254  *
255  * Creates a new `AdwViewSwitcherBar`.
256  *
257  * Returns: the newly created `AdwViewSwitcherBar`
258  *
259  * Since: 1.0
260  */
261 GtkWidget *
adw_view_switcher_bar_new(void)262 adw_view_switcher_bar_new (void)
263 {
264   return g_object_new (ADW_TYPE_VIEW_SWITCHER_BAR, NULL);
265 }
266 
267 /**
268  * adw_view_switcher_bar_get_policy: (attributes org.gtk.Method.get_property=policy)
269  * @self: a `AdwViewSwitcherBar`
270  *
271  * Gets the policy of @self.
272  *
273  * Returns: the policy of @self
274  *
275  * Since: 1.0
276  */
277 AdwViewSwitcherPolicy
adw_view_switcher_bar_get_policy(AdwViewSwitcherBar * self)278 adw_view_switcher_bar_get_policy (AdwViewSwitcherBar *self)
279 {
280   g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self), ADW_VIEW_SWITCHER_POLICY_NARROW);
281 
282   return self->policy;
283 }
284 
285 /**
286  * adw_view_switcher_bar_set_policy: (attributes org.gtk.Method.set_property=policy)
287  * @self: a `AdwViewSwitcherBar`
288  * @policy: the new policy
289  *
290  * Sets the policy of @self.
291  *
292  * Since: 1.0
293  */
294 void
adw_view_switcher_bar_set_policy(AdwViewSwitcherBar * self,AdwViewSwitcherPolicy policy)295 adw_view_switcher_bar_set_policy (AdwViewSwitcherBar    *self,
296                                   AdwViewSwitcherPolicy  policy)
297 {
298   g_return_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self));
299 
300   if (self->policy == policy)
301     return;
302 
303   self->policy = policy;
304 
305   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POLICY]);
306 
307   gtk_widget_queue_resize (GTK_WIDGET (self));
308 }
309 
310 /**
311  * adw_view_switcher_bar_get_stack: (attributes org.gtk.Method.get_property=stack)
312  * @self: a `AdwViewSwitcherBar`
313  *
314  * Gets the stack controlled by @self.
315  *
316  * Returns: (nullable) (transfer none): the stack
317  *
318  * Since: 1.0
319  */
320 AdwViewStack *
adw_view_switcher_bar_get_stack(AdwViewSwitcherBar * self)321 adw_view_switcher_bar_get_stack (AdwViewSwitcherBar *self)
322 {
323   g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self), NULL);
324 
325   return adw_view_switcher_get_stack (self->view_switcher);
326 }
327 
328 /**
329  * adw_view_switcher_bar_set_stack: (attributes org.gtk.Method.set_property=stack)
330  * @self: a `AdwViewSwitcherBar`
331  * @stack: (nullable): a stack
332  *
333  * Sets the stack controlled by @self.
334  *
335  * Since: 1.0
336  */
337 void
adw_view_switcher_bar_set_stack(AdwViewSwitcherBar * self,AdwViewStack * stack)338 adw_view_switcher_bar_set_stack (AdwViewSwitcherBar *self,
339                                  AdwViewStack       *stack)
340 {
341   AdwViewStack *previous_stack;
342 
343   g_return_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self));
344   g_return_if_fail (stack == NULL || ADW_IS_VIEW_STACK (stack));
345 
346   previous_stack = adw_view_switcher_get_stack (self->view_switcher);
347 
348   if (previous_stack == stack)
349     return;
350 
351   if (previous_stack) {
352     g_signal_handlers_disconnect_by_func (self->pages, G_CALLBACK (update_bar_revealed), self);
353     g_clear_object (&self->pages);
354   }
355 
356   adw_view_switcher_set_stack (self->view_switcher, stack);
357 
358   if (stack) {
359     self->pages = adw_view_stack_get_pages (stack);
360 
361     g_signal_connect_swapped (self->pages, "items-changed", G_CALLBACK (update_bar_revealed), self);
362   }
363 
364   update_bar_revealed (self);
365 
366   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STACK]);
367 }
368 
369 /**
370  * adw_view_switcher_bar_get_reveal: (attributes org.gtk.Method.get_property=reveal)
371  * @self: a `AdwViewSwitcherBar`
372  *
373  * Gets whether @self should be revealed or hidden.
374  *
375  * Returns: whether @self is revealed
376  *
377  * Since: 1.0
378  */
379 gboolean
adw_view_switcher_bar_get_reveal(AdwViewSwitcherBar * self)380 adw_view_switcher_bar_get_reveal (AdwViewSwitcherBar *self)
381 {
382   g_return_val_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self), FALSE);
383 
384   return self->reveal;
385 }
386 
387 /**
388  * adw_view_switcher_bar_set_reveal: (attributes org.gtk.Method.set_property=reveal)
389  * @self: a `AdwViewSwitcherBar`
390  * @reveal: whether to reveal @self
391  *
392  * Sets whether @self should be revealed or hidden.
393  *
394  * Since: 1.0
395  */
396 void
adw_view_switcher_bar_set_reveal(AdwViewSwitcherBar * self,gboolean reveal)397 adw_view_switcher_bar_set_reveal (AdwViewSwitcherBar *self,
398                                   gboolean            reveal)
399 {
400   g_return_if_fail (ADW_IS_VIEW_SWITCHER_BAR (self));
401 
402   reveal = !!reveal;
403 
404   if (self->reveal == reveal)
405     return;
406 
407   self->reveal = reveal;
408   update_bar_revealed (self);
409 
410   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL]);
411 }
412