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