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 #include <glib/gi18n-lib.h>
11 
12 #include "hdy-tab-view-private.h"
13 
14 /* FIXME replace with groups */
15 static GSList *tab_view_list;
16 
17 static const GtkTargetEntry dst_targets [] = {
18   { "HDY_TAB", GTK_TARGET_SAME_APP, 0 },
19 };
20 
21 /**
22  * SECTION:hdy-tab-view
23  * @short_description: A dynamic tabbed container
24  * @title: HdyTabView
25  * @See_also: #HdyTabBar
26  *
27  * #HdyTabView is a container which shows one child at a time. While it provides
28  * keyboard shortcuts for switching between pages, it does not provide a visible
29  * tab bar and relies on external widgets for that, such as #HdyTabBar.
30  *
31  * #HdyTabView maintains a #HdyTabPage object for each page,which holds
32  * additional per-page properties. You can obtain the #HdyTabPage for a page
33  * with hdy_tab_view_get_page(), and as return value for hdy_tab_view_append()
34  * and other functions for adding children.
35  *
36  * #HdyTabView only aims to be useful for dynamic tabs in multi-window
37  * document-based applications, such as web browsers, file managers, text
38  * editors or terminals. It does not aim to replace #GtkNotebook for use cases
39  * such as tabbed dialogs.
40  *
41  * As such, it does not support disabling page reordering or detaching, or
42  * adding children via #GtkBuilder.
43  *
44  * # CSS nodes
45  *
46  * #HdyTabView has a main CSS node with the name tabview.
47  *
48  * It contains the subnode overlay, which contains subnodes stack and widget.
49  * The stack subnode contains the added pages.
50  *
51  * |[<!-- language="plain" -->
52  * tabview
53  * ╰── overlay
54  *     ├── stack
55  *     │   ╰── [ Children ]
56  *     ╰── widget
57  * ]|
58  *
59  * Since: 1.2
60  */
61 
62 struct _HdyTabPage
63 {
64   GObject parent_instance;
65 
66   GtkWidget *child;
67   HdyTabPage *parent;
68   gboolean selected;
69   gboolean pinned;
70   gchar *title;
71   gchar *tooltip;
72   GIcon *icon;
73   gboolean loading;
74   GIcon *indicator_icon;
75   gboolean indicator_activatable;
76   gboolean needs_attention;
77 
78   gboolean closing;
79 };
80 
81 G_DEFINE_TYPE (HdyTabPage, hdy_tab_page, G_TYPE_OBJECT)
82 
83 enum {
84   PAGE_PROP_0,
85   PAGE_PROP_CHILD,
86   PAGE_PROP_PARENT,
87   PAGE_PROP_SELECTED,
88   PAGE_PROP_PINNED,
89   PAGE_PROP_TITLE,
90   PAGE_PROP_TOOLTIP,
91   PAGE_PROP_ICON,
92   PAGE_PROP_LOADING,
93   PAGE_PROP_INDICATOR_ICON,
94   PAGE_PROP_INDICATOR_ACTIVATABLE,
95   PAGE_PROP_NEEDS_ATTENTION,
96   LAST_PAGE_PROP
97 };
98 
99 static GParamSpec *page_props[LAST_PAGE_PROP];
100 
101 struct _HdyTabView
102 {
103   GtkBin parent_instance;
104 
105   GtkStack *stack;
106   GListStore *pages;
107 
108   gint n_pages;
109   gint n_pinned_pages;
110   HdyTabPage *selected_page;
111   GIcon *default_icon;
112   GMenuModel *menu_model;
113 
114   gint transfer_count;
115   GtkWidget *shortcut_widget;
116 };
117 
118 G_DEFINE_TYPE (HdyTabView, hdy_tab_view, GTK_TYPE_BIN)
119 
120 enum {
121   PROP_0,
122   PROP_N_PAGES,
123   PROP_N_PINNED_PAGES,
124   PROP_IS_TRANSFERRING_PAGE,
125   PROP_SELECTED_PAGE,
126   PROP_DEFAULT_ICON,
127   PROP_MENU_MODEL,
128   PROP_SHORTCUT_WIDGET,
129   LAST_PROP
130 };
131 
132 static GParamSpec *props[LAST_PROP];
133 
134 enum {
135   SIGNAL_PAGE_ATTACHED,
136   SIGNAL_PAGE_DETACHED,
137   SIGNAL_PAGE_REORDERED,
138   SIGNAL_CLOSE_PAGE,
139   SIGNAL_SETUP_MENU,
140   SIGNAL_CREATE_WINDOW,
141   SIGNAL_INDICATOR_ACTIVATED,
142   SIGNAL_LAST_SIGNAL,
143 };
144 
145 static guint signals[SIGNAL_LAST_SIGNAL];
146 
147 static void
set_page_selected(HdyTabPage * self,gboolean selected)148 set_page_selected (HdyTabPage *self,
149                    gboolean    selected)
150 {
151   g_return_if_fail (HDY_IS_TAB_PAGE (self));
152 
153   selected = !!selected;
154 
155   if (self->selected == selected)
156     return;
157 
158   self->selected = selected;
159 
160   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_SELECTED]);
161 }
162 
163 static void
set_page_pinned(HdyTabPage * self,gboolean pinned)164 set_page_pinned (HdyTabPage *self,
165                  gboolean    pinned)
166 {
167   g_return_if_fail (HDY_IS_TAB_PAGE (self));
168 
169   pinned = !!pinned;
170 
171   if (self->pinned == pinned)
172     return;
173 
174   self->pinned = pinned;
175 
176   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_PINNED]);
177 }
178 
179 static void set_page_parent (HdyTabPage *self,
180                              HdyTabPage *parent);
181 
182 static void
page_parent_notify_cb(HdyTabPage * self)183 page_parent_notify_cb (HdyTabPage *self)
184 {
185   HdyTabPage *grandparent = hdy_tab_page_get_parent (self->parent);
186 
187   self->parent = NULL;
188 
189   if (grandparent)
190     set_page_parent (self, grandparent);
191   else
192     g_object_notify_by_pspec (G_OBJECT (self), props[PAGE_PROP_PARENT]);
193 }
194 
195 static void
set_page_parent(HdyTabPage * self,HdyTabPage * parent)196 set_page_parent (HdyTabPage *self,
197                  HdyTabPage *parent)
198 {
199   g_return_if_fail (HDY_IS_TAB_PAGE (self));
200   g_return_if_fail (HDY_IS_TAB_PAGE (parent) || parent == NULL);
201 
202   if (self->parent == parent)
203     return;
204 
205   if (self->parent)
206     g_object_weak_unref (G_OBJECT (self->parent),
207                          (GWeakNotify) page_parent_notify_cb,
208                          self);
209 
210   self->parent = parent;
211 
212   if (self->parent)
213     g_object_weak_ref (G_OBJECT (self->parent),
214                        (GWeakNotify) page_parent_notify_cb,
215                        self);
216 
217   g_object_notify_by_pspec (G_OBJECT (self), props[PAGE_PROP_PARENT]);
218 }
219 
220 static void
hdy_tab_page_dispose(GObject * object)221 hdy_tab_page_dispose (GObject *object)
222 {
223   HdyTabPage *self = HDY_TAB_PAGE (object);
224 
225   set_page_parent (self, NULL);
226 
227   G_OBJECT_CLASS (hdy_tab_page_parent_class)->dispose (object);
228 }
229 
230 static void
hdy_tab_page_finalize(GObject * object)231 hdy_tab_page_finalize (GObject *object)
232 {
233   HdyTabPage *self = (HdyTabPage *)object;
234 
235   g_clear_object (&self->child);
236   g_clear_pointer (&self->title, g_free);
237   g_clear_pointer (&self->tooltip, g_free);
238   g_clear_object (&self->icon);
239   g_clear_object (&self->indicator_icon);
240 
241   G_OBJECT_CLASS (hdy_tab_page_parent_class)->finalize (object);
242 }
243 
244 static void
hdy_tab_page_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)245 hdy_tab_page_get_property (GObject    *object,
246                            guint       prop_id,
247                            GValue     *value,
248                            GParamSpec *pspec)
249 {
250   HdyTabPage *self = HDY_TAB_PAGE (object);
251 
252   switch (prop_id) {
253   case PAGE_PROP_CHILD:
254     g_value_set_object (value, hdy_tab_page_get_child (self));
255     break;
256 
257   case PAGE_PROP_PARENT:
258     g_value_set_object (value, hdy_tab_page_get_parent (self));
259     break;
260 
261   case PAGE_PROP_SELECTED:
262     g_value_set_boolean (value, hdy_tab_page_get_selected (self));
263     break;
264 
265   case PAGE_PROP_PINNED:
266     g_value_set_boolean (value, hdy_tab_page_get_pinned (self));
267     break;
268 
269   case PAGE_PROP_TITLE:
270     g_value_set_string (value, hdy_tab_page_get_title (self));
271     break;
272 
273   case PAGE_PROP_TOOLTIP:
274     g_value_set_string (value, hdy_tab_page_get_tooltip (self));
275     break;
276 
277   case PAGE_PROP_ICON:
278     g_value_set_object (value, hdy_tab_page_get_icon (self));
279     break;
280 
281   case PAGE_PROP_LOADING:
282     g_value_set_boolean (value, hdy_tab_page_get_loading (self));
283     break;
284 
285   case PAGE_PROP_INDICATOR_ICON:
286     g_value_set_object (value, hdy_tab_page_get_indicator_icon (self));
287     break;
288 
289   case PAGE_PROP_INDICATOR_ACTIVATABLE:
290     g_value_set_boolean (value, hdy_tab_page_get_indicator_activatable (self));
291     break;
292 
293   case PAGE_PROP_NEEDS_ATTENTION:
294     g_value_set_boolean (value, hdy_tab_page_get_needs_attention (self));
295     break;
296 
297   default:
298     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
299   }
300 }
301 
302 static void
hdy_tab_page_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)303 hdy_tab_page_set_property (GObject      *object,
304                            guint         prop_id,
305                            const GValue *value,
306                            GParamSpec   *pspec)
307 {
308   HdyTabPage *self = HDY_TAB_PAGE (object);
309 
310   switch (prop_id) {
311   case PAGE_PROP_CHILD:
312     g_set_object (&self->child, g_value_get_object (value));
313     break;
314 
315   case PAGE_PROP_PARENT:
316     set_page_parent (self, g_value_get_object (value));
317     break;
318 
319   case PAGE_PROP_TITLE:
320     hdy_tab_page_set_title (self, g_value_get_string (value));
321     break;
322 
323   case PAGE_PROP_TOOLTIP:
324     hdy_tab_page_set_tooltip (self, g_value_get_string (value));
325     break;
326 
327   case PAGE_PROP_ICON:
328     hdy_tab_page_set_icon (self, g_value_get_object (value));
329     break;
330 
331   case PAGE_PROP_LOADING:
332     hdy_tab_page_set_loading (self, g_value_get_boolean (value));
333     break;
334 
335   case PAGE_PROP_INDICATOR_ICON:
336     hdy_tab_page_set_indicator_icon (self, g_value_get_object (value));
337     break;
338 
339   case PAGE_PROP_INDICATOR_ACTIVATABLE:
340     hdy_tab_page_set_indicator_activatable (self, g_value_get_boolean (value));
341     break;
342 
343   case PAGE_PROP_NEEDS_ATTENTION:
344     hdy_tab_page_set_needs_attention (self, g_value_get_boolean (value));
345     break;
346 
347   default:
348     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
349   }
350 }
351 
352 static void
hdy_tab_page_class_init(HdyTabPageClass * klass)353 hdy_tab_page_class_init (HdyTabPageClass *klass)
354 {
355   GObjectClass *object_class = G_OBJECT_CLASS (klass);
356 
357   object_class->dispose = hdy_tab_page_dispose;
358   object_class->finalize = hdy_tab_page_finalize;
359   object_class->get_property = hdy_tab_page_get_property;
360   object_class->set_property = hdy_tab_page_set_property;
361 
362   /**
363    * HdyTabPage:child:
364    *
365    * The child of the page.
366    *
367    * Since: 1.2
368    */
369   page_props[PAGE_PROP_CHILD] =
370     g_param_spec_object ("child",
371                          _("Child"),
372                          _("The child of the page"),
373                          GTK_TYPE_WIDGET,
374                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
375 
376   /**
377    * HdyTabPage:parent:
378    *
379    * The parent page of the page.
380    *
381    * See hdy_tab_view_add_page() and hdy_tab_view_close_page().
382 
383    * Since: 1.2
384    */
385   page_props[PAGE_PROP_PARENT] =
386     g_param_spec_object ("parent",
387                          _("Parent"),
388                          _("The parent page of the page"),
389                          HDY_TYPE_TAB_PAGE,
390                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
391 
392   /**
393    * HdyTabPage:selected:
394    *
395    * Whether the page is selected.
396    *
397    * Since: 1.2
398    */
399   page_props[PAGE_PROP_SELECTED] =
400     g_param_spec_boolean ("selected",
401                          _("Selected"),
402                          _("Whether the page is selected"),
403                          FALSE,
404                          G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
405 
406   /**
407    * HdyTabPage:pinned:
408    *
409    * Whether the page is pinned. See hdy_tab_view_set_page_pinned().
410    *
411    * Since: 1.2
412    */
413   page_props[PAGE_PROP_PINNED] =
414     g_param_spec_boolean ("pinned",
415                          _("Pinned"),
416                          _("Whether the page is pinned"),
417                          FALSE,
418                          G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
419 
420   /**
421    * HdyTabPage:title:
422    *
423    * The title of the page.
424    *
425    * #HdyTabBar will display it in the center of the tab unless it's pinned,
426    * and will use it as a tooltip unless #HdyTabPage:tooltip is set.
427    *
428    * Since: 1.2
429    */
430   page_props[PAGE_PROP_TITLE] =
431     g_param_spec_string ("title",
432                          _("Title"),
433                          _("The title of the page"),
434                          NULL,
435                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
436 
437   /**
438    * HdyTabPage:tooltip:
439    *
440    * The tooltip of the page, marked up with the Pango text markup language.
441    *
442    * If not set, #HdyTabBar will use #HdyTabPage:title as a tooltip instead.
443    *
444    * Since: 1.2
445    */
446   page_props[PAGE_PROP_TOOLTIP] =
447     g_param_spec_string ("tooltip",
448                          _("Tooltip"),
449                          _("The tooltip of the page"),
450                          NULL,
451                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
452 
453   /**
454    * HdyTabPage:icon:
455    *
456    * The icon of the page, displayed next to the title.
457    *
458    * #HdyTabBar will not show the icon if #HdyTabPage:loading is set to %TRUE,
459    * or if the page is pinned and #HdyTabPage:indicator-icon is set.
460    *
461    * Since: 1.2
462    */
463   page_props[PAGE_PROP_ICON] =
464     g_param_spec_object ("icon",
465                          _("Icon"),
466                          _("The icon of the page"),
467                          G_TYPE_ICON,
468                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
469 
470   /**
471    * HdyTabPage:loading:
472    *
473    * Whether the page is loading.
474    *
475    * If set to %TRUE, #HdyTabBar will display a spinner in place of icon.
476    *
477    * If the page is pinned and #HdyTabPage:indicator-icon is set, the loading
478    * status will not be visible.
479    *
480    * Since: 1.2
481    */
482   page_props[PAGE_PROP_LOADING] =
483     g_param_spec_boolean ("loading",
484                          _("Loading"),
485                          _("Whether the page is loading"),
486                          FALSE,
487                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
488 
489   /**
490    * HdyTabPage:indicator-icon:
491    *
492    * An indicator icon for the page.
493    *
494    * A common use case is an audio or camera indicator in a web browser.
495    *
496    * #HdyTabPage will show it at the beginning of the tab, alongside icon
497    * representing #HdyTabPage:icon or loading spinner.
498    *
499    * If the page is pinned, the indicator will be shown instead of icon or
500    * spinner.
501    *
502    * If #HdyTabPage:indicator-activatable is set to %TRUE, the indicator icon
503    * can act as a button.
504    *
505    * Since: 1.2
506    */
507   page_props[PAGE_PROP_INDICATOR_ICON] =
508     g_param_spec_object ("indicator-icon",
509                          _("Indicator icon"),
510                          _("An indicator icon for the page"),
511                          G_TYPE_ICON,
512                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
513 
514   /**
515    * HdyTabPage:indicator-activatable:
516    *
517    * Whether the indicator icon is activatable.
518    *
519    * If set to %TRUE, #HdyTabView::indicator-activated will be emitted when
520    * the indicator icon is clicked.
521    *
522    * If #HdyTabPage:indicator-icon is not set, does nothing.
523    *
524    * Since: 1.2
525    */
526   page_props[PAGE_PROP_INDICATOR_ACTIVATABLE] =
527     g_param_spec_boolean ("indicator-activatable",
528                          _("Indicator activatable"),
529                          _("Whether the indicator icon is activatable"),
530                          FALSE,
531                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
532 
533   /**
534    * HdyTabPage:needs-attention:
535    *
536    * Whether the page needs attention.
537    *
538    * #HdyTabBar will display a glow under the tab representing the page if set
539    * to %TRUE. If the tab is not visible, the corresponding edge of the tab bar
540    * will be highlighted.
541    *
542    * Since: 1.2
543    */
544   page_props[PAGE_PROP_NEEDS_ATTENTION] =
545     g_param_spec_boolean ("needs-attention",
546                          _("Needs attention"),
547                          _("Whether the page needs attention"),
548                          FALSE,
549                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
550 
551   g_object_class_install_properties (object_class, LAST_PAGE_PROP, page_props);
552 }
553 
554 static void
hdy_tab_page_init(HdyTabPage * self)555 hdy_tab_page_init (HdyTabPage *self)
556 {
557 }
558 
559 static gboolean
object_handled_accumulator(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer data)560 object_handled_accumulator (GSignalInvocationHint *ihint,
561                             GValue                *return_accu,
562                             const GValue          *handler_return,
563                             gpointer               data)
564 {
565   GObject *object = g_value_get_object (handler_return);
566 
567   g_value_set_object (return_accu, object);
568 
569   return !object;
570 }
571 
572 static void
begin_transfer_for_group(HdyTabView * self)573 begin_transfer_for_group (HdyTabView *self)
574 {
575   GSList *l;
576 
577   for (l = tab_view_list; l; l = l->next) {
578     HdyTabView *view = l->data;
579 
580     view->transfer_count++;
581 
582     if (view->transfer_count == 1)
583       g_object_notify_by_pspec (G_OBJECT (view), props[PROP_IS_TRANSFERRING_PAGE]);
584   }
585 }
586 
587 static void
end_transfer_for_group(HdyTabView * self)588 end_transfer_for_group (HdyTabView *self)
589 {
590   GSList *l;
591 
592   for (l = tab_view_list; l; l = l->next) {
593     HdyTabView *view = l->data;
594 
595     view->transfer_count--;
596 
597     if (view->transfer_count == 0)
598       g_object_notify_by_pspec (G_OBJECT (view), props[PROP_IS_TRANSFERRING_PAGE]);
599   }
600 }
601 
602 static void
set_n_pages(HdyTabView * self,gint n_pages)603 set_n_pages (HdyTabView *self,
604              gint        n_pages)
605 {
606   if (n_pages == self->n_pages)
607     return;
608 
609   self->n_pages = n_pages;
610 
611   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_PAGES]);
612 }
613 
614 static void
set_n_pinned_pages(HdyTabView * self,gint n_pinned_pages)615 set_n_pinned_pages (HdyTabView *self,
616                     gint        n_pinned_pages)
617 {
618   if (n_pinned_pages == self->n_pinned_pages)
619     return;
620 
621   self->n_pinned_pages = n_pinned_pages;
622 
623   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_PINNED_PAGES]);
624 }
625 
626 static inline gboolean
page_belongs_to_this_view(HdyTabView * self,HdyTabPage * page)627 page_belongs_to_this_view (HdyTabView *self,
628                            HdyTabPage *page)
629 {
630   if (!page)
631     return FALSE;
632 
633   return gtk_widget_get_parent (page->child) == GTK_WIDGET (self->stack);
634 }
635 
636 static inline gboolean
is_descendant_of(HdyTabPage * page,HdyTabPage * parent)637 is_descendant_of (HdyTabPage *page,
638                   HdyTabPage *parent)
639 {
640   while (page && page != parent)
641     page = hdy_tab_page_get_parent (page);
642 
643   return page == parent;
644 }
645 
646 static void
attach_page(HdyTabView * self,HdyTabPage * page,gint position)647 attach_page (HdyTabView *self,
648              HdyTabPage *page,
649              gint        position)
650 {
651   GtkWidget *child = hdy_tab_page_get_child (page);
652   HdyTabPage *parent;
653 
654   g_list_store_insert (self->pages, position, page);
655 
656   gtk_container_add (GTK_CONTAINER (self->stack), child);
657   gtk_container_child_set (GTK_CONTAINER (self->stack), child,
658                            "position", position,
659                            NULL);
660 
661   g_object_freeze_notify (G_OBJECT (self));
662 
663   set_n_pages (self, self->n_pages + 1);
664 
665   if (hdy_tab_page_get_pinned (page))
666     set_n_pinned_pages (self, self->n_pinned_pages + 1);
667 
668   g_object_thaw_notify (G_OBJECT (self));
669 
670   parent = hdy_tab_page_get_parent (page);
671 
672   if (parent && !page_belongs_to_this_view (self, parent))
673     set_page_parent (page, NULL);
674 
675   g_signal_emit (self, signals[SIGNAL_PAGE_ATTACHED], 0, page, position);
676 }
677 
678 static void
select_previous_page(HdyTabView * self,HdyTabPage * page)679 select_previous_page (HdyTabView *self,
680                       HdyTabPage *page)
681 {
682   gint pos = hdy_tab_view_get_page_position (self, page);
683   HdyTabPage *parent;
684 
685   if (page != self->selected_page)
686     return;
687 
688   parent = hdy_tab_page_get_parent (page);
689 
690   if (parent && pos > 0) {
691     HdyTabPage *prev_page = hdy_tab_view_get_nth_page (self, pos - 1);
692 
693     /* This usually means we opened a few pages from the same page in a row, or
694      * the previous page is the parent. Switch there. */
695     if (is_descendant_of (prev_page, parent)) {
696       hdy_tab_view_set_selected_page (self, prev_page);
697 
698       return;
699     }
700 
701     /* Pinned pages are special in that opening a page from a pinned parent
702      * will place it not directly after the parent, but after the last pinned
703      * page. This means that if we're closing the first non-pinned page, we need
704      * to jump to the parent directly instead of the previous page which might
705      * be different. */
706     if (hdy_tab_page_get_pinned (prev_page) &&
707         hdy_tab_page_get_pinned (parent)) {
708       hdy_tab_view_set_selected_page (self, parent);
709 
710       return;
711     }
712   }
713 
714   if (hdy_tab_view_select_next_page (self))
715     return;
716 
717   hdy_tab_view_select_previous_page (self);
718 }
719 
720 static void
detach_page(HdyTabView * self,HdyTabPage * page)721 detach_page (HdyTabView *self,
722              HdyTabPage *page)
723 {
724   gint pos = hdy_tab_view_get_page_position (self, page);
725   GtkWidget *child;
726 
727   select_previous_page (self, page);
728 
729   child = hdy_tab_page_get_child (page);
730 
731   g_object_ref (page);
732   g_object_ref (child);
733 
734   g_list_store_remove (self->pages, pos);
735 
736   g_object_freeze_notify (G_OBJECT (self));
737 
738   set_n_pages (self, self->n_pages - 1);
739 
740   if (hdy_tab_page_get_pinned (page))
741     set_n_pinned_pages (self, self->n_pinned_pages - 1);
742 
743   if (self->n_pages == 0)
744     hdy_tab_view_set_selected_page (self, NULL);
745 
746   g_object_thaw_notify (G_OBJECT (self));
747 
748   gtk_container_remove (GTK_CONTAINER (self->stack), child);
749 
750   g_signal_emit (self, signals[SIGNAL_PAGE_DETACHED], 0, page, pos);
751 
752   g_object_unref (child);
753   g_object_unref (page);
754 }
755 
756 static HdyTabPage *
insert_page(HdyTabView * self,GtkWidget * child,HdyTabPage * parent,gint position,gboolean pinned)757 insert_page (HdyTabView *self,
758              GtkWidget  *child,
759              HdyTabPage *parent,
760              gint        position,
761              gboolean    pinned)
762 {
763   g_autoptr (HdyTabPage) page =
764     g_object_new (HDY_TYPE_TAB_PAGE,
765                   "child", child,
766                   "parent", parent,
767                   NULL);
768 
769   set_page_pinned (page, pinned);
770 
771   attach_page (self, page, position);
772 
773   if (!self->selected_page)
774     hdy_tab_view_set_selected_page (self, page);
775 
776   return page;
777 }
778 
779 static gboolean
close_page_cb(HdyTabView * self,HdyTabPage * page)780 close_page_cb (HdyTabView *self,
781                HdyTabPage *page)
782 {
783   hdy_tab_view_close_page_finish (self, page,
784                                   !hdy_tab_page_get_pinned (page));
785 
786   return GDK_EVENT_STOP;
787 }
788 
789 static gboolean
select_page(HdyTabView * self,GtkDirectionType direction,gboolean last)790 select_page (HdyTabView       *self,
791              GtkDirectionType  direction,
792              gboolean          last)
793 {
794   gboolean is_rtl, success = last;
795 
796   if (!self->selected_page)
797     return FALSE;
798 
799   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
800 
801   if (direction == GTK_DIR_LEFT)
802     direction = is_rtl ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD;
803   else if (direction == GTK_DIR_RIGHT)
804     direction = is_rtl ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
805 
806   if (direction == GTK_DIR_TAB_BACKWARD) {
807     if (last)
808       success = hdy_tab_view_select_first_page (self);
809     else
810       success = hdy_tab_view_select_previous_page (self);
811   } else if (direction == GTK_DIR_TAB_FORWARD) {
812     if (last)
813       success = hdy_tab_view_select_last_page (self);
814     else
815       success = hdy_tab_view_select_next_page (self);
816   }
817 
818   gtk_widget_grab_focus (hdy_tab_page_get_child (self->selected_page));
819 
820   return success;
821 }
822 
823 static gboolean
reorder_page(HdyTabView * self,GtkDirectionType direction,gboolean last)824 reorder_page (HdyTabView       *self,
825               GtkDirectionType  direction,
826               gboolean          last)
827 {
828   gboolean is_rtl, success = last;
829 
830   if (!self->selected_page)
831     return FALSE;
832 
833   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
834 
835   if (direction == GTK_DIR_LEFT)
836     direction = is_rtl ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD;
837   else if (direction == GTK_DIR_RIGHT)
838     direction = is_rtl ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
839 
840   if (direction == GTK_DIR_TAB_BACKWARD) {
841     if (last)
842       success = hdy_tab_view_reorder_first (self, self->selected_page);
843     else
844       success = hdy_tab_view_reorder_backward (self, self->selected_page);
845   } else if (direction == GTK_DIR_TAB_FORWARD) {
846     if (last)
847       success = hdy_tab_view_reorder_last (self, self->selected_page);
848     else
849       success = hdy_tab_view_reorder_forward (self, self->selected_page);
850   }
851 
852   return success;
853 }
854 
855 static inline gboolean
handle_select_reorder_shortcuts(HdyTabView * self,guint keyval,GdkModifierType state,guint keysym,GtkDirectionType direction,gboolean last)856 handle_select_reorder_shortcuts (HdyTabView       *self,
857                                  guint             keyval,
858                                  GdkModifierType   state,
859                                  guint             keysym,
860                                  GtkDirectionType  direction,
861                                  gboolean          last)
862 {
863   /* All keypad keysyms are aligned at the same order as non-keypad ones */
864   guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
865   gboolean success = FALSE;
866 
867   if (keyval != keysym && keyval != keypad_keysym)
868     return GDK_EVENT_PROPAGATE;
869 
870   if (state == GDK_CONTROL_MASK)
871     success = select_page (self, direction, last);
872   else if (state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
873     success = reorder_page (self, direction, last);
874   else
875     return GDK_EVENT_PROPAGATE;
876 
877   if (!success)
878     gtk_widget_error_bell (GTK_WIDGET (self));
879 
880   return GDK_EVENT_STOP;
881 }
882 
883 static gboolean
shortcut_key_press_cb(HdyTabView * self,GdkEventKey * event,GtkWidget * widget)884 shortcut_key_press_cb (HdyTabView  *self,
885                        GdkEventKey *event,
886                        GtkWidget   *widget)
887 {
888   GdkModifierType default_modifiers = gtk_accelerator_get_default_mod_mask ();
889   guint keyval;
890   GdkModifierType state;
891   GdkModifierType consumed;
892   GdkKeymap *keymap;
893   gint i;
894 
895   gdk_event_get_state ((GdkEvent *) event, &state);
896 
897   keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
898 
899   gdk_keymap_translate_keyboard_state (keymap,
900                                        event->hardware_keycode,
901                                        state,
902                                        event->group,
903                                        &keyval, NULL, NULL, &consumed);
904 
905   state &= ~consumed & default_modifiers;
906 
907   if (handle_select_reorder_shortcuts (self, keyval, state, GDK_KEY_Page_Up,
908                                        GTK_DIR_TAB_BACKWARD, FALSE))
909     return GDK_EVENT_STOP;
910 
911   if (handle_select_reorder_shortcuts (self, keyval, state, GDK_KEY_Page_Down,
912                                        GTK_DIR_TAB_FORWARD, FALSE))
913     return GDK_EVENT_STOP;
914 
915   if (handle_select_reorder_shortcuts (self, keyval, state, GDK_KEY_Home,
916                                        GTK_DIR_TAB_BACKWARD, TRUE))
917     return GDK_EVENT_STOP;
918 
919   if (handle_select_reorder_shortcuts (self, keyval, state, GDK_KEY_End,
920                                        GTK_DIR_TAB_FORWARD, TRUE))
921     return GDK_EVENT_STOP;
922 
923   if (keyval == GDK_KEY_Tab ||
924       keyval == GDK_KEY_KP_Tab ||
925       keyval == GDK_KEY_ISO_Left_Tab) {
926     if (keyval == GDK_KEY_ISO_Left_Tab &&
927         state == GDK_CONTROL_MASK)
928       state = GDK_CONTROL_MASK | GDK_SHIFT_MASK;
929 
930     if (state == GDK_CONTROL_MASK) {
931       if (!hdy_tab_view_select_next_page (self)) {
932         HdyTabPage *page = hdy_tab_view_get_nth_page (self, 0);
933 
934         hdy_tab_view_set_selected_page (self, page);
935       }
936 
937       return GDK_EVENT_STOP;
938     }
939 
940     if (state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) {
941       if (!hdy_tab_view_select_previous_page (self)) {
942         HdyTabPage *page = hdy_tab_view_get_nth_page (self, self->n_pages - 1);
943 
944         hdy_tab_view_set_selected_page (self, page);
945       }
946 
947       return GDK_EVENT_STOP;
948     }
949   }
950 
951   for (i = 0; i <= 9; i++) {
952     if ((keyval == GDK_KEY_0 + i || keyval == GDK_KEY_KP_0 + i) &&
953         state == GDK_MOD1_MASK) {
954       gint n_page = (i + 9) % 10; /* Alt+0 means page 10, not 0 */
955       HdyTabPage *page;
956 
957       if (n_page >= self->n_pages)
958         return GDK_EVENT_PROPAGATE;
959 
960       page = hdy_tab_view_get_nth_page (self, n_page);
961       hdy_tab_view_set_selected_page (self, page);
962 
963       return GDK_EVENT_STOP;
964     }
965   }
966 
967   return GDK_EVENT_PROPAGATE;
968 }
969 
970 static void
shortcut_widget_notify_cb(HdyTabView * self)971 shortcut_widget_notify_cb (HdyTabView *self)
972 {
973   g_signal_handlers_disconnect_by_func (self->shortcut_widget,
974                                         shortcut_key_press_cb, self);
975 
976   self->shortcut_widget = NULL;
977 
978   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHORTCUT_WIDGET]);
979 }
980 
981 static void
hdy_tab_view_dispose(GObject * object)982 hdy_tab_view_dispose (GObject *object)
983 {
984   HdyTabView *self = HDY_TAB_VIEW (object);
985 
986   hdy_tab_view_set_shortcut_widget (self, NULL);
987 
988   while (self->n_pages) {
989     HdyTabPage *page = hdy_tab_view_get_nth_page (self, 0);
990 
991     detach_page (self, page);
992   }
993 
994   g_clear_object (&self->pages);
995 
996   G_OBJECT_CLASS (hdy_tab_view_parent_class)->dispose (object);
997 }
998 
999 static void
hdy_tab_view_finalize(GObject * object)1000 hdy_tab_view_finalize (GObject *object)
1001 {
1002   HdyTabView *self = (HdyTabView *) object;
1003 
1004   g_clear_object (&self->default_icon);
1005   g_clear_object (&self->menu_model);
1006 
1007   tab_view_list = g_slist_remove (tab_view_list, self);
1008 
1009   G_OBJECT_CLASS (hdy_tab_view_parent_class)->finalize (object);
1010 }
1011 
1012 static void
hdy_tab_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1013 hdy_tab_view_get_property (GObject    *object,
1014                            guint       prop_id,
1015                            GValue     *value,
1016                            GParamSpec *pspec)
1017 {
1018   HdyTabView *self = HDY_TAB_VIEW (object);
1019 
1020   switch (prop_id) {
1021   case PROP_N_PAGES:
1022     g_value_set_int (value, hdy_tab_view_get_n_pages (self));
1023     break;
1024 
1025   case PROP_N_PINNED_PAGES:
1026     g_value_set_int (value, hdy_tab_view_get_n_pinned_pages (self));
1027     break;
1028 
1029   case PROP_IS_TRANSFERRING_PAGE:
1030     g_value_set_boolean (value, hdy_tab_view_get_is_transferring_page (self));
1031     break;
1032 
1033   case PROP_SELECTED_PAGE:
1034     g_value_set_object (value, hdy_tab_view_get_selected_page (self));
1035     break;
1036 
1037   case PROP_DEFAULT_ICON:
1038     g_value_set_object (value, hdy_tab_view_get_default_icon (self));
1039     break;
1040 
1041   case PROP_MENU_MODEL:
1042     g_value_set_object (value, hdy_tab_view_get_menu_model (self));
1043     break;
1044 
1045   case PROP_SHORTCUT_WIDGET:
1046     g_value_set_object (value, hdy_tab_view_get_shortcut_widget (self));
1047     break;
1048 
1049   default:
1050     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1051   }
1052 }
1053 
1054 static void
hdy_tab_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1055 hdy_tab_view_set_property (GObject      *object,
1056                            guint         prop_id,
1057                            const GValue *value,
1058                            GParamSpec   *pspec)
1059 {
1060   HdyTabView *self = HDY_TAB_VIEW (object);
1061 
1062   switch (prop_id) {
1063   case PROP_SELECTED_PAGE:
1064     hdy_tab_view_set_selected_page (self, g_value_get_object (value));
1065     break;
1066 
1067   case PROP_DEFAULT_ICON:
1068     hdy_tab_view_set_default_icon (self, g_value_get_object (value));
1069     break;
1070 
1071   case PROP_MENU_MODEL:
1072     hdy_tab_view_set_menu_model (self, g_value_get_object (value));
1073     break;
1074 
1075   case PROP_SHORTCUT_WIDGET:
1076     hdy_tab_view_set_shortcut_widget (self, g_value_get_object (value));
1077     break;
1078 
1079   default:
1080     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1081   }
1082 }
1083 
1084 static void
hdy_tab_view_class_init(HdyTabViewClass * klass)1085 hdy_tab_view_class_init (HdyTabViewClass *klass)
1086 {
1087   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1088   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1089 
1090   object_class->dispose = hdy_tab_view_dispose;
1091   object_class->finalize = hdy_tab_view_finalize;
1092   object_class->get_property = hdy_tab_view_get_property;
1093   object_class->set_property = hdy_tab_view_set_property;
1094 
1095   /**
1096    * HdyTabView:n-pages:
1097    *
1098    * The number of pages in the tab view.
1099    *
1100    * Since: 1.2
1101    */
1102   props[PROP_N_PAGES] =
1103     g_param_spec_int ("n-pages",
1104                       _("Number of pages"),
1105                       _("The number of pages in the tab view"),
1106                       0, G_MAXINT, 0,
1107                       G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
1108 
1109   /**
1110    * HdyTabView:n-pinned-pages:
1111    *
1112    * The number of pinned pages in the tab view.
1113    *
1114    * See hdy_tab_view_set_page_pinned().
1115    *
1116    * Since: 1.2
1117    */
1118   props[PROP_N_PINNED_PAGES] =
1119     g_param_spec_int ("n-pinned-pages",
1120                       _("Number of pinned pages"),
1121                       _("The number of pinned pages in the tab view"),
1122                       0, G_MAXINT, 0,
1123                       G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
1124 
1125   /**
1126    * HdyTabView:is-transferring-page:
1127    *
1128    * Whether a page is being transferred.
1129    *
1130    * This property will be set to %TRUE when a drag-n-drop tab transfer starts
1131    * on any #HdyTabView, and to %FALSE after it ends.
1132    *
1133    * During the transfer, children cannot receive pointer input and a tab can
1134    * be safely dropped on the tab view.
1135    *
1136    * Since: 1.2
1137    */
1138   props[PROP_IS_TRANSFERRING_PAGE] =
1139     g_param_spec_boolean ("is-transferring-page",
1140                           _("Is transferring page"),
1141                           _("Whether a page is being transferred"),
1142                           FALSE,
1143                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
1144 
1145   /**
1146    * HdyTabView:selected-page:
1147    *
1148    * The currently selected page.
1149    *
1150    * Since: 1.2
1151    */
1152   props[PROP_SELECTED_PAGE] =
1153     g_param_spec_object ("selected-page",
1154                          _("Selected page"),
1155                          _("The currently selected page"),
1156                          HDY_TYPE_TAB_PAGE,
1157                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1158 
1159   /**
1160    * HdyTabView:default-icon:
1161    *
1162    * Default page icon.
1163    *
1164    * If a page doesn't provide its own icon via #HdyTabPage:icon, default icon
1165    * may be used instead for contexts where having an icon is necessary.
1166    *
1167    * #HdyTabBar will use default icon for pinned tabs in case the page is not
1168    * loading, doesn't have an icon and an indicator. Default icon is never used
1169    * for tabs that aren't pinned.
1170    *
1171    * Since: 1.2
1172    */
1173   props[PROP_DEFAULT_ICON] =
1174     g_param_spec_object ("default-icon",
1175                          _("Default icon"),
1176                          _("Default page icon"),
1177                          G_TYPE_ICON,
1178                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1179 
1180   /**
1181    * HdyTabView:menu-model:
1182    *
1183    * Tab context menu model.
1184    *
1185    * When a context menu is shown for a tab, it will be constructed from the
1186    * provided menu model. Use #HdyTabView::setup-menu signal to set up the menu
1187    * actions for the particular tab.
1188    *
1189    * Since: 1.2
1190    */
1191   props[PROP_MENU_MODEL] =
1192     g_param_spec_object ("menu-model",
1193                          _("Menu model"),
1194                          _("Tab context menu model"),
1195                          G_TYPE_MENU_MODEL,
1196                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1197 
1198   /**
1199    * HdyTabView:shortcut-widget:
1200    *
1201    * Tab shortcut widget, has the following shortcuts:
1202    * * Ctrl+Page Up - switch to the previous page
1203    * * Ctrl+Page Down - switch to the next page
1204    * * Ctrl+Home - switch to the first page
1205    * * Ctrl+End - switch to the last page
1206    * * Ctrl+Shift+Page Up - move the current page backward
1207    * * Ctrl+Shift+Page Down - move the current page forward
1208    * * Ctrl+Shift+Home - move the current page at the start
1209    * * Ctrl+Shift+End - move the current page at the end
1210    * * Ctrl+Tab - switch to the next page, with looping
1211    * * Ctrl+Shift+Tab - switch to the previous page, with looping
1212    * * Alt+1-9 - switch to pages 1-9
1213    * * Alt+0 - switch to page 10
1214    *
1215    * These shortcuts are always available on @self, this property is useful if
1216    * they should be available globally.
1217    *
1218    * Since: 1.2
1219    */
1220   props[PROP_SHORTCUT_WIDGET] =
1221     g_param_spec_object ("shortcut-widget",
1222                          _("Shortcut widget"),
1223                          _("Tab shortcut widget"),
1224                          GTK_TYPE_WIDGET,
1225                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1226 
1227   g_object_class_install_properties (object_class, LAST_PROP, props);
1228 
1229   /**
1230    * HdyTabView::page-attached:
1231    * @self: a #HdyTabView
1232    * @page: a page of @self
1233    * @position: the position of the page, starting from 0
1234    *
1235    * This signal is emitted when a page has been created or transferred to
1236    * @self.
1237    *
1238    * A typical reason to connect to this signal would be to connect to page
1239    * signals for things such as updating window title.
1240    *
1241    * Since: 1.2
1242    */
1243   signals[SIGNAL_PAGE_ATTACHED] =
1244     g_signal_new ("page-attached",
1245                   G_TYPE_FROM_CLASS (klass),
1246                   G_SIGNAL_RUN_LAST,
1247                   0,
1248                   NULL, NULL, NULL,
1249                   G_TYPE_NONE,
1250                   2,
1251                   HDY_TYPE_TAB_PAGE, G_TYPE_INT);
1252 
1253   /**
1254    * HdyTabView::page-detached:
1255    * @self: a #HdyTabView
1256    * @page: a page of @self
1257    * @position: the position of the removed page, starting from 0
1258    *
1259    * This signal is emitted when a page has been removed or transferred to
1260    * another view.
1261    *
1262    * A typical reason to connect to this signal would be to disconnect signal
1263    * handlers connected in the #HdyTabView::page-attached handler.
1264    *
1265    * It is important not to try and destroy the page child in the handler of
1266    * this function as the child might merely be moved to another window; use
1267    * child dispose handler for that or do it in sync with your
1268    * hdy_tab_view_close_page_finish() calls.
1269    *
1270    * Since: 1.2
1271    */
1272   signals[SIGNAL_PAGE_DETACHED] =
1273     g_signal_new ("page-detached",
1274                   G_TYPE_FROM_CLASS (klass),
1275                   G_SIGNAL_RUN_LAST,
1276                   0,
1277                   NULL, NULL, NULL,
1278                   G_TYPE_NONE,
1279                   2,
1280                   HDY_TYPE_TAB_PAGE, G_TYPE_INT);
1281 
1282   /**
1283    * HdyTabView::page-reordered:
1284    * @self: a #HdyTabView
1285    * @page: a page of @self
1286    * @position: the position @page was moved to, starting at 0
1287    *
1288    * This signal is emitted after @page has been reordered to @position.
1289    *
1290    * Since: 1.2
1291    */
1292   signals[SIGNAL_PAGE_REORDERED] =
1293     g_signal_new ("page-reordered",
1294                   G_TYPE_FROM_CLASS (klass),
1295                   G_SIGNAL_RUN_LAST,
1296                   0,
1297                   NULL, NULL, NULL,
1298                   G_TYPE_NONE,
1299                   2,
1300                   HDY_TYPE_TAB_PAGE, G_TYPE_INT);
1301 
1302   /**
1303    * HdyTabView::close-page:
1304    * @self: a #HdyTabView
1305    * @page: a page of @self
1306    *
1307    * This signal is emitted after hdy_tab_view_close_page() has been called for
1308    * @page.
1309    *
1310    * The handler is expected to call hdy_tab_view_close_page_finish() to confirm
1311    * or reject the closing.
1312    *
1313    * The default handler will immediately confirm closing for non-pinned pages,
1314    * or reject it for pinned pages, equivalent to the following example:
1315    *
1316    * |[<!-- language="C" -->
1317    * static gboolean
1318    * close_page_cb (HdyTabView *view,
1319    *                HdyTabPage *page,
1320    *                gpointer    user_data)
1321    * {
1322    *   hdy_tab_view_close_page_finish (view, page, !hdy_tab_page_get_pinned (page));
1323    *
1324    *   return GDK_EVENT_STOP;
1325    * }
1326    * ]|
1327    *
1328    * The hdy_tab_view_close_page_finish() doesn't have to happen during the
1329    * handler, so can be used to do asynchronous checks before confirming the
1330    * closing.
1331    *
1332    * A typical reason to connect to this signal is to show a confirmation dialog
1333    * for closing a tab.
1334    *
1335    * Since: 1.2
1336    */
1337   signals[SIGNAL_CLOSE_PAGE] =
1338     g_signal_new ("close-page",
1339                   G_TYPE_FROM_CLASS (klass),
1340                   G_SIGNAL_RUN_LAST,
1341                   0,
1342                   g_signal_accumulator_true_handled,
1343                   NULL, NULL,
1344                   G_TYPE_BOOLEAN,
1345                   1,
1346                   HDY_TYPE_TAB_PAGE);
1347 
1348   /**
1349    * HdyTabView::setup-menu:
1350    * @self: a #HdyTabView
1351    * @page: a page of @self, or %NULL
1352    *
1353    * This signal is emitted before a context menu is opened for @page, and after
1354    * it's closed, in the latter case the @page will be set to %NULL.
1355    *
1356    * It can be used to set up menu actions before showing the menu, for example
1357    * disable actions not applicable to @page.
1358    *
1359    * Since: 1.2
1360    */
1361   signals[SIGNAL_SETUP_MENU] =
1362     g_signal_new ("setup-menu",
1363                   G_TYPE_FROM_CLASS (klass),
1364                   G_SIGNAL_RUN_LAST,
1365                   0,
1366                   NULL, NULL, NULL,
1367                   G_TYPE_NONE,
1368                   1,
1369                   HDY_TYPE_TAB_PAGE);
1370 
1371   /**
1372    * HdyTabView::create-window:
1373    * @self: a #HdyTabView
1374    *
1375    * This signal is emitted when a tab is dropped onto desktop and should be
1376    * transferred into a new window.
1377    *
1378    * The signal handler is expected to create a new window, position it as
1379    * needed and return its #HdyTabView that the page will be transferred into.
1380    *
1381    * Returns: (transfer none) (nullable): the #HdyTabView from the new window
1382    *
1383    * Since: 1.2
1384    */
1385   signals[SIGNAL_CREATE_WINDOW] =
1386     g_signal_new ("create-window",
1387                   G_TYPE_FROM_CLASS (klass),
1388                   G_SIGNAL_RUN_LAST,
1389                   0,
1390                   object_handled_accumulator,
1391                   NULL, NULL,
1392                   HDY_TYPE_TAB_VIEW,
1393                   0);
1394 
1395   /**
1396    * HdyTabView::indicator-activated:
1397    * @self: a #HdyTabView
1398    * @page: a page of @self
1399    *
1400    * This signal is emitted after the indicator icon on @page has been activated.
1401    *
1402    * See #HdyTabPage:indicator-icon and #HdyTabPage:indicator-activatable.
1403    *
1404    * Since: 1.2
1405    */
1406   signals[SIGNAL_INDICATOR_ACTIVATED] =
1407     g_signal_new ("indicator-activated",
1408                   G_TYPE_FROM_CLASS (klass),
1409                   G_SIGNAL_RUN_LAST,
1410                   0,
1411                   NULL, NULL, NULL,
1412                   G_TYPE_NONE,
1413                   1,
1414                   HDY_TYPE_TAB_PAGE);
1415 
1416   g_signal_override_class_handler ("close-page",
1417                                    G_TYPE_FROM_CLASS (klass),
1418                                    G_CALLBACK (close_page_cb));
1419 
1420   gtk_widget_class_set_css_name (widget_class, "tabview");
1421 }
1422 
1423 static void
hdy_tab_view_init(HdyTabView * self)1424 hdy_tab_view_init (HdyTabView *self)
1425 {
1426   GtkWidget *overlay, *drag_shield;
1427 
1428   self->pages = g_list_store_new (HDY_TYPE_TAB_PAGE);
1429   self->default_icon = G_ICON (g_themed_icon_new ("hdy-tab-icon-missing-symbolic"));
1430 
1431   overlay = gtk_overlay_new ();
1432   gtk_widget_show (overlay);
1433   gtk_container_add (GTK_CONTAINER (self), overlay);
1434 
1435   self->stack = GTK_STACK (gtk_stack_new ());
1436   gtk_widget_show (GTK_WIDGET (self->stack));
1437   gtk_container_add (GTK_CONTAINER (overlay), GTK_WIDGET (self->stack));
1438 
1439   drag_shield = gtk_event_box_new ();
1440   gtk_widget_set_no_show_all (drag_shield, TRUE);
1441   gtk_widget_add_events (drag_shield, GDK_ALL_EVENTS_MASK);
1442   gtk_overlay_add_overlay (GTK_OVERLAY (overlay), drag_shield);
1443 
1444   g_object_bind_property (self, "is-transferring-page",
1445                           drag_shield, "visible",
1446                           G_BINDING_DEFAULT);
1447 
1448   gtk_drag_dest_set (GTK_WIDGET (self),
1449                      GTK_DEST_DEFAULT_MOTION,
1450                      dst_targets,
1451                      G_N_ELEMENTS (dst_targets),
1452                      GDK_ACTION_MOVE);
1453 
1454   tab_view_list = g_slist_prepend (tab_view_list, self);
1455 
1456   g_signal_connect_object (self, "key-press-event",
1457                            G_CALLBACK (shortcut_key_press_cb), self,
1458                            G_CONNECT_SWAPPED);
1459 }
1460 
1461 /**
1462  * hdy_tab_page_get_child:
1463  * @self: a #HdyTabPage
1464  *
1465  * Gets the child of @self.
1466  *
1467  * Returns: (transfer none): the child of @self
1468  *
1469  * Since: 1.2
1470  */
1471 GtkWidget *
hdy_tab_page_get_child(HdyTabPage * self)1472 hdy_tab_page_get_child (HdyTabPage *self)
1473 {
1474   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), NULL);
1475 
1476   return self->child;
1477 }
1478 
1479 /**
1480  * hdy_tab_page_get_parent:
1481  * @self: a #HdyTabPage
1482  *
1483  * Gets the parent page of @self, or %NULL if the @self does not have a parent.
1484  *
1485  * See hdy_tab_view_add_page() and hdy_tab_view_close_page().
1486  *
1487  * Returns: (transfer none) (nullable): the parent page of @self, or %NULL
1488  *
1489  * Since: 1.2
1490  */
1491 HdyTabPage *
hdy_tab_page_get_parent(HdyTabPage * self)1492 hdy_tab_page_get_parent (HdyTabPage *self)
1493 {
1494   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), NULL);
1495 
1496   return self->parent;
1497 }
1498 
1499 /**
1500  * hdy_tab_page_get_selected:
1501  * @self: a #HdyTabPage
1502  *
1503  * Gets whether @self is selected. See hdy_tab_view_set_selected_page().
1504  *
1505  * Returns: whether @self is selected
1506  *
1507  * Since: 1.2
1508  */
1509 gboolean
hdy_tab_page_get_selected(HdyTabPage * self)1510 hdy_tab_page_get_selected (HdyTabPage *self)
1511 {
1512   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), FALSE);
1513 
1514   return self->selected;
1515 }
1516 
1517 /**
1518  * hdy_tab_page_get_pinned:
1519  * @self: a #HdyTabPage
1520  *
1521  * Gets whether @self is pinned. See hdy_tab_view_set_page_pinned().
1522  *
1523  * Returns: whether @self is pinned
1524  *
1525  * Since: 1.2
1526  */
1527 gboolean
hdy_tab_page_get_pinned(HdyTabPage * self)1528 hdy_tab_page_get_pinned (HdyTabPage *self)
1529 {
1530   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), FALSE);
1531 
1532   return self->pinned;
1533 }
1534 
1535 /**
1536  * hdy_tab_page_get_title:
1537  * @self: a #HdyTabPage
1538  *
1539  * Gets the title of @self, see hdy_tab_page_set_title().
1540  *
1541  * Returns: (nullable): the title of @self
1542  *
1543  * Since: 1.2
1544  */
1545 const gchar *
hdy_tab_page_get_title(HdyTabPage * self)1546 hdy_tab_page_get_title (HdyTabPage *self)
1547 {
1548   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), NULL);
1549 
1550   return self->title;
1551 }
1552 
1553 /**
1554  * hdy_tab_page_set_title:
1555  * @self: a #HdyTabPage
1556  * @title: (nullable): the title of @self
1557  *
1558  * Sets the title of @self.
1559  *
1560  * #HdyTabBar will display it in the center of the tab representing @self
1561  * unless it's pinned, and will use it as a tooltip unless #HdyTabPage:tooltip
1562  * is set.
1563  *
1564  * Since: 1.2
1565  */
1566 void
hdy_tab_page_set_title(HdyTabPage * self,const gchar * title)1567 hdy_tab_page_set_title (HdyTabPage  *self,
1568                         const gchar *title)
1569 {
1570   g_return_if_fail (HDY_IS_TAB_PAGE (self));
1571 
1572   if (!g_strcmp0 (title, self->title))
1573     return;
1574 
1575   g_clear_pointer (&self->title, g_free);
1576   self->title = g_strdup (title);
1577 
1578   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_TITLE]);
1579 }
1580 
1581 /**
1582  * hdy_tab_page_get_tooltip:
1583  * @self: a #HdyTabPage
1584  *
1585  * Gets the tooltip of @self, see hdy_tab_page_set_tooltip().
1586  *
1587  * Returns: (nullable): the tooltip of @self
1588  *
1589  * Since: 1.2
1590  */
1591 const gchar *
hdy_tab_page_get_tooltip(HdyTabPage * self)1592 hdy_tab_page_get_tooltip (HdyTabPage *self)
1593 {
1594   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), NULL);
1595 
1596   return self->tooltip;
1597 }
1598 
1599 /**
1600  * hdy_tab_page_set_tooltip:
1601  * @self: a #HdyTabPage
1602  * @tooltip: (nullable): the tooltip of @self
1603  *
1604  * Sets the tooltip of @self, marked up with the Pango text markup language.
1605  *
1606  * If not set, #HdyTabBar will use #HdyTabPage:title as a tooltip instead.
1607  *
1608  * Since: 1.2
1609  */
1610 void
hdy_tab_page_set_tooltip(HdyTabPage * self,const gchar * tooltip)1611 hdy_tab_page_set_tooltip (HdyTabPage  *self,
1612                           const gchar *tooltip)
1613 {
1614   g_return_if_fail (HDY_IS_TAB_PAGE (self));
1615 
1616   if (!g_strcmp0 (tooltip, self->tooltip))
1617     return;
1618 
1619   g_clear_pointer (&self->tooltip, g_free);
1620   self->tooltip = g_strdup (tooltip);
1621 
1622   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_TOOLTIP]);
1623 }
1624 
1625 /**
1626  * hdy_tab_page_get_icon:
1627  * @self: a #HdyTabPage
1628  *
1629  * Gets the icon of @self, see hdy_tab_page_set_icon().
1630  *
1631  * Returns: (transfer none) (nullable): the icon of @self
1632  *
1633  * Since: 1.2
1634  */
1635 GIcon *
hdy_tab_page_get_icon(HdyTabPage * self)1636 hdy_tab_page_get_icon (HdyTabPage *self)
1637 {
1638   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), NULL);
1639 
1640   return self->icon;
1641 }
1642 
1643 /**
1644  * hdy_tab_page_set_icon:
1645  * @self: a #HdyTabPage
1646  * @icon: (nullable): the icon of @self
1647  *
1648  * Sets the icon of @self, displayed next to the title.
1649  *
1650  * #HdyTabBar will not show the icon if #HdyTabPage:loading is set to %TRUE,
1651  * or if @self is pinned and #HdyTabPage:indicator-icon is set.
1652  *
1653  * Since: 1.2
1654  */
1655 void
hdy_tab_page_set_icon(HdyTabPage * self,GIcon * icon)1656 hdy_tab_page_set_icon (HdyTabPage *self,
1657                        GIcon      *icon)
1658 {
1659   g_return_if_fail (HDY_IS_TAB_PAGE (self));
1660   g_return_if_fail (G_IS_ICON (icon) || icon == NULL);
1661 
1662   if (self->icon == icon)
1663     return;
1664 
1665   g_set_object (&self->icon, icon);
1666 
1667   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_ICON]);
1668 }
1669 
1670 /**
1671  * hdy_tab_page_get_loading:
1672  * @self: a #HdyTabPage
1673  *
1674  * Gets whether @self is loading, see hdy_tab_page_set_loading().
1675  *
1676  * Returns: whether @self is loading
1677  *
1678  * Since: 1.2
1679  */
1680 gboolean
hdy_tab_page_get_loading(HdyTabPage * self)1681 hdy_tab_page_get_loading (HdyTabPage *self)
1682 {
1683   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), FALSE);
1684 
1685   return self->loading;
1686 }
1687 
1688 /**
1689  * hdy_tab_page_set_loading:
1690  * @self: a #HdyTabPage
1691  * @loading: whether @self is loading
1692  *
1693  * Sets wether @self is loading.
1694  *
1695  * If set to %TRUE, #HdyTabBar will display a spinner in place of icon.
1696  *
1697  * If @self is pinned and #HdyTabPage:indicator-icon is set, the loading status
1698  * will not be visible.
1699  *
1700  * Since: 1.2
1701  */
1702 void
hdy_tab_page_set_loading(HdyTabPage * self,gboolean loading)1703 hdy_tab_page_set_loading (HdyTabPage *self,
1704                           gboolean    loading)
1705 {
1706   g_return_if_fail (HDY_IS_TAB_PAGE (self));
1707 
1708   loading = !!loading;
1709 
1710   if (self->loading == loading)
1711     return;
1712 
1713   self->loading = loading;
1714 
1715   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_LOADING]);
1716 }
1717 
1718 /**
1719  * hdy_tab_page_get_indicator_icon:
1720  * @self: a #HdyTabPage
1721  *
1722  * Gets the indicator icon of @self, see hdy_tab_page_set_indicator_icon().
1723  *
1724  * Returns: (transfer none) (nullable): the indicator icon of @self
1725  *
1726  * Since: 1.2
1727  */
1728 GIcon *
hdy_tab_page_get_indicator_icon(HdyTabPage * self)1729 hdy_tab_page_get_indicator_icon (HdyTabPage *self)
1730 {
1731   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), NULL);
1732 
1733   return self->indicator_icon;
1734 }
1735 
1736 /**
1737  * hdy_tab_page_set_indicator_icon:
1738  * @self: a #HdyTabPage
1739  * @indicator_icon: (nullable): the indicator icon of @self
1740  *
1741  * Sets the indicator icon of @self.
1742  *
1743  * A common use case is an audio or camera indicator in a web browser.
1744  *
1745  * #HdyTabPage will show it at the beginning of the tab, alongside icon
1746  * representing #HdyTabPage:icon or loading spinner.
1747  *
1748  * If the page is pinned, the indicator will be shown instead of icon or spinner.
1749  *
1750  * If #HdyTabPage:indicator-activatable is set to %TRUE, indicator icon
1751  * can act as a button.
1752  *
1753  * Since: 1.2
1754  */
1755 void
hdy_tab_page_set_indicator_icon(HdyTabPage * self,GIcon * indicator_icon)1756 hdy_tab_page_set_indicator_icon (HdyTabPage *self,
1757                                  GIcon      *indicator_icon)
1758 {
1759   g_return_if_fail (HDY_IS_TAB_PAGE (self));
1760   g_return_if_fail (G_IS_ICON (indicator_icon) || indicator_icon == NULL);
1761 
1762   if (self->indicator_icon == indicator_icon)
1763     return;
1764 
1765   g_set_object (&self->indicator_icon, indicator_icon);
1766 
1767   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_INDICATOR_ICON]);
1768 }
1769 
1770 /**
1771  * hdy_tab_page_get_indicator_activatable:
1772  * @self: a #HdyTabPage
1773  *
1774  *
1775  * Gets whether the indicator of @self is activatable, see
1776  * hdy_tab_page_set_indicator_activatable().
1777  *
1778  * Returns: whether the indicator is activatable
1779  *
1780  * Since: 1.2
1781  */
1782 gboolean
hdy_tab_page_get_indicator_activatable(HdyTabPage * self)1783 hdy_tab_page_get_indicator_activatable (HdyTabPage *self)
1784 {
1785   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), FALSE);
1786 
1787   return self->indicator_activatable;
1788 }
1789 
1790 /**
1791  * hdy_tab_page_set_indicator_activatable:
1792  * @self: a #HdyTabPage
1793  * @activatable: whether the indicator is activatable
1794  *
1795  * sets whether the indicator of @self is activatable.
1796  *
1797  * If set to %TRUE, #HdyTabView::indicator-activated will be emitted when
1798  * the indicator is clicked.
1799  *
1800  * If #HdyTabPage:indicator-icon is not set, does nothing.
1801  *
1802  * Since: 1.2
1803  */
1804 void
hdy_tab_page_set_indicator_activatable(HdyTabPage * self,gboolean activatable)1805 hdy_tab_page_set_indicator_activatable (HdyTabPage *self,
1806                                         gboolean    activatable)
1807 {
1808   g_return_if_fail (HDY_IS_TAB_PAGE (self));
1809 
1810   activatable = !!activatable;
1811 
1812   if (self->indicator_activatable == activatable)
1813     return;
1814 
1815   self->indicator_activatable = activatable;
1816 
1817   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_INDICATOR_ACTIVATABLE]);
1818 }
1819 
1820 /**
1821  * hdy_tab_page_get_needs_attention:
1822  * @self: a #HdyTabPage
1823  *
1824  * Gets whether @self needs attention, see hdy_tab_page_set_needs_attention().
1825  *
1826  * Returns: whether @self needs attention
1827  *
1828  * Since: 1.2
1829  */
1830 gboolean
hdy_tab_page_get_needs_attention(HdyTabPage * self)1831 hdy_tab_page_get_needs_attention (HdyTabPage *self)
1832 {
1833   g_return_val_if_fail (HDY_IS_TAB_PAGE (self), FALSE);
1834 
1835   return self->needs_attention;
1836 }
1837 
1838 /**
1839  * hdy_tab_page_set_needs_attention:
1840  * @self: a #HdyTabPage
1841  * @needs_attention: whether @self needs attention
1842  *
1843  * Sets whether @self needs attention.
1844  *
1845  * #HdyTabBar will display a glow under the tab representing @self if set to
1846  * %TRUE. If the tab is not visible, the corresponding edge of the tab bar will
1847  * be highlighted.
1848  *
1849  * Since: 1.2
1850  */
1851 void
hdy_tab_page_set_needs_attention(HdyTabPage * self,gboolean needs_attention)1852 hdy_tab_page_set_needs_attention (HdyTabPage *self,
1853                                   gboolean    needs_attention)
1854 {
1855   g_return_if_fail (HDY_IS_TAB_PAGE (self));
1856 
1857   needs_attention = !!needs_attention;
1858 
1859   if (self->needs_attention == needs_attention)
1860     return;
1861 
1862   self->needs_attention = needs_attention;
1863 
1864   g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_NEEDS_ATTENTION]);
1865 }
1866 
1867 /**
1868  * hdy_tab_view_new:
1869  *
1870  * Creates a new #HdyTabView widget.
1871  *
1872  * Returns: a new #HdyTabView
1873  *
1874  * Since: 1.2
1875  */
1876 HdyTabView *
hdy_tab_view_new(void)1877 hdy_tab_view_new (void)
1878 {
1879   return g_object_new (HDY_TYPE_TAB_VIEW, NULL);
1880 }
1881 
1882 /**
1883  * hdy_tab_view_get_n_pages:
1884  * @self: a #HdyTabView
1885  *
1886  * Gets the number of pages in @self.
1887  *
1888  * Returns: the number of pages in @self
1889  *
1890  * Since: 1.2
1891  */
1892 gint
hdy_tab_view_get_n_pages(HdyTabView * self)1893 hdy_tab_view_get_n_pages (HdyTabView *self)
1894 {
1895   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), 0);
1896 
1897   return self->n_pages;
1898 }
1899 
1900 /**
1901  * hdy_tab_view_get_n_pinned_pages:
1902  * @self: a #HdyTabView
1903  *
1904  * Gets the number of pinned pages in @self.
1905  *
1906  * See hdy_tab_view_set_page_pinned().
1907  *
1908  * Returns: the number of pinned pages in @self
1909  *
1910  * Since: 1.2
1911  */
1912 gint
hdy_tab_view_get_n_pinned_pages(HdyTabView * self)1913 hdy_tab_view_get_n_pinned_pages (HdyTabView *self)
1914 {
1915   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), 0);
1916 
1917   return self->n_pinned_pages;
1918 }
1919 
1920 /**
1921  * hdy_tab_view_get_is_transferring_page:
1922  * @self: a #HdyTabView
1923  *
1924  * Whether a page is being transferred.
1925  *
1926  * Gets the value of #HdyTabView:is-transferring-page property.
1927  *
1928  * Returns: whether a page is being transferred
1929  *
1930  * Since: 1.2
1931  */
1932 gboolean
hdy_tab_view_get_is_transferring_page(HdyTabView * self)1933 hdy_tab_view_get_is_transferring_page (HdyTabView *self)
1934 {
1935   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
1936 
1937   return self->transfer_count > 0;
1938 }
1939 
1940 /**
1941  * hdy_tab_view_get_selected_page:
1942  * @self: a #HdyTabView
1943  *
1944  * Gets the currently selected page in @self.
1945  *
1946  * Returns: (transfer none) (nullable): the selected page in @self
1947  *
1948  * Since: 1.2
1949  */
1950 HdyTabPage *
hdy_tab_view_get_selected_page(HdyTabView * self)1951 hdy_tab_view_get_selected_page (HdyTabView *self)
1952 {
1953   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
1954 
1955   return self->selected_page;
1956 }
1957 
1958 /**
1959  * hdy_tab_view_set_selected_page:
1960  * @self: a #HdyTabView
1961  * @selected_page: a page in @self
1962  *
1963  * Sets the currently selected page in @self.
1964  *
1965  * Since: 1.2
1966  */
1967 void
hdy_tab_view_set_selected_page(HdyTabView * self,HdyTabPage * selected_page)1968 hdy_tab_view_set_selected_page (HdyTabView *self,
1969                                 HdyTabPage *selected_page)
1970 {
1971   g_return_if_fail (HDY_IS_TAB_VIEW (self));
1972 
1973   if (self->n_pages > 0) {
1974     g_return_if_fail (HDY_IS_TAB_PAGE (selected_page));
1975     g_return_if_fail (page_belongs_to_this_view (self, selected_page));
1976   } else {
1977     g_return_if_fail (selected_page == NULL);
1978   }
1979 
1980   if (self->selected_page == selected_page)
1981     return;
1982 
1983   if (self->selected_page)
1984     set_page_selected (self->selected_page, FALSE);
1985 
1986   self->selected_page = selected_page;
1987 
1988   if (self->selected_page) {
1989     gtk_stack_set_visible_child (self->stack,
1990                                  hdy_tab_page_get_child (selected_page));
1991     set_page_selected (self->selected_page, TRUE);
1992   }
1993 
1994   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_PAGE]);
1995 }
1996 
1997 /**
1998  * hdy_tab_view_select_previous_page:
1999  * @self: a #HdyTabView
2000  *
2001  * Selects the page before the currently selected page.
2002  *
2003  * If the first page was already selected, this function does nothing.
2004  *
2005  * Returns: %TRUE if the selected page was changed, %FALSE otherwise
2006  *
2007  * Since: 1.2
2008  */
2009 gboolean
hdy_tab_view_select_previous_page(HdyTabView * self)2010 hdy_tab_view_select_previous_page (HdyTabView *self)
2011 {
2012   HdyTabPage *page;
2013   gint pos;
2014 
2015   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2016 
2017   if (!self->selected_page)
2018     return FALSE;
2019 
2020   pos = hdy_tab_view_get_page_position (self, self->selected_page);
2021 
2022   if (pos <= 0)
2023     return FALSE;
2024 
2025   page = hdy_tab_view_get_nth_page (self, pos - 1);
2026 
2027   hdy_tab_view_set_selected_page (self, page);
2028 
2029   return TRUE;
2030 }
2031 
2032 /**
2033  * hdy_tab_view_select_next_page:
2034  * @self: a #HdyTabView
2035  *
2036  * Selects the page after the currently selected page.
2037  *
2038  * If the last page was already selected, this function does nothing.
2039  *
2040  * Returns: %TRUE if the selected page was changed, %FALSE otherwise
2041  *
2042  * Since: 1.2
2043  */
2044 gboolean
hdy_tab_view_select_next_page(HdyTabView * self)2045 hdy_tab_view_select_next_page (HdyTabView *self)
2046 {
2047   HdyTabPage *page;
2048   gint pos;
2049 
2050   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2051 
2052   if (!self->selected_page)
2053     return FALSE;
2054 
2055   pos = hdy_tab_view_get_page_position (self, self->selected_page);
2056 
2057   if (pos >= self->n_pages - 1)
2058     return FALSE;
2059 
2060   page = hdy_tab_view_get_nth_page (self, pos + 1);
2061 
2062   hdy_tab_view_set_selected_page (self, page);
2063 
2064   return TRUE;
2065 }
2066 
2067 gboolean
hdy_tab_view_select_first_page(HdyTabView * self)2068 hdy_tab_view_select_first_page (HdyTabView *self)
2069 {
2070   HdyTabPage *page;
2071   gint pos;
2072   gboolean pinned;
2073 
2074   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2075 
2076   if (!self->selected_page)
2077     return FALSE;
2078 
2079   pinned = hdy_tab_page_get_pinned (self->selected_page);
2080   pos = pinned ? 0 : self->n_pinned_pages;
2081 
2082   page = hdy_tab_view_get_nth_page (self, pos);
2083 
2084   /* If we're on the first non-pinned tab, go to the first pinned tab */
2085   if (page == self->selected_page && !pinned)
2086     page = hdy_tab_view_get_nth_page (self, 0);
2087 
2088   if (page == self->selected_page)
2089     return FALSE;
2090 
2091   hdy_tab_view_set_selected_page (self, page);
2092 
2093   return TRUE;
2094 }
2095 
2096 gboolean
hdy_tab_view_select_last_page(HdyTabView * self)2097 hdy_tab_view_select_last_page (HdyTabView *self)
2098 {
2099   HdyTabPage *page;
2100   gint pos;
2101   gboolean pinned;
2102 
2103   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2104 
2105   if (!self->selected_page)
2106     return FALSE;
2107 
2108   pinned = hdy_tab_page_get_pinned (self->selected_page);
2109   pos = (pinned ? self->n_pinned_pages : self->n_pages) - 1;
2110 
2111   page = hdy_tab_view_get_nth_page (self, pos);
2112 
2113   /* If we're on the last pinned tab, go to the last non-pinned tab */
2114   if (page == self->selected_page && pinned)
2115     page = hdy_tab_view_get_nth_page (self, self->n_pages - 1);
2116 
2117   if (page == self->selected_page)
2118     return FALSE;
2119 
2120   hdy_tab_view_set_selected_page (self, page);
2121 
2122   return TRUE;
2123 }
2124 
2125 /**
2126  * hdy_tab_view_get_default_icon:
2127  * @self: a #HdyTabView
2128  *
2129  * Gets default icon of @self, see hdy_tab_view_set_default_icon().
2130  *
2131  * Returns: (transfer none): the default icon of @self.
2132  *
2133  * Since: 1.2
2134  */
2135 GIcon *
hdy_tab_view_get_default_icon(HdyTabView * self)2136 hdy_tab_view_get_default_icon (HdyTabView *self)
2137 {
2138   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2139 
2140   return self->default_icon;
2141 }
2142 
2143 /**
2144  * hdy_tab_view_set_default_icon:
2145  * @self: a #HdyTabView
2146  * @default_icon: the default icon
2147  *
2148  * Sets default page icon for @self.
2149  *
2150  * If a page doesn't provide its own icon via #HdyTabPage:icon, default icon
2151  * may be used instead for contexts where having an icon is necessary.
2152  *
2153  * #HdyTabBar will use default icon for pinned tabs in case the page is not
2154  * loading, doesn't have an icon and an indicator. Default icon is never used
2155  * for tabs that aren't pinned.
2156  *
2157  * By default, 'hdy-tab-icon-missing-symbolic' icon is used.
2158  *
2159  * Since: 1.2
2160  */
2161 void
hdy_tab_view_set_default_icon(HdyTabView * self,GIcon * default_icon)2162 hdy_tab_view_set_default_icon (HdyTabView *self,
2163                                GIcon      *default_icon)
2164 {
2165   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2166   g_return_if_fail (G_IS_ICON (default_icon));
2167 
2168   if (self->default_icon == default_icon)
2169     return;
2170 
2171   g_set_object (&self->default_icon, default_icon);
2172 
2173   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEFAULT_ICON]);
2174 }
2175 
2176 /**
2177  * hdy_tab_view_get_menu_model:
2178  * @self: a #HdyTabView
2179  *
2180  * Gets the tab context menu model for @self, see hdy_tab_view_set_menu_model().
2181  *
2182  * Returns: (transfer none) (nullable): the tab context menu model for @self
2183  *
2184  * Since: 1.2
2185  */
2186 GMenuModel *
hdy_tab_view_get_menu_model(HdyTabView * self)2187 hdy_tab_view_get_menu_model (HdyTabView *self)
2188 {
2189   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2190 
2191   return self->menu_model;
2192 }
2193 
2194 /**
2195  * hdy_tab_view_set_menu_model:
2196  * @self: a #HdyTabView
2197  * @menu_model: (nullable): a menu model
2198  *
2199  * Sets the tab context menu model for @self.
2200  *
2201  * When a context menu is shown for a tab, it will be constructed from the
2202  * provided menu model. Use #HdyTabView::setup-menu signal to set up the menu
2203  * actions for the particular tab.
2204  *
2205  * Since: 1.2
2206  */
2207 void
hdy_tab_view_set_menu_model(HdyTabView * self,GMenuModel * menu_model)2208 hdy_tab_view_set_menu_model (HdyTabView *self,
2209                              GMenuModel *menu_model)
2210 {
2211   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2212   g_return_if_fail (G_IS_MENU_MODEL (menu_model));
2213 
2214   if (self->menu_model == menu_model)
2215     return;
2216 
2217   g_set_object (&self->menu_model, menu_model);
2218 
2219   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MENU_MODEL]);
2220 }
2221 
2222 /**
2223  * hdy_tab_view_get_shortcut_widget:
2224  * @self: a #HdyTabView
2225  *
2226  * Gets the shortcut widget for @self, see hdy_tab_view_set_shortcut_widget().
2227  *
2228  * Returns: (transfer none) (nullable): the shortcut widget for @self
2229  *
2230  * Since: 1.2
2231  */
2232 GtkWidget *
hdy_tab_view_get_shortcut_widget(HdyTabView * self)2233 hdy_tab_view_get_shortcut_widget (HdyTabView *self)
2234 {
2235   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2236 
2237   return self->shortcut_widget;
2238 }
2239 
2240 /**
2241  * hdy_tab_view_set_shortcut_widget:
2242  * @self: a #HdyTabView
2243  * @widget: (nullable): a shortcut widget
2244  *
2245  * Sets the shortcut widget for @self.
2246  *
2247  * Registers the following shortcuts on @widget:
2248  * * Ctrl+Page Up - switch to the previous page
2249  * * Ctrl+Page Down - switch to the next page
2250  * * Ctrl+Home - switch to the first page
2251  * * Ctrl+End - switch to the last page
2252  * * Ctrl+Shift+Page Up - move the current page backward
2253  * * Ctrl+Shift+Page Down - move the current page forward
2254  * * Ctrl+Shift+Home - move the current page at the start
2255  * * Ctrl+Shift+End - move the current page at the end
2256  * * Ctrl+Tab - switch to the next page, with looping
2257  * * Ctrl+Shift+Tab - switch to the previous page, with looping
2258  * * Alt+1-9 - switch to pages 1-9
2259  * * Alt+0 - switch to page 10
2260  *
2261  * These shortcuts are always available on @self, this function is useful if
2262  * they should be available globally.
2263  *
2264  * Since: 1.2
2265  */
2266 void
hdy_tab_view_set_shortcut_widget(HdyTabView * self,GtkWidget * widget)2267 hdy_tab_view_set_shortcut_widget (HdyTabView *self,
2268                                   GtkWidget  *widget)
2269 {
2270   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2271   g_return_if_fail (GTK_IS_WIDGET (widget) || widget == NULL);
2272 
2273   if (widget == self->shortcut_widget)
2274     return;
2275 
2276   if (self->shortcut_widget) {
2277     g_signal_handlers_disconnect_by_func (self->shortcut_widget,
2278                                           shortcut_key_press_cb, self);
2279 
2280     g_object_weak_unref (G_OBJECT (self->shortcut_widget),
2281                          (GWeakNotify) shortcut_widget_notify_cb,
2282                          self);
2283   }
2284 
2285   self->shortcut_widget = widget;
2286 
2287   if (self->shortcut_widget) {
2288     g_object_weak_ref (G_OBJECT (self->shortcut_widget),
2289                        (GWeakNotify) shortcut_widget_notify_cb,
2290                        self);
2291 
2292     g_signal_connect_swapped (self->shortcut_widget, "key-press-event",
2293                               G_CALLBACK (shortcut_key_press_cb), self);
2294   }
2295 
2296   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHORTCUT_WIDGET]);
2297 }
2298 
2299 /**
2300  * hdy_tab_view_set_page_pinned:
2301  * @self: a #HdyTabView
2302  * @page: a page of @self
2303  * @pinned: whether @page should be pinned
2304  *
2305  * Pins or unpins @page.
2306  *
2307  * Pinned pages are guaranteed to be placed before all non-pinned pages; at any
2308  * given moment the first #HdyTabView:n-pinned-pages pages in @self are
2309  * guaranteed to be pinned.
2310  *
2311  * When a page is pinned or unpinned, it's automatically reordered: pinning a
2312  * page moves it after other pinned pages; unpinning a page moves it before
2313  * other non-pinned pages.
2314  *
2315  * Pinned pages can still be reordered between each other.
2316  *
2317  * #HdyTabBar will display pinned pages in a compact form, never showing the
2318  * title or close button, and only showing a single icon, selected in the
2319  * following order:
2320  *
2321  * 1. #HdyTabPage:indicator-icon
2322  * 2. A spinner if #HdyTabPage:loading is %TRUE
2323  * 3. #HdyTabPage:icon
2324  * 4. #HdyTabView:default-icon
2325  *
2326  * Pinned pages cannot be closed by default, see #HdyTabView::close-page for how
2327  * to override that behavior.
2328  *
2329  * Since: 1.2
2330  */
2331 void
hdy_tab_view_set_page_pinned(HdyTabView * self,HdyTabPage * page,gboolean pinned)2332 hdy_tab_view_set_page_pinned (HdyTabView *self,
2333                               HdyTabPage *page,
2334                               gboolean    pinned)
2335 {
2336   gint pos;
2337 
2338   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2339   g_return_if_fail (HDY_IS_TAB_PAGE (page));
2340   g_return_if_fail (page_belongs_to_this_view (self, page));
2341 
2342   pinned = !!pinned;
2343 
2344   if (hdy_tab_page_get_pinned (page) == pinned)
2345     return;
2346 
2347   pos = hdy_tab_view_get_page_position (self, page);
2348 
2349   g_object_ref (page);
2350 
2351   g_list_store_remove (self->pages, pos);
2352 
2353   pos = self->n_pinned_pages;
2354 
2355   if (!pinned)
2356     pos--;
2357 
2358   g_list_store_insert (self->pages, pos, page);
2359 
2360   g_object_unref (page);
2361 
2362   if (pinned)
2363     pos++;
2364 
2365   gtk_container_child_set (GTK_CONTAINER (self->stack),
2366                            hdy_tab_page_get_child (page),
2367                            "position", self->n_pinned_pages,
2368                            NULL);
2369 
2370   set_n_pinned_pages (self, pos);
2371 
2372   set_page_pinned (page, pinned);
2373 }
2374 
2375 /**
2376  * hdy_tab_view_get_page:
2377  * @self: a #HdyTabView
2378  * @child: a child in @self
2379  *
2380  * Gets the #HdyTabPage object representing @child.
2381  *
2382  * Returns: (transfer none): the #HdyTabPage representing @child
2383  *
2384  * Since: 1.2
2385  */
2386 HdyTabPage *
hdy_tab_view_get_page(HdyTabView * self,GtkWidget * child)2387 hdy_tab_view_get_page (HdyTabView *self,
2388                        GtkWidget  *child)
2389 {
2390   gint i;
2391 
2392   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2393   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2394   g_return_val_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (self->stack), NULL);
2395 
2396   for (i = 0; i < self->n_pages; i++) {
2397     HdyTabPage *page = hdy_tab_view_get_nth_page (self, i);
2398 
2399     if (hdy_tab_page_get_child (page) == child)
2400       return page;
2401   }
2402 
2403   g_assert_not_reached ();
2404 }
2405 
2406 /**
2407  * hdy_tab_view_get_nth_page:
2408  * @self: a #HdyTabView
2409  * @position: the index of the page in @self, starting from 0
2410  *
2411  * Gets the #HdyTabPage representing the child at @position.
2412  *
2413  * Returns: (transfer none): the page object at @position
2414  *
2415  * Since: 1.2
2416  */
2417 HdyTabPage *
hdy_tab_view_get_nth_page(HdyTabView * self,gint position)2418 hdy_tab_view_get_nth_page (HdyTabView *self,
2419                            gint        position)
2420 {
2421   g_autoptr (HdyTabPage) page = NULL;
2422 
2423   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2424   g_return_val_if_fail (position >= 0, NULL);
2425   g_return_val_if_fail (position < self->n_pages, NULL);
2426 
2427   page = g_list_model_get_item (G_LIST_MODEL (self->pages), (guint) position);
2428 
2429   return page;
2430 }
2431 
2432 /**
2433  * hdy_tab_view_get_page_position:
2434  * @self: a #HdyTabView
2435  * @page: a page of @self
2436  *
2437  * Finds the position of @page in @self, starting from 0.
2438  *
2439  * Returns: the position of @page in @self
2440  *
2441  * Since: 1.2
2442  */
2443 gint
hdy_tab_view_get_page_position(HdyTabView * self,HdyTabPage * page)2444 hdy_tab_view_get_page_position (HdyTabView *self,
2445                                 HdyTabPage *page)
2446 {
2447   gint i;
2448 
2449   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), -1);
2450   g_return_val_if_fail (HDY_IS_TAB_PAGE (page), -1);
2451   g_return_val_if_fail (page_belongs_to_this_view (self, page), -1);
2452 
2453   for (i = 0; i < self->n_pages; i++) {
2454     HdyTabPage *p = hdy_tab_view_get_nth_page (self, i);
2455 
2456     if (page == p)
2457       return i;
2458   }
2459 
2460   g_assert_not_reached ();
2461 }
2462 
2463 /**
2464  * hdy_tab_view_add_page:
2465  * @self: a #HdyTabView
2466  * @child: a widget to add
2467  * @parent: (nullable): a parent page for @child, or %NULL
2468  *
2469  * Adds @child to @self with @parent as the parent.
2470  *
2471  * This function can be used to automatically position new pages, and to select
2472  * the correct page when this page is closed while being selected (see
2473  * hdy_tab_view_close_page()).
2474  *
2475  * If @parent is %NULL, this function is equivalent to hdy_tab_view_append().
2476  *
2477  * Returns: (transfer none): the page object representing @child
2478  *
2479  * Since: 1.2
2480  */
2481 HdyTabPage *
hdy_tab_view_add_page(HdyTabView * self,GtkWidget * child,HdyTabPage * parent)2482 hdy_tab_view_add_page (HdyTabView *self,
2483                        GtkWidget  *child,
2484                        HdyTabPage *parent)
2485 {
2486   gint position;
2487 
2488   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2489   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2490   g_return_val_if_fail (HDY_IS_TAB_PAGE (parent) || parent == NULL, NULL);
2491 
2492   if (parent) {
2493     HdyTabPage *page;
2494 
2495     g_return_val_if_fail (page_belongs_to_this_view (self, parent), NULL);
2496 
2497     if (hdy_tab_page_get_pinned (parent))
2498       position = self->n_pinned_pages - 1;
2499     else
2500       position = hdy_tab_view_get_page_position (self, parent);
2501 
2502     do {
2503       position++;
2504 
2505       if (position >= self->n_pages)
2506         break;
2507 
2508       page = hdy_tab_view_get_nth_page (self, position);
2509     } while (is_descendant_of (page, parent));
2510   } else {
2511     position = self->n_pages;
2512   }
2513 
2514   return insert_page (self, child, parent, position, FALSE);
2515 }
2516 
2517 /**
2518  * hdy_tab_view_insert:
2519  * @self: a #HdyTabView
2520  * @child: a widget to add
2521  * @position: the position to add @child at, starting from 0
2522  *
2523  * Inserts a non-pinned page at @position.
2524  *
2525  * It's an error to try to insert a page before a pinned page, in that case
2526  * hdy_tab_view_insert_pinned() should be used instead.
2527  *
2528  * Returns: (transfer none): the page object representing @child
2529  *
2530  * Since: 1.2
2531  */
2532 HdyTabPage *
hdy_tab_view_insert(HdyTabView * self,GtkWidget * child,gint position)2533 hdy_tab_view_insert (HdyTabView *self,
2534                      GtkWidget  *child,
2535                      gint        position)
2536 {
2537   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2538   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2539   g_return_val_if_fail (position >= self->n_pinned_pages, NULL);
2540   g_return_val_if_fail (position <= self->n_pages, NULL);
2541 
2542   return insert_page (self, child, NULL, position, FALSE);
2543 }
2544 
2545 /**
2546  * hdy_tab_view_prepend:
2547  * @self: a #HdyTabView
2548  * @child: a widget to add
2549  *
2550  * Inserts @child as the first non-pinned page.
2551  *
2552  * Returns: (transfer none): the page object representing @child
2553  *
2554  * Since: 1.2
2555  */
2556 HdyTabPage *
hdy_tab_view_prepend(HdyTabView * self,GtkWidget * child)2557 hdy_tab_view_prepend (HdyTabView *self,
2558                       GtkWidget  *child)
2559 {
2560   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2561   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2562 
2563   return insert_page (self, child, NULL, self->n_pinned_pages, FALSE);
2564 }
2565 
2566 /**
2567  * hdy_tab_view_append:
2568  * @self: a #HdyTabView
2569  * @child: a widget to add
2570  *
2571  * Inserts @child as the last non-pinned page.
2572  *
2573  * Returns: (transfer none): the page object representing @child
2574  *
2575  * Since: 1.2
2576  */
2577 HdyTabPage *
hdy_tab_view_append(HdyTabView * self,GtkWidget * child)2578 hdy_tab_view_append (HdyTabView *self,
2579                      GtkWidget  *child)
2580 {
2581   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2582   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2583 
2584   return insert_page (self, child, NULL, self->n_pages, FALSE);
2585 }
2586 
2587 /**
2588  * hdy_tab_view_insert_pinned:
2589  * @self: a #HdyTabView
2590  * @child: a widget to add
2591  * @position: the position to add @child at, starting from 0
2592  *
2593  * Inserts a pinned page at @position.
2594  *
2595  * It's an error to try to insert a pinned page after a non-pinned page, in
2596  * that case hdy_tab_view_insert() should be used instead.
2597  *
2598  * Returns: (transfer none): the page object representing @child
2599  *
2600  * Since: 1.2
2601  */
2602 HdyTabPage *
hdy_tab_view_insert_pinned(HdyTabView * self,GtkWidget * child,gint position)2603 hdy_tab_view_insert_pinned (HdyTabView *self,
2604                             GtkWidget  *child,
2605                             gint        position)
2606 {
2607   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2608   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2609   g_return_val_if_fail (position >= 0, NULL);
2610   g_return_val_if_fail (position <= self->n_pinned_pages, NULL);
2611 
2612   return insert_page (self, child, NULL, position, TRUE);
2613 }
2614 
2615 /**
2616  * hdy_tab_view_prepend_pinned:
2617  * @self: a #HdyTabView
2618  * @child: a widget to add
2619  *
2620  * Inserts @child as the first pinned page.
2621  *
2622  * Returns: (transfer none): the page object representing @child
2623  *
2624  * Since: 1.2
2625  */
2626 HdyTabPage *
hdy_tab_view_prepend_pinned(HdyTabView * self,GtkWidget * child)2627 hdy_tab_view_prepend_pinned (HdyTabView *self,
2628                              GtkWidget  *child)
2629 {
2630   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2631   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2632 
2633   return insert_page (self, child, NULL, 0, TRUE);
2634 }
2635 
2636 /**
2637  * hdy_tab_view_append_pinned:
2638  * @self: a #HdyTabView
2639  * @child: a widget to add
2640  *
2641  * Inserts @child as the last pinned page.
2642  *
2643  * Returns: (transfer none): the page object representing @child
2644  *
2645  * Since: 1.2
2646  */
2647 HdyTabPage *
hdy_tab_view_append_pinned(HdyTabView * self,GtkWidget * child)2648 hdy_tab_view_append_pinned (HdyTabView *self,
2649                             GtkWidget  *child)
2650 {
2651   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
2652   g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
2653 
2654   return insert_page (self, child, NULL, self->n_pinned_pages, TRUE);
2655 }
2656 
2657 /**
2658  * hdy_tab_view_close_page:
2659  * @self: a #HdyTabView
2660  * @page: a page of @self
2661  *
2662  * Requests to close @page.
2663  *
2664  * Calling this function will result in #HdyTabView::close-page signal being
2665  * emitted for @page. Closing the page can then be confirmed or denied via
2666  * hdy_tab_view_close_page_finish().
2667  *
2668  * If the page is waiting for a hdy_tab_view_close_page_finish() call, this
2669  * function will do nothing.
2670  *
2671  * The default handler for #HdyTabView::close-page will immediately confirm
2672  * closing the page if it's non-pinned, or reject it if it's pinned. This
2673  * behavior can be changed by registering your own handler for that signal.
2674  *
2675  * If @page was selected, another page will be selected instead:
2676  *
2677  * If the #HdyTabPage:parent value is %NULL, the next page will be selected when
2678  * possible, or if the page was already last, the previous page will be selected
2679  * instead.
2680  *
2681  * If it's not %NULL, the previous page will be selected if it's a
2682  * descendant (possibly indirect) of the parent. If both the previous page and
2683  * the parent are pinned, the parent will be selected instead.
2684  *
2685  * Since: 1.2
2686  */
2687 void
hdy_tab_view_close_page(HdyTabView * self,HdyTabPage * page)2688 hdy_tab_view_close_page (HdyTabView *self,
2689                          HdyTabPage *page)
2690 {
2691   gboolean ret;
2692 
2693   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2694   g_return_if_fail (HDY_IS_TAB_PAGE (page));
2695   g_return_if_fail (page_belongs_to_this_view (self, page));
2696 
2697   if (page->closing)
2698     return;
2699 
2700   page->closing = TRUE;
2701   g_signal_emit (self, signals[SIGNAL_CLOSE_PAGE], 0, page, &ret);
2702 }
2703 
2704 /**
2705  * hdy_tab_view_close_page_finish:
2706  * @self: a #HdyTabView
2707  * @page: a page of @self
2708  * @confirm: whether to confirm or deny closing @page
2709  *
2710  * Completes a hdy_tab_view_close_page() call for @page.
2711  *
2712  * If @confirm is %TRUE, @page will be closed. If it's %FALSE, ite will be
2713  * reverted to its previous state and hdy_tab_view_close_page() can be called
2714  * for it again.
2715  *
2716  * This function should not be called unless a custom handler for
2717  * #HdyTabView::close-page is used.
2718  *
2719  * Since: 1.2
2720  */
2721 void
hdy_tab_view_close_page_finish(HdyTabView * self,HdyTabPage * page,gboolean confirm)2722 hdy_tab_view_close_page_finish (HdyTabView *self,
2723                                 HdyTabPage *page,
2724                                 gboolean    confirm)
2725 {
2726   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2727   g_return_if_fail (HDY_IS_TAB_PAGE (page));
2728   g_return_if_fail (page_belongs_to_this_view (self, page));
2729   g_return_if_fail (page->closing);
2730 
2731   page->closing = FALSE;
2732 
2733   if (confirm)
2734     detach_page (self, page);
2735 }
2736 
2737 /**
2738  * hdy_tab_view_close_other_pages:
2739  * @self: a #HdyTabView
2740  * @page: a page of @self
2741  *
2742  * Requests to close all pages other than @page.
2743  *
2744  * Since: 1.2
2745  */
2746 void
hdy_tab_view_close_other_pages(HdyTabView * self,HdyTabPage * page)2747 hdy_tab_view_close_other_pages (HdyTabView *self,
2748                                 HdyTabPage *page)
2749 {
2750   gint i;
2751 
2752   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2753   g_return_if_fail (HDY_IS_TAB_PAGE (page));
2754   g_return_if_fail (page_belongs_to_this_view (self, page));
2755 
2756   for (i = self->n_pages - 1; i >= 0; i--) {
2757     HdyTabPage *p = hdy_tab_view_get_nth_page (self, i);
2758 
2759     if (p == page)
2760       continue;
2761 
2762     hdy_tab_view_close_page (self, p);
2763   }
2764 }
2765 
2766 /**
2767  * hdy_tab_view_close_pages_before:
2768  * @self: a #HdyTabView
2769  * @page: a page of @self
2770  *
2771  * Requests to close all pages before @page.
2772  *
2773  * Since: 1.2
2774  */
2775 void
hdy_tab_view_close_pages_before(HdyTabView * self,HdyTabPage * page)2776 hdy_tab_view_close_pages_before (HdyTabView *self,
2777                                  HdyTabPage *page)
2778 {
2779   gint pos, i;
2780 
2781   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2782   g_return_if_fail (HDY_IS_TAB_PAGE (page));
2783   g_return_if_fail (page_belongs_to_this_view (self, page));
2784 
2785   pos = hdy_tab_view_get_page_position (self, page);
2786 
2787   for (i = pos - 1; i >= 0; i--) {
2788     HdyTabPage *p = hdy_tab_view_get_nth_page (self, i);
2789 
2790     hdy_tab_view_close_page (self, p);
2791   }
2792 }
2793 
2794 /**
2795  * hdy_tab_view_close_pages_after:
2796  * @self: a #HdyTabView
2797  * @page: a page of @self
2798  *
2799  * Requests to close all pages after @page.
2800  *
2801  * Since: 1.2
2802  */
2803 void
hdy_tab_view_close_pages_after(HdyTabView * self,HdyTabPage * page)2804 hdy_tab_view_close_pages_after (HdyTabView *self,
2805                                 HdyTabPage *page)
2806 {
2807   gint pos, i;
2808 
2809   g_return_if_fail (HDY_IS_TAB_VIEW (self));
2810   g_return_if_fail (HDY_IS_TAB_PAGE (page));
2811   g_return_if_fail (page_belongs_to_this_view (self, page));
2812 
2813   pos = hdy_tab_view_get_page_position (self, page);
2814 
2815   for (i = self->n_pages - 1; i > pos; i--) {
2816     HdyTabPage *p = hdy_tab_view_get_nth_page (self, i);
2817 
2818     hdy_tab_view_close_page (self, p);
2819   }
2820 }
2821 
2822 /**
2823  * hdy_tab_view_reorder_page:
2824  * @self: a #HdyTabView
2825  * @page: a page of @self
2826  * @position: the position to insert the page at, starting at 0
2827  *
2828  * Reorders @page to @position.
2829  *
2830  * It's a programmer error to try to reorder a pinned page after a non-pinned
2831  * one, or a non-pinned page before a pinned one.
2832  *
2833  * Returns: %TRUE if @page was moved, %FALSE otherwise
2834  *
2835  * Since: 1.2
2836  */
2837 gboolean
hdy_tab_view_reorder_page(HdyTabView * self,HdyTabPage * page,gint position)2838 hdy_tab_view_reorder_page (HdyTabView *self,
2839                            HdyTabPage *page,
2840                            gint        position)
2841 {
2842   gint original_pos;
2843 
2844   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2845   g_return_val_if_fail (HDY_IS_TAB_PAGE (page), FALSE);
2846   g_return_val_if_fail (page_belongs_to_this_view (self, page), FALSE);
2847 
2848   if (hdy_tab_page_get_pinned (page)) {
2849     g_return_val_if_fail (position >= 0, FALSE);
2850     g_return_val_if_fail (position < self->n_pinned_pages, FALSE);
2851   } else {
2852     g_return_val_if_fail (position >= self->n_pinned_pages, FALSE);
2853     g_return_val_if_fail (position < self->n_pages, FALSE);
2854   }
2855 
2856   original_pos = hdy_tab_view_get_page_position (self, page);
2857 
2858   if (original_pos == position)
2859     return FALSE;
2860 
2861   g_object_ref (page);
2862 
2863   g_list_store_remove (self->pages, original_pos);
2864   g_list_store_insert (self->pages, position, page);
2865 
2866   g_object_unref (page);
2867 
2868   gtk_container_child_set (GTK_CONTAINER (self->stack),
2869                            hdy_tab_page_get_child (page),
2870                            "position", position,
2871                            NULL);
2872 
2873   g_signal_emit (self, signals[SIGNAL_PAGE_REORDERED], 0, page, position);
2874 
2875   return TRUE;
2876 }
2877 
2878 /**
2879  * hdy_tab_view_reorder_backward:
2880  * @self: a #HdyTabView
2881  * @page: a page of @self
2882  *
2883  * Reorders @page to before its previous page if possible.
2884  *
2885  * Returns: %TRUE if @page was moved, %FALSE otherwise
2886  *
2887  * Since: 1.2
2888  */
2889 gboolean
hdy_tab_view_reorder_backward(HdyTabView * self,HdyTabPage * page)2890 hdy_tab_view_reorder_backward (HdyTabView *self,
2891                                HdyTabPage *page)
2892 {
2893   gboolean pinned;
2894   gint pos, first;
2895 
2896   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2897   g_return_val_if_fail (HDY_IS_TAB_PAGE (page), FALSE);
2898   g_return_val_if_fail (page_belongs_to_this_view (self, page), FALSE);
2899 
2900   pos = hdy_tab_view_get_page_position (self, page);
2901 
2902   pinned = hdy_tab_page_get_pinned (page);
2903   first = pinned ? 0 : self->n_pinned_pages;
2904 
2905   if (pos <= first)
2906     return FALSE;
2907 
2908   return hdy_tab_view_reorder_page (self, page, pos - 1);
2909 }
2910 
2911 /**
2912  * hdy_tab_view_reorder_forward:
2913  * @self: a #HdyTabView
2914  * @page: a page of @self
2915  *
2916  * Reorders @page to after its next page if possible.
2917  *
2918  * Returns: %TRUE if @page was moved, %FALSE otherwise
2919  *
2920  * Since: 1.2
2921  */
2922 gboolean
hdy_tab_view_reorder_forward(HdyTabView * self,HdyTabPage * page)2923 hdy_tab_view_reorder_forward (HdyTabView *self,
2924                               HdyTabPage *page)
2925 {
2926   gboolean pinned;
2927   gint pos, last;
2928 
2929   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2930   g_return_val_if_fail (HDY_IS_TAB_PAGE (page), FALSE);
2931   g_return_val_if_fail (page_belongs_to_this_view (self, page), FALSE);
2932 
2933   pos = hdy_tab_view_get_page_position (self, page);
2934 
2935   pinned = hdy_tab_page_get_pinned (page);
2936   last = (pinned ? self->n_pinned_pages : self->n_pages) - 1;
2937 
2938   if (pos >= last)
2939     return FALSE;
2940 
2941   return hdy_tab_view_reorder_page (self, page, pos + 1);
2942 }
2943 
2944 /**
2945  * hdy_tab_view_reorder_first:
2946  * @self: a #HdyTabView
2947  * @page: a page of @self
2948  *
2949  * Reorders @page to the first possible position.
2950  *
2951  * Returns: %TRUE if @page was moved, %FALSE otherwise
2952  *
2953  * Since: 1.2
2954  */
2955 gboolean
hdy_tab_view_reorder_first(HdyTabView * self,HdyTabPage * page)2956 hdy_tab_view_reorder_first (HdyTabView *self,
2957                             HdyTabPage *page)
2958 {
2959   gboolean pinned;
2960   gint pos;
2961 
2962   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2963   g_return_val_if_fail (HDY_IS_TAB_PAGE (page), FALSE);
2964   g_return_val_if_fail (page_belongs_to_this_view (self, page), FALSE);
2965 
2966   pinned = hdy_tab_page_get_pinned (page);
2967   pos = pinned ? 0 : self->n_pinned_pages;
2968 
2969   return hdy_tab_view_reorder_page (self, page, pos);
2970 }
2971 
2972 /**
2973  * hdy_tab_view_reorder_last:
2974  * @self: a #HdyTabView
2975  * @page: a page of @self
2976  *
2977  * Reorders @page to the last possible position.
2978  *
2979  * Returns: %TRUE if @page was moved, %FALSE otherwise
2980  *
2981  * Since: 1.2
2982  */
2983 gboolean
hdy_tab_view_reorder_last(HdyTabView * self,HdyTabPage * page)2984 hdy_tab_view_reorder_last (HdyTabView *self,
2985                            HdyTabPage *page)
2986 {
2987   gboolean pinned;
2988   gint pos;
2989 
2990   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), FALSE);
2991   g_return_val_if_fail (HDY_IS_TAB_PAGE (page), FALSE);
2992   g_return_val_if_fail (page_belongs_to_this_view (self, page), FALSE);
2993 
2994   pinned = hdy_tab_page_get_pinned (page);
2995   pos = (pinned ? self->n_pinned_pages : self->n_pages) - 1;
2996 
2997   return hdy_tab_view_reorder_page (self, page, pos);
2998 }
2999 
3000 void
hdy_tab_view_detach_page(HdyTabView * self,HdyTabPage * page)3001 hdy_tab_view_detach_page (HdyTabView *self,
3002                           HdyTabPage *page)
3003 {
3004   g_return_if_fail (HDY_IS_TAB_VIEW (self));
3005   g_return_if_fail (HDY_IS_TAB_PAGE (page));
3006   g_return_if_fail (page_belongs_to_this_view (self, page));
3007 
3008   g_object_ref (page);
3009 
3010   begin_transfer_for_group (self);
3011 
3012   detach_page (self, page);
3013 }
3014 
3015 void
hdy_tab_view_attach_page(HdyTabView * self,HdyTabPage * page,gint position)3016 hdy_tab_view_attach_page (HdyTabView *self,
3017                           HdyTabPage *page,
3018                           gint        position)
3019 {
3020   g_return_if_fail (HDY_IS_TAB_VIEW (self));
3021   g_return_if_fail (HDY_IS_TAB_PAGE (page));
3022   g_return_if_fail (!page_belongs_to_this_view (self, page));
3023   g_return_if_fail (position >= 0);
3024   g_return_if_fail (position <= self->n_pages);
3025 
3026   attach_page (self, page, position);
3027 
3028   hdy_tab_view_set_selected_page (self, page);
3029 
3030   end_transfer_for_group (self);
3031 
3032   g_object_unref (page);
3033 }
3034 
3035 /**
3036  * hdy_tab_view_transfer_page:
3037  * @self: a #HdyTabView
3038  * @page: a page of @self
3039  * @other_view: the tab view to transfer the page to
3040  * @position: the position to insert the page at, starting at 0
3041  *
3042  * Transfers @page from @self to @other_view. The @page object will be reused.
3043  *
3044  * It's a programmer error to try to insert a pinned page after a non-pinned
3045  * one, or a non-pinned page before a pinned one.
3046  *
3047  * Since: 1.2
3048  */
3049 void
hdy_tab_view_transfer_page(HdyTabView * self,HdyTabPage * page,HdyTabView * other_view,gint position)3050 hdy_tab_view_transfer_page (HdyTabView *self,
3051                             HdyTabPage *page,
3052                             HdyTabView *other_view,
3053                             gint        position)
3054 {
3055   gboolean pinned;
3056 
3057   g_return_if_fail (HDY_IS_TAB_VIEW (self));
3058   g_return_if_fail (HDY_IS_TAB_PAGE (page));
3059   g_return_if_fail (HDY_IS_TAB_VIEW (other_view));
3060   g_return_if_fail (page_belongs_to_this_view (self, page));
3061   g_return_if_fail (position >= 0);
3062   g_return_if_fail (position <= other_view->n_pages);
3063 
3064   pinned = hdy_tab_page_get_pinned (page);
3065 
3066   g_return_if_fail (!pinned || position <= other_view->n_pinned_pages);
3067   g_return_if_fail (pinned || position >= other_view->n_pinned_pages);
3068 
3069   hdy_tab_view_detach_page (self, page);
3070   hdy_tab_view_attach_page (other_view, page, position);
3071 }
3072 
3073 /**
3074  * hdy_tab_view_get_pages:
3075  * @self: a #HdyTabView
3076  *
3077  * Returns a #GListModel containing the pages of @self. This model can be used
3078  * to keep an up to date view of the pages.
3079  *
3080  * Returns: (transfer none): the model containing pages of @self
3081  *
3082  * Since: 1.2
3083  */
3084 GListModel *
hdy_tab_view_get_pages(HdyTabView * self)3085 hdy_tab_view_get_pages (HdyTabView *self)
3086 {
3087   g_return_val_if_fail (HDY_IS_TAB_VIEW (self), NULL);
3088 
3089   return G_LIST_MODEL (self->pages);
3090 }
3091 
3092 HdyTabView *
hdy_tab_view_create_window(HdyTabView * self)3093 hdy_tab_view_create_window (HdyTabView *self)
3094 {
3095   HdyTabView *new_view = NULL;
3096 
3097   g_signal_emit (self, signals[SIGNAL_CREATE_WINDOW], 0, &new_view);
3098 
3099   if (!new_view) {
3100     g_critical ("HdyTabView::create-window handler must not return NULL");
3101 
3102     return NULL;
3103   }
3104 
3105   new_view->transfer_count = self->transfer_count;
3106 
3107   return new_view;
3108 }
3109