1 /*
2  * Copyright (C) 2020 Purism SPC
3  *
4  * SPDX-License-Identifier: LGPL-2.1+
5  *
6  * Author: Alexander Mikhaylenko <alexander.mikhaylenko@puri.sm>
7  */
8 
9 #include "config.h"
10 
11 #include "adw-tab-bar-private.h"
12 
13 #include "adw-bin.h"
14 #include "adw-tab-box-private.h"
15 
16 /**
17  * SECTION:adw-tab-bar
18  * @short_description: A tab bar for #AdwTabView
19  * @title: AdwTabBar
20  * @See_also: #AdwTabView
21  *
22  * The #AdwTabBar widget is a tab bar that can be used with conjunction with
23  * #AdwTabView.
24  *
25  * #AdwTabBar can autohide and can optionally contain action widgets on both
26  * sides of the tabs.
27  *
28  * When there's not enough space to show all the tabs, #AdwTabBar will scroll
29  * them. Pinned tabs always stay visible and aren't a part of the scrollable
30  * area.
31  *
32  * # CSS nodes
33  *
34  * #AdwTabBar has a single CSS node with name tabbar.
35  *
36  * Since: 1.0
37  */
38 
39 struct _AdwTabBar
40 {
41   GtkWidget parent_instance;
42 
43   GtkRevealer *revealer;
44   AdwBin *start_action_bin;
45   AdwBin *end_action_bin;
46 
47   AdwTabBox *box;
48   GtkScrolledWindow *scrolled_window;
49 
50   AdwTabBox *pinned_box;
51   GtkScrolledWindow *pinned_scrolled_window;
52 
53   AdwTabView *view;
54   gboolean autohide;
55 
56   gboolean is_overflowing;
57   gboolean resize_frozen;
58 };
59 
60 static void adw_tab_bar_buildable_init (GtkBuildableIface *iface);
61 
62 G_DEFINE_TYPE_WITH_CODE (AdwTabBar, adw_tab_bar, GTK_TYPE_WIDGET,
63                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
64                          adw_tab_bar_buildable_init))
65 
66 enum {
67   PROP_0,
68   PROP_VIEW,
69   PROP_START_ACTION_WIDGET,
70   PROP_END_ACTION_WIDGET,
71   PROP_AUTOHIDE,
72   PROP_TABS_REVEALED,
73   PROP_EXPAND_TABS,
74   PROP_INVERTED,
75   PROP_IS_OVERFLOWING,
76   LAST_PROP
77 };
78 
79 static GParamSpec *props[LAST_PROP];
80 
81 enum {
82   SIGNAL_EXTRA_DRAG_DROP,
83   SIGNAL_LAST_SIGNAL,
84 };
85 
86 static guint signals[SIGNAL_LAST_SIGNAL];
87 
88 static void
set_tabs_revealed(AdwTabBar * self,gboolean tabs_revealed)89 set_tabs_revealed (AdwTabBar *self,
90                    gboolean   tabs_revealed)
91 {
92   if (tabs_revealed == adw_tab_bar_get_tabs_revealed (self))
93     return;
94 
95   gtk_revealer_set_reveal_child (self->revealer, tabs_revealed);
96 
97   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TABS_REVEALED]);
98 }
99 
100 static void
update_autohide_cb(AdwTabBar * self)101 update_autohide_cb (AdwTabBar *self)
102 {
103   int n_tabs = 0, n_pinned_tabs = 0;
104   gboolean is_transferring_page;
105 
106   if (!self->view) {
107     set_tabs_revealed (self, FALSE);
108 
109     return;
110   }
111 
112   if (!self->autohide) {
113     set_tabs_revealed (self, TRUE);
114 
115     return;
116   }
117 
118   n_tabs = adw_tab_view_get_n_pages (self->view);
119   n_pinned_tabs = adw_tab_view_get_n_pinned_pages (self->view);
120   is_transferring_page = adw_tab_view_get_is_transferring_page (self->view);
121 
122   set_tabs_revealed (self, n_tabs > 1 || n_pinned_tabs >= 1 || is_transferring_page);
123 }
124 
125 static void
notify_selected_page_cb(AdwTabBar * self)126 notify_selected_page_cb (AdwTabBar *self)
127 {
128   AdwTabPage *page = adw_tab_view_get_selected_page (self->view);
129 
130   if (!page)
131     return;
132 
133   if (adw_tab_page_get_pinned (page)) {
134     adw_tab_box_select_page (self->pinned_box, page);
135     adw_tab_box_select_page (self->box, page);
136   } else {
137     adw_tab_box_select_page (self->box, page);
138     adw_tab_box_select_page (self->pinned_box, page);
139   }
140 }
141 
142 static void
notify_pinned_cb(AdwTabPage * page,GParamSpec * pspec,AdwTabBar * self)143 notify_pinned_cb (AdwTabPage *page,
144                   GParamSpec *pspec,
145                   AdwTabBar  *self)
146 {
147   AdwTabBox *from, *to;
148   gboolean should_focus;
149 
150   if (adw_tab_page_get_pinned (page)) {
151     from = self->box;
152     to = self->pinned_box;
153   } else {
154     from = self->pinned_box;
155     to = self->box;
156   }
157 
158   should_focus = adw_tab_box_is_page_focused (from, page);
159 
160   adw_tab_box_detach_page (from, page);
161   adw_tab_box_attach_page (to, page, adw_tab_view_get_n_pinned_pages (self->view));
162 
163   if (should_focus)
164     adw_tab_box_try_focus_selected_tab (to);
165 }
166 
167 static void
page_attached_cb(AdwTabBar * self,AdwTabPage * page,int position)168 page_attached_cb (AdwTabBar  *self,
169                   AdwTabPage *page,
170                   int         position)
171 {
172   g_signal_connect_object (page, "notify::pinned",
173                            G_CALLBACK (notify_pinned_cb), self,
174                            0);
175 }
176 
177 static void
page_detached_cb(AdwTabBar * self,AdwTabPage * page,int position)178 page_detached_cb (AdwTabBar  *self,
179                   AdwTabPage *page,
180                   int         position)
181 {
182   g_signal_handlers_disconnect_by_func (page, notify_pinned_cb, self);
183 }
184 
185 static void
update_needs_attention(AdwTabBar * self,gboolean pinned)186 update_needs_attention (AdwTabBar *self,
187                         gboolean   pinned)
188 {
189   GtkStyleContext *context;
190   gboolean left, right;
191 
192   g_object_get (pinned ? self->pinned_box : self->box,
193                 "needs-attention-left", &left,
194                 "needs-attention-right", &right,
195                 NULL);
196 
197   if (pinned)
198     context = gtk_widget_get_style_context (GTK_WIDGET (self->pinned_scrolled_window));
199   else
200     context = gtk_widget_get_style_context (GTK_WIDGET (self->scrolled_window));
201 
202   if (left)
203     gtk_style_context_add_class (context, "needs-attention-left");
204   else
205     gtk_style_context_remove_class (context, "needs-attention-left");
206 
207   if (right)
208     gtk_style_context_add_class (context, "needs-attention-right");
209   else
210     gtk_style_context_remove_class (context, "needs-attention-right");
211 }
212 
213 static void
notify_needs_attention_cb(AdwTabBar * self)214 notify_needs_attention_cb (AdwTabBar *self)
215 {
216   update_needs_attention (self, FALSE);
217 }
218 
219 static void
notify_needs_attention_pinned_cb(AdwTabBar * self)220 notify_needs_attention_pinned_cb (AdwTabBar *self)
221 {
222   update_needs_attention (self, TRUE);
223 }
224 
225 static inline gboolean
is_overflowing(GtkAdjustment * adj)226 is_overflowing (GtkAdjustment *adj)
227 {
228   double lower, upper, page_size;
229 
230   lower = gtk_adjustment_get_lower (adj);
231   upper = gtk_adjustment_get_upper (adj);
232   page_size = gtk_adjustment_get_page_size (adj);
233   return upper - lower > page_size;
234 }
235 
236 static void
update_is_overflowing(AdwTabBar * self)237 update_is_overflowing (AdwTabBar *self)
238 {
239   GtkAdjustment *adj = gtk_scrolled_window_get_hadjustment (self->scrolled_window);
240   GtkAdjustment *pinned_adj = gtk_scrolled_window_get_hadjustment (self->pinned_scrolled_window);
241   gboolean overflowing = is_overflowing (adj) || is_overflowing (pinned_adj);
242 
243   if (overflowing == self->is_overflowing)
244     return;
245 
246   overflowing |= self->resize_frozen;
247 
248   if (overflowing == self->is_overflowing)
249     return;
250 
251   self->is_overflowing = overflowing;
252 
253   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_IS_OVERFLOWING]);
254 }
255 
256 static void
notify_resize_frozen_cb(AdwTabBar * self)257 notify_resize_frozen_cb (AdwTabBar *self)
258 {
259   gboolean frozen, pinned_frozen;
260 
261   g_object_get (self->box, "resize-frozen", &frozen, NULL);
262   g_object_get (self->pinned_box, "resize-frozen", &pinned_frozen, NULL);
263 
264   self->resize_frozen = frozen || pinned_frozen;
265 
266   update_is_overflowing (self);
267 }
268 
269 static void
stop_kinetic_scrolling_cb(GtkScrolledWindow * scrolled_window)270 stop_kinetic_scrolling_cb (GtkScrolledWindow *scrolled_window)
271 {
272   /* HACK: Need to cancel kinetic scrolling. If only the built-in adjustment
273    * animation API was public, we wouldn't have to do any of this... */
274   gtk_scrolled_window_set_kinetic_scrolling (scrolled_window, FALSE);
275   gtk_scrolled_window_set_kinetic_scrolling (scrolled_window, TRUE);
276 }
277 
278 static gboolean
extra_drag_drop_cb(AdwTabBar * self,AdwTabPage * page,GValue * value)279 extra_drag_drop_cb (AdwTabBar  *self,
280                     AdwTabPage *page,
281                     GValue     *value)
282 {
283   gboolean ret = GDK_EVENT_PROPAGATE;
284 
285   g_signal_emit (self, signals[SIGNAL_EXTRA_DRAG_DROP], 0, page, value, &ret);
286 
287   return ret;
288 }
289 
290 static void
view_destroy_cb(AdwTabBar * self)291 view_destroy_cb (AdwTabBar *self)
292 {
293   adw_tab_bar_set_view (self, NULL);
294 }
295 
296 static gboolean
adw_tab_bar_focus(GtkWidget * widget,GtkDirectionType direction)297 adw_tab_bar_focus (GtkWidget        *widget,
298                    GtkDirectionType  direction)
299 {
300   AdwTabBar *self = ADW_TAB_BAR (widget);
301   gboolean is_rtl;
302   GtkDirectionType start, end;
303 
304   if (!adw_tab_bar_get_tabs_revealed (self))
305     return GDK_EVENT_PROPAGATE;
306 
307   if (!gtk_widget_get_focus_child (widget))
308     return gtk_widget_child_focus (GTK_WIDGET (self->pinned_box), direction) ||
309            gtk_widget_child_focus (GTK_WIDGET (self->box), direction);
310 
311   is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
312   start = is_rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT;
313   end = is_rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT;
314 
315   if (direction == start) {
316     if (adw_tab_view_select_previous_page (self->view))
317       return GDK_EVENT_STOP;
318 
319     return gtk_widget_keynav_failed (widget, direction);
320   }
321 
322   if (direction == end) {
323     if (adw_tab_view_select_next_page (self->view))
324       return GDK_EVENT_STOP;
325 
326     return gtk_widget_keynav_failed (widget, direction);
327   }
328 
329   return GDK_EVENT_PROPAGATE;
330 }
331 
332 static void
adw_tab_bar_dispose(GObject * object)333 adw_tab_bar_dispose (GObject *object)
334 {
335   AdwTabBar *self = ADW_TAB_BAR (object);
336 
337   adw_tab_bar_set_view (self, NULL);
338 
339   gtk_widget_unparent (GTK_WIDGET (self->revealer));
340 
341   G_OBJECT_CLASS (adw_tab_bar_parent_class)->dispose (object);
342 }
343 
344 static void
adw_tab_bar_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)345 adw_tab_bar_get_property (GObject    *object,
346                           guint       prop_id,
347                           GValue     *value,
348                           GParamSpec *pspec)
349 {
350   AdwTabBar *self = ADW_TAB_BAR (object);
351 
352   switch (prop_id) {
353   case PROP_VIEW:
354     g_value_set_object (value, adw_tab_bar_get_view (self));
355     break;
356 
357   case PROP_START_ACTION_WIDGET:
358     g_value_set_object (value, adw_tab_bar_get_start_action_widget (self));
359     break;
360 
361   case PROP_END_ACTION_WIDGET:
362     g_value_set_object (value, adw_tab_bar_get_end_action_widget (self));
363     break;
364 
365   case PROP_AUTOHIDE:
366     g_value_set_boolean (value, adw_tab_bar_get_autohide (self));
367     break;
368 
369   case PROP_TABS_REVEALED:
370     g_value_set_boolean (value, adw_tab_bar_get_tabs_revealed (self));
371     break;
372 
373   case PROP_EXPAND_TABS:
374     g_value_set_boolean (value, adw_tab_bar_get_expand_tabs (self));
375     break;
376 
377   case PROP_INVERTED:
378     g_value_set_boolean (value, adw_tab_bar_get_inverted (self));
379     break;
380 
381   case PROP_IS_OVERFLOWING:
382     g_value_set_boolean (value, adw_tab_bar_get_is_overflowing (self));
383     break;
384 
385   default:
386     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
387   }
388 }
389 
390 static void
adw_tab_bar_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)391 adw_tab_bar_set_property (GObject      *object,
392                           guint         prop_id,
393                           const GValue *value,
394                           GParamSpec   *pspec)
395 {
396   AdwTabBar *self = ADW_TAB_BAR (object);
397 
398   switch (prop_id) {
399   case PROP_VIEW:
400     adw_tab_bar_set_view (self, g_value_get_object (value));
401     break;
402 
403   case PROP_START_ACTION_WIDGET:
404     adw_tab_bar_set_start_action_widget (self, g_value_get_object (value));
405     break;
406 
407   case PROP_END_ACTION_WIDGET:
408     adw_tab_bar_set_end_action_widget (self, g_value_get_object (value));
409     break;
410 
411   case PROP_AUTOHIDE:
412     adw_tab_bar_set_autohide (self, g_value_get_boolean (value));
413     break;
414 
415   case PROP_EXPAND_TABS:
416     adw_tab_bar_set_expand_tabs (self, g_value_get_boolean (value));
417     break;
418 
419   case PROP_INVERTED:
420     adw_tab_bar_set_inverted (self, g_value_get_boolean (value));
421     break;
422 
423   default:
424     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
425   }
426 }
427 
428 static void
adw_tab_bar_class_init(AdwTabBarClass * klass)429 adw_tab_bar_class_init (AdwTabBarClass *klass)
430 {
431   GObjectClass *object_class = G_OBJECT_CLASS (klass);
432   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
433 
434   object_class->dispose = adw_tab_bar_dispose;
435   object_class->get_property = adw_tab_bar_get_property;
436   object_class->set_property = adw_tab_bar_set_property;
437 
438   widget_class->focus = adw_tab_bar_focus;
439 
440   /**
441    * AdwTabBar:view:
442    *
443    * The #AdwTabView the tab bar controls.
444    *
445    * Since: 1.0
446    */
447   props[PROP_VIEW] =
448     g_param_spec_object ("view",
449                          "View",
450                          "The view the tab bar controls.",
451                          ADW_TYPE_TAB_VIEW,
452                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
453 
454   /**
455    * AdwTabBar:start-action-widget:
456    *
457    * The widget shown before the tabs.
458    *
459    * Since: 1.0
460    */
461   props[PROP_START_ACTION_WIDGET] =
462     g_param_spec_object ("start-action-widget",
463                          "Start action widget",
464                          "The widget shown before the tabs",
465                          GTK_TYPE_WIDGET,
466                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
467 
468   /**
469    * AdwTabBar:end-action-widget:
470    *
471    * The widget shown after the tabs.
472    *
473    * Since: 1.0
474    */
475   props[PROP_END_ACTION_WIDGET] =
476     g_param_spec_object ("end-action-widget",
477                          "End action widget",
478                          "The widget shown after the tabs",
479                          GTK_TYPE_WIDGET,
480                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
481 
482   /**
483    * AdwTabBar:autohide:
484    *
485    * Whether tabs automatically hide.
486    *
487    * If set to %TRUE, the tab bar disappears when the associated #AdwTabView
488    * has 0 or 1 tab, no pinned tabs, and no tab is being transferred.
489    *
490    * See #AdwTabBar:tabs-revealed.
491    *
492    * Since: 1.0
493    */
494   props[PROP_AUTOHIDE] =
495     g_param_spec_boolean ("autohide",
496                           "Autohide",
497                           "Whether the tabs automatically hide",
498                           TRUE,
499                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
500 
501   /**
502    * AdwTabBar:tabs-revealed:
503    *
504    * Whether tabs are currently revealed.
505    *
506    * See AdwTabBar:autohide.
507    *
508    * Since: 1.0
509    */
510   props[PROP_TABS_REVEALED] =
511     g_param_spec_boolean ("tabs-revealed",
512                           "Tabs revealed",
513                           "Whether the tabs are currently revealed",
514                           FALSE,
515                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
516 
517   /**
518    * AdwTabBar:expand-tabs:
519    *
520    * Whether tabs should expand.
521    *
522    * If set to %TRUE, the tabs will always vary width filling the whole width
523    * when possible, otherwise tabs will always have the minimum possible size.
524    *
525    * Since: 1.0
526    */
527   props[PROP_EXPAND_TABS] =
528     g_param_spec_boolean ("expand-tabs",
529                           "Expand tabs",
530                           "Whether tabs expand to full width",
531                           TRUE,
532                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
533 
534   /**
535    * AdwTabBar:inverted:
536    *
537    * Whether tabs use inverted layout.
538    *
539    * If set to %TRUE, non-pinned tabs will have the close button at the
540    * beginning and the indicator at the end rather than the opposite.
541    *
542    * Since: 1.0
543    */
544   props[PROP_INVERTED] =
545     g_param_spec_boolean ("inverted",
546                           "Inverted",
547                           "Whether tabs use inverted layout",
548                           FALSE,
549                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
550 
551   /**
552    * AdwTabBar:is-overflowing:
553    *
554    * Whether the tab bar is overflowing.
555    *
556    * If set to %TRUE, all tabs cannot be displayed at once and require
557    * scrolling.
558    *
559    * Since: 1.0
560    */
561   props[PROP_IS_OVERFLOWING] =
562     g_param_spec_boolean ("is-overflowing",
563                           "Is overflowing",
564                           "Whether the tab bar is overflowing",
565                           FALSE,
566                           G_PARAM_READABLE);
567 
568   g_object_class_install_properties (object_class, LAST_PROP, props);
569 
570   /**
571    * AdwTabBar::extra-drag-drop:
572    * @self: a #AdwTabBar
573    * @page: the #AdwTabPage matching the tab the content was dropped onto
574    * @value: the #GValue being dropped
575    *
576    * This signal is emitted when content allowed via
577    * #adw_tab_bar_setup_extra_drop_target() is dropped onto a tab representing
578    * @page.
579    *
580    * See #GtkDropTarget::drop.
581    *
582    * Returns: whether the drop was accepted for the given page
583    *
584    * Since: 1.0
585    */
586   signals[SIGNAL_EXTRA_DRAG_DROP] =
587     g_signal_new ("extra-drag-drop",
588                   G_TYPE_FROM_CLASS (klass),
589                   G_SIGNAL_RUN_LAST,
590                   0,
591                   g_signal_accumulator_first_wins, NULL, NULL,
592                   G_TYPE_BOOLEAN,
593                   2,
594                   ADW_TYPE_TAB_PAGE,
595                   G_TYPE_VALUE);
596 
597   gtk_widget_class_set_template_from_resource (widget_class,
598                                                "/org/gnome/Adwaita/ui/adw-tab-bar.ui");
599   gtk_widget_class_bind_template_child (widget_class, AdwTabBar, revealer);
600   gtk_widget_class_bind_template_child (widget_class, AdwTabBar, pinned_box);
601   gtk_widget_class_bind_template_child (widget_class, AdwTabBar, box);
602   gtk_widget_class_bind_template_child (widget_class, AdwTabBar, scrolled_window);
603   gtk_widget_class_bind_template_child (widget_class, AdwTabBar, pinned_scrolled_window);
604   gtk_widget_class_bind_template_child (widget_class, AdwTabBar, start_action_bin);
605   gtk_widget_class_bind_template_child (widget_class, AdwTabBar, end_action_bin);
606   gtk_widget_class_bind_template_callback (widget_class, notify_needs_attention_cb);
607   gtk_widget_class_bind_template_callback (widget_class, notify_needs_attention_pinned_cb);
608   gtk_widget_class_bind_template_callback (widget_class, notify_resize_frozen_cb);
609   gtk_widget_class_bind_template_callback (widget_class, stop_kinetic_scrolling_cb);
610   gtk_widget_class_bind_template_callback (widget_class, extra_drag_drop_cb);
611 
612   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
613   gtk_widget_class_set_css_name (widget_class, "tabbar");
614 }
615 
616 static void
adw_tab_bar_init(AdwTabBar * self)617 adw_tab_bar_init (AdwTabBar *self)
618 {
619   GtkAdjustment *adj;
620 
621   self->autohide = TRUE;
622 
623   g_type_ensure (ADW_TYPE_TAB_BOX);
624 
625   gtk_widget_init_template (GTK_WIDGET (self));
626 
627   adj = gtk_scrolled_window_get_hadjustment (self->scrolled_window);
628   adw_tab_box_set_adjustment (self->box, adj);
629   g_signal_connect_object (adj, "changed", G_CALLBACK (update_is_overflowing),
630                            self, G_CONNECT_SWAPPED);
631 
632   adj = gtk_scrolled_window_get_hadjustment (self->pinned_scrolled_window);
633   adw_tab_box_set_adjustment (self->pinned_box, adj);
634   g_signal_connect_object (adj, "changed", G_CALLBACK (update_is_overflowing),
635                            self, G_CONNECT_SWAPPED);
636 }
637 
638 static void
adw_tab_bar_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const char * type)639 adw_tab_bar_buildable_add_child (GtkBuildable *buildable,
640                                  GtkBuilder   *builder,
641                                  GObject      *child,
642                                  const char   *type)
643 {
644   AdwTabBar *self = ADW_TAB_BAR (buildable);
645 
646   if (!self->revealer) {
647     gtk_widget_set_parent (GTK_WIDGET (child), GTK_WIDGET (self));
648 
649     return;
650   }
651 
652   if (!type || !g_strcmp0 (type, "start"))
653     adw_tab_bar_set_start_action_widget (self, GTK_WIDGET (child));
654   else if (!g_strcmp0 (type, "end"))
655     adw_tab_bar_set_end_action_widget (self, GTK_WIDGET (child));
656   else
657     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (ADW_TAB_BAR (self), type);
658 }
659 
660 static void
adw_tab_bar_buildable_init(GtkBuildableIface * iface)661 adw_tab_bar_buildable_init (GtkBuildableIface *iface)
662 {
663   iface->add_child = adw_tab_bar_buildable_add_child;
664 }
665 
666 gboolean
adw_tab_bar_tabs_have_visible_focus(AdwTabBar * self)667 adw_tab_bar_tabs_have_visible_focus (AdwTabBar *self)
668 {
669   GtkWidget *pinned_focus_child, *scroll_focus_child;
670 
671   g_return_val_if_fail (ADW_IS_TAB_BAR (self), FALSE);
672 
673   pinned_focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self->pinned_box));
674   scroll_focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self->box));
675 
676   if (pinned_focus_child && gtk_widget_has_visible_focus (pinned_focus_child))
677     return TRUE;
678 
679   if (scroll_focus_child && gtk_widget_has_visible_focus (scroll_focus_child))
680     return TRUE;
681 
682   return FALSE;
683 }
684 
685 /**
686  * adw_tab_bar_new:
687  *
688  * Creates a new #AdwTabBar widget.
689  *
690  * Returns: a new #AdwTabBar
691  *
692  * Since: 1.0
693  */
694 AdwTabBar *
adw_tab_bar_new(void)695 adw_tab_bar_new (void)
696 {
697   return g_object_new (ADW_TYPE_TAB_BAR, NULL);
698 }
699 
700 /**
701  * adw_tab_bar_get_view:
702  * @self: a #AdwTabBar
703  *
704  * Gets the #AdwTabView @self controls.
705  *
706  * Returns: (transfer none) (nullable): the #AdwTabView @self controls
707  *
708  * Since: 1.0
709  */
710 AdwTabView *
adw_tab_bar_get_view(AdwTabBar * self)711 adw_tab_bar_get_view (AdwTabBar *self)
712 {
713   g_return_val_if_fail (ADW_IS_TAB_BAR (self), NULL);
714 
715   return self->view;
716 }
717 
718 /**
719  * adw_tab_bar_set_view:
720  * @self: a #AdwTabBar
721  * @view: (nullable): a #AdwTabView
722  *
723  * Sets the #AdwTabView @self controls.
724  *
725  * Since: 1.0
726  */
727 void
adw_tab_bar_set_view(AdwTabBar * self,AdwTabView * view)728 adw_tab_bar_set_view (AdwTabBar  *self,
729                       AdwTabView *view)
730 {
731   g_return_if_fail (ADW_IS_TAB_BAR (self));
732   g_return_if_fail (ADW_IS_TAB_VIEW (view) || view == NULL);
733 
734   if (self->view == view)
735     return;
736 
737   if (self->view) {
738     int i, n;
739 
740     g_signal_handlers_disconnect_by_func (self->view, update_autohide_cb, self);
741     g_signal_handlers_disconnect_by_func (self->view, notify_selected_page_cb, self);
742     g_signal_handlers_disconnect_by_func (self->view, page_attached_cb, self);
743     g_signal_handlers_disconnect_by_func (self->view, page_detached_cb, self);
744     g_signal_handlers_disconnect_by_func (self->view, view_destroy_cb, self);
745 
746     n = adw_tab_view_get_n_pages (self->view);
747 
748     for (i = 0; i < n; i++)
749       page_detached_cb (self, adw_tab_view_get_nth_page (self->view, i), i);
750 
751     adw_tab_box_set_view (self->pinned_box, NULL);
752     adw_tab_box_set_view (self->box, NULL);
753   }
754 
755   g_set_object (&self->view, view);
756 
757   if (self->view) {
758     int i, n;
759 
760     adw_tab_box_set_view (self->pinned_box, view);
761     adw_tab_box_set_view (self->box, view);
762 
763     g_signal_connect_object (self->view, "notify::is-transferring-page",
764                              G_CALLBACK (update_autohide_cb), self,
765                              G_CONNECT_SWAPPED);
766     g_signal_connect_object (self->view, "notify::n-pages",
767                              G_CALLBACK (update_autohide_cb), self,
768                              G_CONNECT_SWAPPED);
769     g_signal_connect_object (self->view, "notify::n-pinned-pages",
770                              G_CALLBACK (update_autohide_cb), self,
771                              G_CONNECT_SWAPPED);
772     g_signal_connect_object (self->view, "notify::selected-page",
773                              G_CALLBACK (notify_selected_page_cb), self,
774                              G_CONNECT_SWAPPED);
775     g_signal_connect_object (self->view, "page-attached",
776                              G_CALLBACK (page_attached_cb), self,
777                              G_CONNECT_SWAPPED);
778     g_signal_connect_object (self->view, "page-detached",
779                              G_CALLBACK (page_detached_cb), self,
780                              G_CONNECT_SWAPPED);
781     g_signal_connect_object (self->view, "destroy",
782                              G_CALLBACK (view_destroy_cb), self,
783                              G_CONNECT_SWAPPED);
784 
785     n = adw_tab_view_get_n_pages (self->view);
786 
787     for (i = 0; i < n; i++)
788       page_attached_cb (self, adw_tab_view_get_nth_page (self->view, i), i);
789   }
790 
791   update_autohide_cb (self);
792 
793   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VIEW]);
794 }
795 
796 /**
797  * adw_tab_bar_get_start_action_widget:
798  * @self: a #AdwTabBar
799  *
800  * Gets the widget shown before the tabs.
801  *
802  * Returns: (transfer none) (nullable): the widget shown before the tabs, or %NULL
803  *
804  * Since: 1.0
805  */
806 GtkWidget *
adw_tab_bar_get_start_action_widget(AdwTabBar * self)807 adw_tab_bar_get_start_action_widget (AdwTabBar *self)
808 {
809   g_return_val_if_fail (ADW_IS_TAB_BAR (self), NULL);
810 
811   return self->start_action_bin ? adw_bin_get_child (self->start_action_bin) : NULL;
812 }
813 
814 /**
815  * adw_tab_bar_set_start_action_widget:
816  * @self: a #AdwTabBar
817  * @widget: (transfer none) (nullable): the widget to show before the tabs, or %NULL
818  *
819  * Sets the widget to show before the tabs.
820  *
821  * Since: 1.0
822  */
823 void
adw_tab_bar_set_start_action_widget(AdwTabBar * self,GtkWidget * widget)824 adw_tab_bar_set_start_action_widget (AdwTabBar *self,
825                                      GtkWidget *widget)
826 {
827   GtkWidget *old_widget;
828 
829   g_return_if_fail (ADW_IS_TAB_BAR (self));
830   g_return_if_fail (GTK_IS_WIDGET (widget) || widget == NULL);
831 
832   old_widget = adw_bin_get_child (self->start_action_bin);
833 
834   if (old_widget == widget)
835     return;
836 
837   adw_bin_set_child (self->start_action_bin, widget);
838   gtk_widget_set_visible (GTK_WIDGET (self->start_action_bin), widget != NULL);
839 
840   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_START_ACTION_WIDGET]);
841 }
842 
843 /**
844  * adw_tab_bar_get_end_action_widget:
845  * @self: a #AdwTabBar
846  *
847  * Gets the widget shown after the tabs.
848  *
849  * Returns: (transfer none) (nullable): the widget shown after the tabs, or %NULL
850  *
851  * Since: 1.0
852  */
853 GtkWidget *
adw_tab_bar_get_end_action_widget(AdwTabBar * self)854 adw_tab_bar_get_end_action_widget (AdwTabBar *self)
855 {
856   g_return_val_if_fail (ADW_IS_TAB_BAR (self), NULL);
857 
858   return self->end_action_bin ? adw_bin_get_child (self->end_action_bin) : NULL;
859 }
860 
861 /**
862  * adw_tab_bar_set_end_action_widget:
863  * @self: a #AdwTabBar
864  * @widget: (transfer none) (nullable): the widget to show after the tabs, or %NULL
865  *
866  * Sets the widget to show after the tabs.
867  *
868  * Since: 1.0
869  */
870 void
adw_tab_bar_set_end_action_widget(AdwTabBar * self,GtkWidget * widget)871 adw_tab_bar_set_end_action_widget (AdwTabBar *self,
872                                    GtkWidget *widget)
873 {
874   GtkWidget *old_widget;
875 
876   g_return_if_fail (ADW_IS_TAB_BAR (self));
877   g_return_if_fail (GTK_IS_WIDGET (widget) || widget == NULL);
878 
879   old_widget = adw_bin_get_child (self->end_action_bin);
880 
881   if (old_widget == widget)
882     return;
883 
884   adw_bin_set_child (self->end_action_bin, widget);
885   gtk_widget_set_visible (GTK_WIDGET (self->end_action_bin), widget != NULL);
886 
887   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_END_ACTION_WIDGET]);
888 }
889 
890 /**
891  * adw_tab_bar_get_autohide:
892  * @self: a #AdwTabBar
893  *
894  * Gets whether the tabs automatically hide, see adw_tab_bar_set_autohide().
895  *
896  * Returns: whether the tabs automatically hide
897  *
898  * Since: 1.0
899  */
900 gboolean
adw_tab_bar_get_autohide(AdwTabBar * self)901 adw_tab_bar_get_autohide (AdwTabBar *self)
902 {
903   g_return_val_if_fail (ADW_IS_TAB_BAR (self), FALSE);
904 
905   return self->autohide;
906 }
907 
908 /**
909  * adw_tab_bar_set_autohide:
910  * @self: a #AdwTabBar
911  * @autohide: whether the tabs automatically hide
912  *
913  * Sets whether the tabs automatically hide.
914  *
915  * If @autohide is %TRUE, the tab bar disappears when the associated #AdwTabView
916  * has 0 or 1 tab, no pinned tabs, and no tab is being transferred.
917  *
918  * Autohide is enabled by default.
919  *
920  * See #AdwTabBar:tabs-revealed.
921  *
922  * Since: 1.0
923  */
924 void
adw_tab_bar_set_autohide(AdwTabBar * self,gboolean autohide)925 adw_tab_bar_set_autohide (AdwTabBar *self,
926                           gboolean   autohide)
927 {
928   g_return_if_fail (ADW_IS_TAB_BAR (self));
929 
930   autohide = !!autohide;
931 
932   if (autohide == self->autohide)
933     return;
934 
935   self->autohide = autohide;
936 
937   update_autohide_cb (self);
938 
939   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_AUTOHIDE]);
940 }
941 
942 /**
943  * adw_tab_bar_get_tabs_revealed:
944  * @self: a #AdwTabBar
945  *
946  * Gets the value of the #AdwTabBar:tabs-revealed property.
947  *
948  * Returns: whether the tabs are current revealed
949  *
950  * Since: 1.0
951  */
952 gboolean
adw_tab_bar_get_tabs_revealed(AdwTabBar * self)953 adw_tab_bar_get_tabs_revealed (AdwTabBar *self)
954 {
955   g_return_val_if_fail (ADW_IS_TAB_BAR (self), FALSE);
956 
957   return gtk_revealer_get_reveal_child (self->revealer);
958 }
959 
960 /**
961  * adw_tab_bar_get_expand_tabs:
962  * @self: a #AdwTabBar
963  *
964  * Gets whether tabs should expand, see adw_tab_bar_set_expand_tabs().
965  *
966  * Returns: whether tabs should expand
967  *
968  * Since: 1.0
969  */
970 gboolean
adw_tab_bar_get_expand_tabs(AdwTabBar * self)971 adw_tab_bar_get_expand_tabs (AdwTabBar *self)
972 {
973   g_return_val_if_fail (ADW_IS_TAB_BAR (self), FALSE);
974 
975   return adw_tab_box_get_expand_tabs (self->box);
976 }
977 
978 /**
979  * adw_tab_bar_set_expand_tabs:
980  * @self: a #AdwTabBar
981  * @expand_tabs: whether to expand tabs
982  *
983  * Sets whether tabs should expand.
984  *
985  * If @expand_tabs is %TRUE, the tabs will always vary width filling the whole
986  * width when possible, otherwise tabs will always have the minimum possible
987  * size.
988  *
989  * Expand is enabled by default.
990  *
991  * Since: 1.0
992  */
993 void
adw_tab_bar_set_expand_tabs(AdwTabBar * self,gboolean expand_tabs)994 adw_tab_bar_set_expand_tabs (AdwTabBar *self,
995                              gboolean   expand_tabs)
996 {
997   g_return_if_fail (ADW_IS_TAB_BAR (self));
998 
999   expand_tabs = !!expand_tabs;
1000 
1001   if (adw_tab_bar_get_expand_tabs (self) == expand_tabs)
1002     return;
1003 
1004   adw_tab_box_set_expand_tabs (self->box, expand_tabs);
1005 
1006   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EXPAND_TABS]);
1007 }
1008 
1009 /**
1010  * adw_tab_bar_get_inverted:
1011  * @self: a #AdwTabBar
1012  *
1013  * Gets whether tabs use inverted layout, see adw_tab_bar_set_inverted().
1014  *
1015  * Returns: whether tabs use inverted layout
1016  *
1017  * Since: 1.0
1018  */
1019 gboolean
adw_tab_bar_get_inverted(AdwTabBar * self)1020 adw_tab_bar_get_inverted (AdwTabBar *self)
1021 {
1022   g_return_val_if_fail (ADW_IS_TAB_BAR (self), FALSE);
1023 
1024   return adw_tab_box_get_inverted (self->box);
1025 }
1026 
1027 /**
1028  * adw_tab_bar_set_inverted:
1029  * @self: a #AdwTabBar
1030  * @inverted: whether tabs use inverted layout
1031  *
1032  * Sets whether tabs tabs use inverted layout.
1033  *
1034  * If @inverted is %TRUE, non-pinned tabs will have the close button at the
1035  * beginning and the indicator at the end rather than the opposite.
1036  *
1037  * Since: 1.0
1038  */
1039 void
adw_tab_bar_set_inverted(AdwTabBar * self,gboolean inverted)1040 adw_tab_bar_set_inverted (AdwTabBar *self,
1041                           gboolean   inverted)
1042 {
1043   g_return_if_fail (ADW_IS_TAB_BAR (self));
1044 
1045   inverted = !!inverted;
1046 
1047   if (adw_tab_bar_get_inverted (self) == inverted)
1048     return;
1049 
1050   adw_tab_box_set_inverted (self->box, inverted);
1051 
1052   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INVERTED]);
1053 }
1054 
1055 /**
1056  * adw_tab_bar_setup_extra_drop_target:
1057  * @self: a #AdwTabBar
1058  * @actions: the supported actions
1059  * @types: (nullable) (transfer none) (array length=n_types):
1060  *     all supported #GTypes that can be dropped
1061  * @n_types: number of @types
1062  *
1063  * Sets the supported #GTypes for this drop target.
1064  *
1065  * Sets up an extra drop target on tabs.
1066  *
1067  * This allows to drag arbitrary content onto tabs, for example URLs in a web
1068  * browser.
1069  *
1070  * If a tab is hovered for a certain period of time while dragging the content,
1071  * it will be automatically selected.
1072  *
1073  * After content is dropped, the #AdwTabBar::extra-drag-data-received signal can
1074  * be used to retrieve and process the drag data.
1075  *
1076  * Since: 1.0
1077  */
1078 void
adw_tab_bar_setup_extra_drop_target(AdwTabBar * self,GdkDragAction actions,GType * types,gsize n_types)1079 adw_tab_bar_setup_extra_drop_target (AdwTabBar     *self,
1080                                      GdkDragAction  actions,
1081                                      GType         *types,
1082                                      gsize          n_types)
1083 {
1084   g_return_if_fail (ADW_IS_TAB_BAR (self));
1085   g_return_if_fail (n_types == 0 || types != NULL);
1086 
1087   adw_tab_box_setup_extra_drop_target (self->box, actions, types, n_types);
1088   adw_tab_box_setup_extra_drop_target (self->pinned_box, actions, types, n_types);
1089 }
1090 
1091 /**
1092  * adw_tab_bar_get_is_overflowing:
1093  * @self: a #AdwTabBar
1094  *
1095  * Gets whether @self is overflowing.
1096  *
1097  * Returns: whether @self is overflowing
1098  *
1099  * Since: 1.0
1100  */
1101 gboolean
adw_tab_bar_get_is_overflowing(AdwTabBar * self)1102 adw_tab_bar_get_is_overflowing (AdwTabBar *self)
1103 {
1104   g_return_val_if_fail (ADW_IS_TAB_BAR (self), FALSE);
1105 
1106   return self->is_overflowing;
1107 }
1108