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