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-box-private.h"
13 #include "hdy-animation-private.h"
14 #include "hdy-css-private.h"
15 #include "hdy-tab-private.h"
16 #include "hdy-tab-bar-private.h"
17 #include "hdy-tab-view-private.h"
18 #include <math.h>
19
20 /* Border collapsing without glitches */
21 #define OVERLAP 1
22 #define DND_THRESHOLD_MULTIPLIER 4
23 #define DROP_SWITCH_TIMEOUT 500
24
25 #define AUTOSCROLL_SPEED 2.5
26
27 #define OPEN_ANIMATION_DURATION 200
28 #define CLOSE_ANIMATION_DURATION 200
29 #define FOCUS_ANIMATION_DURATION 200
30 #define SCROLL_ANIMATION_DURATION 200
31 #define RESIZE_ANIMATION_DURATION 200
32 #define REORDER_ANIMATION_DURATION 250
33 #define ICON_RESIZE_ANIMATION_DURATION 200
34
35 #define MAX_TAB_WIDTH_NON_EXPAND 220
36
37 typedef enum {
38 TAB_RESIZE_NORMAL,
39 TAB_RESIZE_FIXED_TAB_WIDTH,
40 TAB_RESIZE_FIXED_END_PADDING
41 } TabResizeMode;
42
43 static const GtkTargetEntry src_targets [] = {
44 { "HDY_TAB", GTK_TARGET_SAME_APP, 0 },
45 { "application/x-rootwindow-drop", 0, 0 },
46 };
47
48 static const GtkTargetEntry dst_targets [] = {
49 { "HDY_TAB", GTK_TARGET_SAME_APP, 0 },
50 };
51
52 typedef struct {
53 GtkWidget *window;
54 GdkDragContext *context;
55
56 HdyTab *tab;
57 GtkBorder tab_margin;
58
59 gint hotspot_x;
60 gint hotspot_y;
61
62 gint width;
63 gint target_width;
64 HdyAnimation *resize_animation;
65 } DragIcon;
66
67 typedef struct {
68 HdyTabPage *page;
69 HdyTab *tab;
70
71 gint pos;
72 gint width;
73 gint last_width;
74
75 gdouble end_reorder_offset;
76 gdouble reorder_offset;
77
78 HdyAnimation *reorder_animation;
79 gboolean reorder_ignore_bounds;
80
81 gdouble appear_progress;
82 HdyAnimation *appear_animation;
83
84 gulong notify_needs_attention_id;
85 } TabInfo;
86
87 struct _HdyTabBox
88 {
89 GtkContainer parent_instance;
90
91 gboolean pinned;
92 HdyTabBar *tab_bar;
93 HdyTabView *view;
94 GtkAdjustment *adjustment;
95 gboolean needs_attention_left;
96 gboolean needs_attention_right;
97 gboolean expand_tabs;
98 gboolean inverted;
99
100 GList *tabs;
101 gint n_tabs;
102
103 GdkWindow *window;
104 GdkWindow *reorder_window;
105
106 GtkMenu *context_menu;
107 GtkPopover *touch_menu;
108 GtkGesture *touch_menu_gesture;
109
110 gint allocated_width;
111 gint last_width;
112 gint end_padding;
113 gint initial_end_padding;
114 TabResizeMode tab_resize_mode;
115 HdyAnimation *resize_animation;
116
117 TabInfo *selected_tab;
118
119 gboolean hovering;
120 gdouble hover_x;
121 gdouble hover_y;
122 TabInfo *hovered_tab;
123
124 gboolean pressed;
125 TabInfo *pressed_tab;
126
127 TabInfo *reordered_tab;
128 HdyAnimation *reorder_animation;
129
130 gint reorder_x;
131 gint reorder_y;
132 gint reorder_index;
133 gint reorder_window_x;
134 gboolean continue_reorder;
135 gboolean indirect_reordering;
136 gint pressed_button;
137
138 gboolean dragging;
139 gdouble drag_begin_x;
140 gdouble drag_begin_y;
141 gdouble drag_offset_x;
142 gdouble drag_offset_y;
143 GdkSeat *drag_seat;
144
145 guint drag_autoscroll_cb_id;
146 gint64 drag_autoscroll_prev_time;
147
148 HdyTabPage *detached_page;
149 gint detached_index;
150 TabInfo *reorder_placeholder;
151 HdyTabPage *placeholder_page;
152 gint placeholder_scroll_offset;
153 gboolean can_remove_placeholder;
154 DragIcon *drag_icon;
155 gboolean should_detach_into_new_window;
156 GtkTargetList *source_targets;
157
158 TabInfo *drop_target_tab;
159 guint drop_switch_timeout_id;
160 guint reset_drop_target_tab_id;
161 gboolean can_accept_drop;
162 int drop_target_x;
163
164 struct {
165 TabInfo *info;
166 gint pos;
167 gint64 duration;
168 gboolean keep_selected_visible;
169 } scheduled_scroll;
170
171 HdyAnimation *scroll_animation;
172 gboolean scroll_animation_done;
173 gdouble scroll_animation_from;
174 gdouble scroll_animation_offset;
175 TabInfo *scroll_animation_tab;
176 gboolean block_scrolling;
177 gdouble adjustment_prev_value;
178 };
179
180 G_DEFINE_TYPE (HdyTabBox, hdy_tab_box, GTK_TYPE_CONTAINER)
181
182 enum {
183 PROP_0,
184 PROP_PINNED,
185 PROP_TAB_BAR,
186 PROP_VIEW,
187 PROP_ADJUSTMENT,
188 PROP_NEEDS_ATTENTION_LEFT,
189 PROP_NEEDS_ATTENTION_RIGHT,
190 PROP_RESIZE_FROZEN,
191 LAST_PROP
192 };
193
194 static GParamSpec *props[LAST_PROP];
195
196 enum {
197 SIGNAL_STOP_KINETIC_SCROLLING,
198 SIGNAL_EXTRA_DRAG_DATA_RECEIVED,
199 SIGNAL_ACTIVATE_TAB,
200 SIGNAL_FOCUS_TAB,
201 SIGNAL_REORDER_TAB,
202 SIGNAL_LAST_SIGNAL,
203 };
204
205 static guint signals[SIGNAL_LAST_SIGNAL];
206
207 /* Helpers */
208
209 static void
remove_and_free_tab_info(TabInfo * info)210 remove_and_free_tab_info (TabInfo *info)
211 {
212 gtk_widget_unparent (GTK_WIDGET (info->tab));
213
214 g_free (info);
215 }
216
217 static inline gint
get_tab_position(HdyTabBox * self,TabInfo * info)218 get_tab_position (HdyTabBox *self,
219 TabInfo *info)
220 {
221 if (info == self->reordered_tab) {
222 gint pos = 0;
223 gdk_window_get_position (self->reorder_window, &pos, NULL);
224
225 return pos;
226 }
227
228 return info->pos;
229 }
230
231 static inline TabInfo *
find_tab_info_at(HdyTabBox * self,double x)232 find_tab_info_at (HdyTabBox *self,
233 double x)
234 {
235 GList *l;
236
237 if (self->reordered_tab) {
238 gint pos = get_tab_position (self, self->reordered_tab);
239
240 if (pos <= x && x < pos + self->reordered_tab->width)
241 return self->reordered_tab;
242 }
243
244 for (l = self->tabs; l; l = l->next) {
245 TabInfo *info = l->data;
246
247 if (info != self->reordered_tab &&
248 info->pos <= x && x < info->pos + info->width)
249 return info;
250 }
251
252 return NULL;
253 }
254
255 static inline GList *
find_link_for_page(HdyTabBox * self,HdyTabPage * page)256 find_link_for_page (HdyTabBox *self,
257 HdyTabPage *page)
258 {
259 GList *l;
260
261 for (l = self->tabs; l; l = l->next) {
262 TabInfo *info = l->data;
263
264 if (info->page == page)
265 return l;
266 }
267
268 return NULL;
269 }
270
271 static inline TabInfo *
find_info_for_page(HdyTabBox * self,HdyTabPage * page)272 find_info_for_page (HdyTabBox *self,
273 HdyTabPage *page)
274 {
275 GList *l = find_link_for_page (self, page);
276
277 return l ? l->data : NULL;
278 }
279
280 static GList *
find_nth_alive_tab(HdyTabBox * self,guint position)281 find_nth_alive_tab (HdyTabBox *self,
282 guint position)
283 {
284 GList *l;
285
286 for (l = self->tabs; l; l = l->next) {
287 TabInfo *info = l->data;
288
289 if (!info->page)
290 continue;
291
292 if (!position--)
293 return l;
294 }
295
296 return NULL;
297 }
298
299 static inline gint
calculate_tab_width(TabInfo * info,gint base_width)300 calculate_tab_width (TabInfo *info,
301 gint base_width)
302 {
303 return OVERLAP + (gint) floor ((base_width - OVERLAP) * info->appear_progress);
304 }
305
306 static gint
get_base_tab_width(HdyTabBox * self,gboolean target)307 get_base_tab_width (HdyTabBox *self, gboolean target)
308 {
309 gdouble max_progress = 0;
310 gdouble n = 0;
311 gdouble used_width;
312 GList *l;
313 gint ret;
314
315 for (l = self->tabs; l; l = l->next) {
316 TabInfo *info = l->data;
317
318 max_progress = MAX (max_progress, info->appear_progress);
319 n += info->appear_progress;
320 }
321
322 used_width = (self->allocated_width + (n + 1) * OVERLAP - (target ? 0 : self->end_padding)) * max_progress;
323
324 ret = (gint) ceil (used_width / n);
325
326 if (!self->expand_tabs)
327 ret = MIN (ret, MAX_TAB_WIDTH_NON_EXPAND + OVERLAP);
328
329 return ret;
330 }
331
332 static gint
predict_tab_width(HdyTabBox * self,TabInfo * info,gboolean assume_placeholder)333 predict_tab_width (HdyTabBox *self,
334 TabInfo *info,
335 gboolean assume_placeholder)
336 {
337 gint n;
338 gint width = self->allocated_width;
339 gint min;
340
341 if (self->pinned)
342 n = hdy_tab_view_get_n_pinned_pages (self->view);
343 else
344 n = hdy_tab_view_get_n_pages (self->view) - hdy_tab_view_get_n_pinned_pages (self->view);
345
346 if (assume_placeholder)
347 n++;
348
349 width += OVERLAP * (n + 1) - self->end_padding;
350
351 /* Tabs have 0 minimum width, we need natural width instead */
352 gtk_widget_get_preferred_width (GTK_WIDGET (info->tab), NULL, &min);
353
354 if (self->expand_tabs)
355 return MAX ((gint) floor (width / (gdouble) n), min);
356 else
357 return CLAMP ((gint) floor (width / (gdouble) n), min, MAX_TAB_WIDTH_NON_EXPAND);
358 }
359
360 static gint
calculate_tab_offset(HdyTabBox * self,TabInfo * info,gboolean target)361 calculate_tab_offset (HdyTabBox *self,
362 TabInfo *info,
363 gboolean target)
364 {
365 gint width;
366
367 if (!self->reordered_tab)
368 return 0;
369
370 width = (target ? hdy_tab_get_display_width (self->reordered_tab->tab) : self->reordered_tab->width) - OVERLAP;
371
372 if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
373 width = -width;
374
375 return (gint) round (width * (target ? info->end_reorder_offset : info->reorder_offset));
376 }
377
378 static gboolean
get_widget_coordinates(HdyTabBox * self,GdkEvent * event,gdouble * x,gdouble * y)379 get_widget_coordinates (HdyTabBox *self,
380 GdkEvent *event,
381 gdouble *x,
382 gdouble *y)
383 {
384 GdkWindow *window = gdk_event_get_window (event);
385 gdouble tx, ty, out_x = -1, out_y = -1;
386
387 if (!gdk_event_get_coords (event, &tx, &ty))
388 goto out;
389
390 while (window && window != self->window) {
391 gint window_x, window_y;
392
393 gdk_window_get_position (window, &window_x, &window_y);
394
395 tx += window_x;
396 ty += window_y;
397
398 window = gdk_window_get_parent (window);
399 }
400
401 if (window) {
402 out_x = tx;
403 out_y = ty;
404 goto out;
405 }
406
407 out:
408 if (x)
409 *x = out_x;
410
411 if (y)
412 *y = out_y;
413
414 return out_x >= 0 && out_y >= 0;
415 }
416
417 static void
get_visible_range(HdyTabBox * self,gint * lower,gint * upper)418 get_visible_range (HdyTabBox *self,
419 gint *lower,
420 gint *upper)
421 {
422 gint min, max;
423 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
424 GtkStateFlags flags = gtk_widget_get_state_flags (GTK_WIDGET (self));
425 GtkBorder border, padding;
426
427 gtk_style_context_get_border (context, flags, &border);
428 gtk_style_context_get_padding (context, flags, &padding);
429
430 min = border.left + padding.left - OVERLAP;
431 max = border.left + padding.left + self->allocated_width + OVERLAP;
432
433 if (self->adjustment) {
434 GtkBorder margin;
435 gint scroll_min, scroll_max;
436 gdouble value, page_size;
437
438 gtk_style_context_get_margin (context, flags, &margin);
439
440 value = gtk_adjustment_get_value (self->adjustment);
441 page_size = gtk_adjustment_get_page_size (self->adjustment);
442
443 scroll_min = (gint) floor (value);
444 scroll_max = (gint) ceil (value + page_size);
445
446 min = MAX (min, scroll_min - margin.left - OVERLAP);
447 max = MIN (max, scroll_max - margin.left + OVERLAP);
448 }
449
450 if (lower)
451 *lower = min;
452
453 if (upper)
454 *upper = max;
455 }
456
457 /* Tab resize delay */
458
459 static void
resize_animation_value_cb(gdouble value,gpointer user_data)460 resize_animation_value_cb (gdouble value,
461 gpointer user_data)
462 {
463 HdyTabBox *self = HDY_TAB_BOX (user_data);
464 gdouble target_end_padding = 0;
465
466 if (!self->expand_tabs) {
467 gint predicted_tab_width = get_base_tab_width (self, TRUE);
468 GList *l;
469
470 target_end_padding = self->allocated_width + OVERLAP;
471
472 for (l = self->tabs; l; l = l->next) {
473 TabInfo *info = l->data;
474
475 target_end_padding -= calculate_tab_width (info, predicted_tab_width) - OVERLAP;
476 }
477
478 target_end_padding = MAX (target_end_padding, 0);
479 }
480
481 self->end_padding = (gint) floor (hdy_lerp (self->initial_end_padding, target_end_padding, value));
482
483 gtk_widget_queue_resize (GTK_WIDGET (self));
484 }
485
486 static void
resize_animation_done_cb(gpointer user_data)487 resize_animation_done_cb (gpointer user_data)
488 {
489 HdyTabBox *self = HDY_TAB_BOX (user_data);
490
491 self->end_padding = 0;
492 gtk_widget_queue_resize (GTK_WIDGET (self));
493
494 g_clear_pointer (&self->resize_animation, hdy_animation_unref);
495 }
496
497 static void
set_tab_resize_mode(HdyTabBox * self,TabResizeMode mode)498 set_tab_resize_mode (HdyTabBox *self,
499 TabResizeMode mode)
500 {
501 gboolean notify;
502
503 if (self->tab_resize_mode == mode)
504 return;
505
506 if (mode == TAB_RESIZE_FIXED_TAB_WIDTH) {
507 GList *l;
508
509 self->last_width = self->allocated_width;
510
511 for (l = self->tabs; l; l = l->next) {
512 TabInfo *info = l->data;
513
514 if (info->appear_animation)
515 info->last_width = hdy_tab_get_display_width (info->tab);
516 else
517 info->last_width = info->width;
518 }
519 } else {
520 self->last_width = 0;
521 }
522
523 if (mode == TAB_RESIZE_NORMAL) {
524 self->initial_end_padding = self->end_padding;
525
526 self->resize_animation =
527 hdy_animation_new (GTK_WIDGET (self), 0, 1,
528 RESIZE_ANIMATION_DURATION,
529 hdy_ease_out_cubic,
530 resize_animation_value_cb,
531 resize_animation_done_cb,
532 self);
533
534 hdy_animation_start (self->resize_animation);
535 }
536
537 notify = (self->tab_resize_mode == TAB_RESIZE_NORMAL) !=
538 (mode == TAB_RESIZE_NORMAL);
539
540 self->tab_resize_mode = mode;
541
542 if (notify)
543 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_RESIZE_FROZEN]);
544 }
545
546 /* Hover */
547
548 static void
update_hover(HdyTabBox * self)549 update_hover (HdyTabBox *self)
550 {
551 TabInfo *info;
552
553 if (self->dragging)
554 return;
555
556 if (!self->hovering) {
557 set_tab_resize_mode (self, TAB_RESIZE_NORMAL);
558
559 if (self->hovered_tab) {
560 hdy_tab_set_hovering (self->hovered_tab->tab, FALSE);
561 self->hovered_tab = NULL;
562 }
563
564 return;
565 }
566
567 info = find_tab_info_at (self, self->hover_x);
568
569 if (info != self->hovered_tab) {
570 if (self->hovered_tab)
571 hdy_tab_set_hovering (self->hovered_tab->tab, FALSE);
572
573 self->hovered_tab = info;
574
575 if (self->hovered_tab)
576 hdy_tab_set_hovering (self->hovered_tab->tab, TRUE);
577 }
578 }
579
580 /* Keybindings */
581
582 static void
add_focus_bindings(GtkBindingSet * binding_set,guint keysym,GtkDirectionType direction,gboolean last)583 add_focus_bindings (GtkBindingSet *binding_set,
584 guint keysym,
585 GtkDirectionType direction,
586 gboolean last)
587 {
588 /* All keypad keysyms are aligned at the same order as non-keypad ones */
589 guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
590
591 gtk_binding_entry_add_signal (binding_set, keysym, 0,
592 "focus-tab", 2,
593 GTK_TYPE_DIRECTION_TYPE, direction,
594 G_TYPE_BOOLEAN, last);
595 gtk_binding_entry_add_signal (binding_set, keypad_keysym, 0,
596 "focus-tab", 2,
597 GTK_TYPE_DIRECTION_TYPE, direction,
598 G_TYPE_BOOLEAN, last);
599 }
600
601 static void
add_reorder_bindings(GtkBindingSet * binding_set,guint keysym,GtkDirectionType direction,gboolean last)602 add_reorder_bindings (GtkBindingSet *binding_set,
603 guint keysym,
604 GtkDirectionType direction,
605 gboolean last)
606 {
607 /* All keypad keysyms are aligned at the same order as non-keypad ones */
608 guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
609
610 gtk_binding_entry_add_signal (binding_set, keysym, GDK_SHIFT_MASK,
611 "reorder-tab", 2,
612 GTK_TYPE_DIRECTION_TYPE, direction,
613 G_TYPE_BOOLEAN, last);
614 gtk_binding_entry_add_signal (binding_set, keypad_keysym, GDK_SHIFT_MASK,
615 "reorder-tab", 2,
616 GTK_TYPE_DIRECTION_TYPE, direction,
617 G_TYPE_BOOLEAN, last);
618 }
619
620 static void
activate_tab(HdyTabBox * self)621 activate_tab (HdyTabBox *self)
622 {
623 GtkWidget *child;
624
625 if (!self->selected_tab || !self->selected_tab->page)
626 return;
627
628 child = hdy_tab_page_get_child (self->selected_tab->page);
629
630 gtk_widget_grab_focus (child);
631 }
632
633 static void
focus_tab_cb(HdyTabBox * self,GtkDirectionType direction,gboolean last)634 focus_tab_cb (HdyTabBox *self,
635 GtkDirectionType direction,
636 gboolean last)
637 {
638 gboolean is_rtl, success = last;
639
640 if (!self->view || !self->selected_tab)
641 return;
642
643 is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
644
645 if (direction == GTK_DIR_LEFT)
646 direction = is_rtl ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD;
647 else if (direction == GTK_DIR_RIGHT)
648 direction = is_rtl ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
649
650 if (direction == GTK_DIR_TAB_BACKWARD) {
651 if (last)
652 success = hdy_tab_view_select_first_page (self->view);
653 else
654 success = hdy_tab_view_select_previous_page (self->view);
655 } else if (direction == GTK_DIR_TAB_FORWARD) {
656 if (last)
657 success = hdy_tab_view_select_last_page (self->view);
658 else
659 success = hdy_tab_view_select_next_page (self->view);
660 }
661
662 if (!success)
663 gtk_widget_error_bell (GTK_WIDGET (self));
664 }
665
666 static void
reorder_tab_cb(HdyTabBox * self,GtkDirectionType direction,gboolean last)667 reorder_tab_cb (HdyTabBox *self,
668 GtkDirectionType direction,
669 gboolean last)
670 {
671 gboolean is_rtl, success = last;
672
673 if (!self->view || !self->selected_tab || !self->selected_tab->page)
674 return;
675
676 is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
677
678 if (direction == GTK_DIR_LEFT)
679 direction = is_rtl ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD;
680 else if (direction == GTK_DIR_RIGHT)
681 direction = is_rtl ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
682
683 if (direction == GTK_DIR_TAB_BACKWARD) {
684 if (last)
685 success = hdy_tab_view_reorder_first (self->view, self->selected_tab->page);
686 else
687 success = hdy_tab_view_reorder_backward (self->view, self->selected_tab->page);
688 } else if (direction == GTK_DIR_TAB_FORWARD) {
689 if (last)
690 success = hdy_tab_view_reorder_last (self->view, self->selected_tab->page);
691 else
692 success = hdy_tab_view_reorder_forward (self->view, self->selected_tab->page);
693 }
694
695 if (!success)
696 gtk_widget_error_bell (GTK_WIDGET (self));
697 }
698
699 /* Scrolling */
700
701 static void
update_visible(HdyTabBox * self)702 update_visible (HdyTabBox *self)
703 {
704 gboolean left = FALSE, right = FALSE;
705 GList *l;
706 gdouble value, page_size;
707
708 if (!self->adjustment)
709 return;
710
711 value = gtk_adjustment_get_value (self->adjustment);
712 page_size = gtk_adjustment_get_page_size (self->adjustment);
713
714 if (!self->adjustment)
715 return;
716
717 for (l = self->tabs; l; l = l->next) {
718 TabInfo *info = l->data;
719 gint pos;
720
721 if (!info->page)
722 continue;
723
724 pos = get_tab_position (self, info);
725
726 hdy_tab_set_fully_visible (info->tab,
727 pos + OVERLAP >= value &&
728 pos + info->width - OVERLAP <= value + page_size);
729
730 if (!hdy_tab_page_get_needs_attention (info->page))
731 continue;
732
733 if (pos + info->width / 2.0 <= value)
734 left = TRUE;
735
736 if (pos + info->width / 2.0 >= value + page_size)
737 right = TRUE;
738 }
739
740 if (self->needs_attention_left != left) {
741 self->needs_attention_left = left;
742 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NEEDS_ATTENTION_LEFT]);
743 }
744
745 if (self->needs_attention_right != right) {
746 self->needs_attention_right = right;
747 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NEEDS_ATTENTION_RIGHT]);
748 }
749 }
750
751 static gdouble
get_scroll_animation_value(HdyTabBox * self)752 get_scroll_animation_value (HdyTabBox *self)
753 {
754 gdouble to, value;
755
756 g_assert (self->scroll_animation);
757
758 to = self->scroll_animation_offset;
759
760 if (self->scroll_animation_tab) {
761 gdouble lower, upper, page_size;
762
763 to += get_tab_position (self, self->scroll_animation_tab);
764
765 g_object_get (self->adjustment,
766 "lower", &lower,
767 "upper", &upper,
768 "page-size", &page_size,
769 NULL);
770
771 to = CLAMP (to, lower, upper - page_size);
772 }
773
774 value = hdy_animation_get_value (self->scroll_animation);
775
776 return round (hdy_lerp (self->scroll_animation_from, to, value));
777 }
778
779 static gboolean
drop_switch_timeout_cb(HdyTabBox * self)780 drop_switch_timeout_cb (HdyTabBox *self)
781 {
782 self->drop_switch_timeout_id = 0;
783 hdy_tab_view_set_selected_page (self->view,
784 self->drop_target_tab->page);
785
786 return G_SOURCE_REMOVE;
787 }
788
789 static void
set_drop_target_tab(HdyTabBox * self,TabInfo * info,gboolean highlight)790 set_drop_target_tab (HdyTabBox *self,
791 TabInfo *info,
792 gboolean highlight)
793 {
794 if (self->drop_target_tab == info)
795 return;
796
797 if (self->drop_target_tab) {
798 g_clear_handle_id (&self->drop_switch_timeout_id, g_source_remove);
799
800 gtk_drag_unhighlight (GTK_WIDGET (self->drop_target_tab->tab));
801 hdy_tab_set_hovering (self->drop_target_tab->tab, FALSE);
802 }
803
804 self->drop_target_tab = info;
805
806 if (self->drop_target_tab) {
807 hdy_tab_set_hovering (info->tab, TRUE);
808
809 if (highlight)
810 gtk_drag_highlight (GTK_WIDGET (info->tab));
811
812 self->drop_switch_timeout_id =
813 g_timeout_add (DROP_SWITCH_TIMEOUT,
814 (GSourceFunc) drop_switch_timeout_cb,
815 self);
816 }
817 }
818
819 static void
adjustment_value_changed_cb(HdyTabBox * self)820 adjustment_value_changed_cb (HdyTabBox *self)
821 {
822 gdouble value = gtk_adjustment_get_value (self->adjustment);
823
824 self->hover_x += (value - self->adjustment_prev_value);
825
826 update_hover (self);
827 update_visible (self);
828
829 if (self->drop_target_tab) {
830 self->drop_target_x += (value - self->adjustment_prev_value);
831 set_drop_target_tab (self, find_tab_info_at (self, self->drop_target_x), self->can_accept_drop);
832 }
833
834 self->adjustment_prev_value = value;
835
836 if (self->block_scrolling)
837 return;
838
839 if (self->scroll_animation)
840 hdy_animation_stop (self->scroll_animation);
841 }
842
843 static void
scroll_animation_value_cb(gdouble value,gpointer user_data)844 scroll_animation_value_cb (gdouble value,
845 gpointer user_data)
846 {
847 gtk_widget_queue_resize (GTK_WIDGET (user_data));
848 }
849
850 static void
scroll_animation_done_cb(gpointer user_data)851 scroll_animation_done_cb (gpointer user_data)
852 {
853 HdyTabBox *self = HDY_TAB_BOX (user_data);
854
855 self->scroll_animation_done = TRUE;
856 gtk_widget_queue_resize (GTK_WIDGET (self));
857 }
858
859 static void
animate_scroll(HdyTabBox * self,TabInfo * info,gdouble offset,gint64 duration)860 animate_scroll (HdyTabBox *self,
861 TabInfo *info,
862 gdouble offset,
863 gint64 duration)
864 {
865 if (!self->adjustment)
866 return;
867
868 g_signal_emit (self, signals[SIGNAL_STOP_KINETIC_SCROLLING], 0);
869
870 if (self->scroll_animation)
871 hdy_animation_stop (self->scroll_animation);
872
873 g_clear_pointer (&self->scroll_animation, hdy_animation_unref);
874 self->scroll_animation_done = FALSE;
875 self->scroll_animation_from = gtk_adjustment_get_value (self->adjustment);
876 self->scroll_animation_tab = info;
877 self->scroll_animation_offset = offset;
878
879 /* The actual update will be done in size_allocate (). After the animation
880 * finishes, don't remove it right away, it will be done in size-allocate as
881 * well after one last update, so that we don't miss the last frame.
882 */
883
884 self->scroll_animation =
885 hdy_animation_new (GTK_WIDGET (self), 0, 1, duration,
886 hdy_ease_out_cubic,
887 scroll_animation_value_cb,
888 scroll_animation_done_cb,
889 self);
890
891 hdy_animation_start (self->scroll_animation);
892 }
893
894 static void
animate_scroll_relative(HdyTabBox * self,gdouble delta,gint64 duration)895 animate_scroll_relative (HdyTabBox *self,
896 gdouble delta,
897 gint64 duration)
898 {
899 gdouble current_value = gtk_adjustment_get_value (self->adjustment);
900
901 if (self->scroll_animation) {
902 current_value = self->scroll_animation_offset;
903
904 if (self->scroll_animation_tab)
905 current_value += get_tab_position (self, self->scroll_animation_tab);
906 }
907
908 animate_scroll (self, NULL, current_value + delta, duration);
909 }
910
911 static void
scroll_to_tab_full(HdyTabBox * self,TabInfo * info,gint pos,gint64 duration,gboolean keep_selected_visible)912 scroll_to_tab_full (HdyTabBox *self,
913 TabInfo *info,
914 gint pos,
915 gint64 duration,
916 gboolean keep_selected_visible)
917 {
918 gint tab_width;
919 gdouble padding, value, page_size;
920
921 if (!self->adjustment)
922 return;
923
924 tab_width = info->width;
925
926 if (tab_width < 0) {
927 self->scheduled_scroll.info = info;
928 self->scheduled_scroll.pos = pos;
929 self->scheduled_scroll.duration = duration;
930 self->scheduled_scroll.keep_selected_visible = keep_selected_visible;
931
932 gtk_widget_queue_allocate (GTK_WIDGET (self));
933
934 return;
935 }
936
937 if (info->appear_animation)
938 tab_width = hdy_tab_get_display_width (info->tab);
939
940 value = gtk_adjustment_get_value (self->adjustment);
941 page_size = gtk_adjustment_get_page_size (self->adjustment);
942
943 padding = MIN (tab_width, page_size - tab_width) / 2.0;
944
945 if (pos < 0)
946 pos = get_tab_position (self, info);
947
948 if (pos + OVERLAP < value)
949 animate_scroll (self, info, -padding, duration);
950 else if (pos + tab_width - OVERLAP > value + page_size)
951 animate_scroll (self, info, tab_width + padding - page_size, duration);
952 }
953
954 static void
scroll_to_tab(HdyTabBox * self,TabInfo * info,gint64 duration)955 scroll_to_tab (HdyTabBox *self,
956 TabInfo *info,
957 gint64 duration)
958 {
959 scroll_to_tab_full (self, info, -1, duration, FALSE);
960 }
961
962 /* Reordering */
963
964 static void
force_end_reordering(HdyTabBox * self)965 force_end_reordering (HdyTabBox *self)
966 {
967 GList *l;
968
969 if (self->dragging || !self->reordered_tab)
970 return;
971
972 if (self->reorder_animation)
973 hdy_animation_stop (self->reorder_animation);
974
975 for (l = self->tabs; l; l = l->next) {
976 TabInfo *info = l->data;
977
978 if (info->reorder_animation)
979 hdy_animation_stop (info->reorder_animation);
980 }
981 }
982
983 static void
check_end_reordering(HdyTabBox * self)984 check_end_reordering (HdyTabBox *self)
985 {
986 gboolean should_focus;
987 GtkWidget *tab_widget;
988 GList *l;
989
990 if (self->dragging || !self->reordered_tab || self->continue_reorder)
991 return;
992
993 if (self->reorder_animation)
994 return;
995
996 for (l = self->tabs; l; l = l->next) {
997 TabInfo *info = l->data;
998
999 if (info->reorder_animation)
1000 return;
1001 }
1002
1003 for (l = self->tabs; l; l = l->next) {
1004 TabInfo *info = l->data;
1005
1006 info->end_reorder_offset = 0;
1007 info->reorder_offset = 0;
1008 }
1009
1010 tab_widget = GTK_WIDGET (self->reordered_tab->tab);
1011
1012 should_focus = gtk_widget_has_visible_focus (tab_widget);
1013
1014 gtk_widget_set_child_visible (tab_widget, FALSE);
1015 gtk_widget_unrealize (tab_widget);
1016 gtk_widget_set_parent_window (tab_widget, self->window);
1017 gtk_widget_set_child_visible (tab_widget, TRUE);
1018 gtk_widget_set_has_tooltip (tab_widget, TRUE);
1019
1020 self->reordered_tab->reorder_ignore_bounds = FALSE;
1021
1022 if (should_focus)
1023 gtk_widget_grab_focus (tab_widget);
1024
1025 gdk_window_hide (self->reorder_window);
1026
1027 self->tabs = g_list_remove (self->tabs, self->reordered_tab);
1028 self->tabs = g_list_insert (self->tabs, self->reordered_tab, self->reorder_index);
1029
1030 gtk_widget_queue_allocate (GTK_WIDGET (self));
1031
1032 self->reordered_tab = NULL;
1033 }
1034
1035 static void
start_reordering(HdyTabBox * self,TabInfo * info)1036 start_reordering (HdyTabBox *self,
1037 TabInfo *info)
1038 {
1039 gboolean should_focus;
1040 GtkWidget *tab_widget;
1041
1042 self->reordered_tab = info;
1043
1044 tab_widget = GTK_WIDGET (self->reordered_tab->tab);
1045
1046 should_focus = gtk_widget_has_visible_focus (tab_widget);
1047
1048 gtk_widget_set_has_tooltip (tab_widget, FALSE);
1049 gtk_widget_set_child_visible (tab_widget, FALSE);
1050 gtk_widget_unrealize (tab_widget);
1051 gtk_widget_set_parent_window (tab_widget, self->reorder_window);
1052 gtk_widget_set_child_visible (tab_widget, TRUE);
1053
1054 if (should_focus)
1055 gtk_widget_grab_focus (tab_widget);
1056
1057 gtk_widget_queue_allocate (GTK_WIDGET (self));
1058 }
1059
1060 static gint
get_reorder_position(HdyTabBox * self)1061 get_reorder_position (HdyTabBox *self)
1062 {
1063 gint lower, upper;
1064
1065 if (self->reordered_tab->reorder_ignore_bounds)
1066 return self->reorder_x;
1067
1068 get_visible_range (self, &lower, &upper);
1069
1070 return CLAMP (self->reorder_x, lower, upper - self->reordered_tab->width);
1071 }
1072
1073 static void
reorder_animation_value_cb(gdouble value,gpointer user_data)1074 reorder_animation_value_cb (gdouble value,
1075 gpointer user_data)
1076 {
1077 TabInfo *dest_tab = user_data;
1078 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (dest_tab->tab));
1079 HdyTabBox *self = HDY_TAB_BOX (parent);
1080 gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1081 gdouble x1, x2;
1082
1083 x1 = get_reorder_position (self);
1084 x2 = dest_tab->pos - calculate_tab_offset (self, dest_tab, FALSE);
1085
1086 if (dest_tab->end_reorder_offset * (is_rtl ? 1 : -1) > 0)
1087 x2 += dest_tab->width - self->reordered_tab->width;
1088
1089 self->reorder_window_x = (gint) round (hdy_lerp (x1, x2, value));
1090
1091 gdk_window_move_resize (self->reorder_window,
1092 self->reorder_window_x,
1093 0,
1094 self->reordered_tab->width,
1095 gtk_widget_get_allocated_height (GTK_WIDGET (self)));
1096
1097 update_hover (self);
1098 gtk_widget_queue_draw (GTK_WIDGET (self));
1099 }
1100
1101 static void
reorder_animation_done_cb(gpointer user_data)1102 reorder_animation_done_cb (gpointer user_data)
1103 {
1104 TabInfo *dest_tab = user_data;
1105 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (dest_tab->tab));
1106 HdyTabBox *self = HDY_TAB_BOX (parent);
1107
1108 g_clear_pointer (&self->reorder_animation, hdy_animation_unref);
1109 check_end_reordering (self);
1110 }
1111
1112 static void
animate_reordering(HdyTabBox * self,TabInfo * dest_tab)1113 animate_reordering (HdyTabBox *self,
1114 TabInfo *dest_tab)
1115 {
1116 if (self->reorder_animation)
1117 hdy_animation_stop (self->reorder_animation);
1118
1119 self->reorder_animation =
1120 hdy_animation_new (GTK_WIDGET (self), 0, 1,
1121 REORDER_ANIMATION_DURATION,
1122 hdy_ease_out_cubic,
1123 reorder_animation_value_cb,
1124 reorder_animation_done_cb,
1125 dest_tab);
1126
1127 hdy_animation_start (self->reorder_animation);
1128
1129 check_end_reordering (self);
1130 }
1131
1132 static void
reorder_offset_animation_value_cb(gdouble value,gpointer user_data)1133 reorder_offset_animation_value_cb (gdouble value,
1134 gpointer user_data)
1135 {
1136 TabInfo *info = user_data;
1137 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1138
1139 info->reorder_offset = value;
1140 gtk_widget_queue_allocate (parent);
1141 }
1142
1143 static void
reorder_offset_animation_done_cb(gpointer user_data)1144 reorder_offset_animation_done_cb (gpointer user_data)
1145 {
1146 TabInfo *info = user_data;
1147 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1148 HdyTabBox *self = HDY_TAB_BOX (parent);
1149
1150 g_clear_pointer (&info->reorder_animation, hdy_animation_unref);
1151 check_end_reordering (self);
1152 }
1153
1154 static void
animate_reorder_offset(HdyTabBox * self,TabInfo * info,gdouble offset)1155 animate_reorder_offset (HdyTabBox *self,
1156 TabInfo *info,
1157 gdouble offset)
1158 {
1159 gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1160
1161 offset *= (is_rtl ? -1 : 1);
1162
1163 if (info->end_reorder_offset == offset)
1164 return;
1165
1166 info->end_reorder_offset = offset;
1167
1168 if (info->reorder_animation)
1169 hdy_animation_stop (info->reorder_animation);
1170
1171 info->reorder_animation =
1172 hdy_animation_new (GTK_WIDGET (self), info->reorder_offset, offset,
1173 REORDER_ANIMATION_DURATION,
1174 hdy_ease_out_cubic,
1175 reorder_offset_animation_value_cb,
1176 reorder_offset_animation_done_cb,
1177 info);
1178
1179 hdy_animation_start (info->reorder_animation);
1180 }
1181
1182 static void
reset_reorder_animations(HdyTabBox * self)1183 reset_reorder_animations (HdyTabBox *self)
1184 {
1185 gint i, original_index;
1186 GList *l;
1187
1188 if (!hdy_get_enable_animations (GTK_WIDGET (self)))
1189 return;
1190
1191 l = find_link_for_page (self, self->reordered_tab->page);
1192 original_index = g_list_position (self->tabs, l);
1193
1194 if (self->reorder_index > original_index)
1195 for (i = 0; i < self->reorder_index - original_index; i++) {
1196 l = l->next;
1197 animate_reorder_offset (self, l->data, 0);
1198 }
1199
1200 if (self->reorder_index < original_index)
1201 for (i = 0; i < original_index - self->reorder_index; i++) {
1202 l = l->prev;
1203 animate_reorder_offset (self, l->data, 0);
1204 }
1205 }
1206
1207 static void
page_reordered_cb(HdyTabBox * self,HdyTabPage * page,gint index)1208 page_reordered_cb (HdyTabBox *self,
1209 HdyTabPage *page,
1210 gint index)
1211 {
1212 GList *link;
1213 gint original_index;
1214 TabInfo *info, *dest_tab;
1215 gboolean is_rtl;
1216
1217 if (hdy_tab_page_get_pinned (page) != self->pinned)
1218 return;
1219
1220 self->continue_reorder = self->reordered_tab && page == self->reordered_tab->page;
1221
1222 if (self->continue_reorder)
1223 reset_reorder_animations (self);
1224 else
1225 force_end_reordering (self);
1226
1227 link = find_link_for_page (self, page);
1228 info = link->data;
1229 original_index = g_list_position (self->tabs, link);
1230
1231 if (!self->continue_reorder)
1232 start_reordering (self, info);
1233
1234 gdk_window_show (self->reorder_window);
1235
1236 if (self->continue_reorder)
1237 self->reorder_x = self->reorder_window_x;
1238 else
1239 self->reorder_x = info->pos;
1240
1241 self->reorder_index = index;
1242
1243 if (!self->pinned)
1244 self->reorder_index -= hdy_tab_view_get_n_pinned_pages (self->view);
1245
1246 dest_tab = g_list_nth_data (self->tabs, self->reorder_index);
1247
1248 if (info == self->selected_tab)
1249 scroll_to_tab_full (self, self->selected_tab, dest_tab->pos, REORDER_ANIMATION_DURATION, FALSE);
1250
1251 animate_reordering (self, dest_tab);
1252
1253 is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1254
1255 /* If animations are disabled, animate_reordering() animation will have
1256 * already finished and called check_end_reordering () by this point, so
1257 * it's too late to animate these, so we get a crash.
1258 */
1259
1260 if (hdy_get_enable_animations (GTK_WIDGET (self)) &&
1261 gtk_widget_get_mapped (GTK_WIDGET (self))) {
1262 gint i;
1263
1264 if (self->reorder_index > original_index)
1265 for (i = 0; i < self->reorder_index - original_index; i++) {
1266 link = link->next;
1267 animate_reorder_offset (self, link->data, is_rtl ? 1 : -1);
1268 }
1269
1270 if (self->reorder_index < original_index)
1271 for (i = 0; i < original_index - self->reorder_index; i++) {
1272 link = link->prev;
1273 animate_reorder_offset (self, link->data, is_rtl ? -1 : 1);
1274 }
1275 }
1276
1277 self->continue_reorder = FALSE;
1278 }
1279
1280 static void
prepare_drag_window(GdkSeat * seat,GdkWindow * window,gpointer user_data)1281 prepare_drag_window (GdkSeat *seat,
1282 GdkWindow *window,
1283 gpointer user_data)
1284 {
1285 gdk_window_show (window);
1286 }
1287
1288 static void
update_dragging(HdyTabBox * self)1289 update_dragging (HdyTabBox *self)
1290 {
1291 gboolean is_rtl, after_selected, found_index;
1292 gint x;
1293 gint i = 0;
1294 gint width;
1295 GList *l;
1296
1297 if (!self->dragging)
1298 return;
1299
1300 x = get_reorder_position (self);
1301
1302 width = hdy_tab_get_display_width (self->reordered_tab->tab);
1303
1304 gdk_window_move_resize (self->reorder_window,
1305 x, 0,
1306 width,
1307 gtk_widget_get_allocated_height (GTK_WIDGET (self)));
1308
1309 gtk_widget_queue_draw (GTK_WIDGET (self));
1310
1311 is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1312 after_selected = FALSE;
1313 found_index = FALSE;
1314
1315 for (l = self->tabs; l; l = l->next) {
1316 TabInfo *info = l->data;
1317 gint center = info->pos - calculate_tab_offset (self, info, FALSE) + info->width / 2;
1318 gdouble offset = 0;
1319
1320 if (x + width > center && center > x &&
1321 (!found_index || after_selected)) {
1322 self->reorder_index = i;
1323 found_index = TRUE;
1324 }
1325
1326 i++;
1327
1328 if (info == self->reordered_tab) {
1329 after_selected = TRUE;
1330 continue;
1331 }
1332
1333 if (after_selected != is_rtl && x + width > center)
1334 offset = -1;
1335 else if (after_selected == is_rtl && x < center)
1336 offset = 1;
1337
1338 animate_reorder_offset (self, info, offset);
1339 }
1340 }
1341
1342 static gboolean
drag_autoscroll_cb(GtkWidget * widget,GdkFrameClock * frame_clock,HdyTabBox * self)1343 drag_autoscroll_cb (GtkWidget *widget,
1344 GdkFrameClock *frame_clock,
1345 HdyTabBox *self)
1346 {
1347 gdouble value, lower, upper, page_size;
1348 gdouble x, delta_ms, start_threshold, end_threshold, autoscroll_factor;
1349 gint64 time;
1350 gint offset = 0;
1351 gint tab_width = 0;
1352 gint autoscroll_area = 0;
1353
1354 if (self->reordered_tab) {
1355 gtk_widget_get_preferred_width (GTK_WIDGET (self->reordered_tab->tab),
1356 NULL, &tab_width);
1357 x = (gdouble) self->reorder_x;
1358 } else if (self->drop_target_tab) {
1359 gtk_widget_get_preferred_width (GTK_WIDGET (self->drop_target_tab->tab),
1360 NULL, &tab_width);
1361 x = (gdouble) self->drop_target_x - tab_width / 2;
1362 } else {
1363 return G_SOURCE_CONTINUE;
1364 }
1365
1366 g_object_get (self->adjustment,
1367 "value", &value,
1368 "lower", &lower,
1369 "upper", &upper,
1370 "page-size", &page_size,
1371 NULL);
1372
1373 autoscroll_area = (tab_width - OVERLAP) / 2;
1374
1375 x = CLAMP (x,
1376 lower + autoscroll_area,
1377 upper - tab_width - autoscroll_area);
1378
1379 time = gdk_frame_clock_get_frame_time (frame_clock);
1380 delta_ms = (time - self->drag_autoscroll_prev_time) / 1000.0;
1381
1382 start_threshold = value + autoscroll_area;
1383 end_threshold = value + page_size - tab_width - autoscroll_area;
1384 autoscroll_factor = 0;
1385
1386 if (x < start_threshold)
1387 autoscroll_factor = -(start_threshold - x) / autoscroll_area;
1388 else if (x > end_threshold)
1389 autoscroll_factor = (x - end_threshold) / autoscroll_area;
1390
1391 autoscroll_factor = CLAMP (autoscroll_factor, -1, 1);
1392 autoscroll_factor = hdy_ease_in_cubic (autoscroll_factor);
1393 self->drag_autoscroll_prev_time = time;
1394
1395 if (autoscroll_factor == 0)
1396 return G_SOURCE_CONTINUE;
1397
1398 if (autoscroll_factor > 0)
1399 offset = (gint) ceil (autoscroll_factor * delta_ms * AUTOSCROLL_SPEED);
1400 else
1401 offset = (gint) floor (autoscroll_factor * delta_ms * AUTOSCROLL_SPEED);
1402
1403 self->reorder_x += offset;
1404 gtk_adjustment_set_value (self->adjustment, value + offset);
1405 update_dragging (self);
1406
1407 return G_SOURCE_CONTINUE;
1408 }
1409
1410 static void
start_autoscroll(HdyTabBox * self)1411 start_autoscroll (HdyTabBox *self)
1412 {
1413 GdkFrameClock *frame_clock;
1414
1415 if (!self->adjustment)
1416 return;
1417
1418 if (self->drag_autoscroll_cb_id)
1419 return;
1420
1421 frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));
1422
1423 self->drag_autoscroll_prev_time = gdk_frame_clock_get_frame_time (frame_clock);
1424 self->drag_autoscroll_cb_id =
1425 gtk_widget_add_tick_callback (GTK_WIDGET (self),
1426 (GtkTickCallback) drag_autoscroll_cb,
1427 self, NULL);
1428 }
1429
1430 static void
end_autoscroll(HdyTabBox * self)1431 end_autoscroll (HdyTabBox *self)
1432 {
1433 if (self->drag_autoscroll_cb_id) {
1434 gtk_widget_remove_tick_callback (GTK_WIDGET (self),
1435 self->drag_autoscroll_cb_id);
1436 self->drag_autoscroll_cb_id = 0;
1437 }
1438 }
1439
1440 static void
start_dragging(HdyTabBox * self,GdkEvent * event,TabInfo * info)1441 start_dragging (HdyTabBox *self,
1442 GdkEvent *event,
1443 TabInfo *info)
1444 {
1445 if (self->dragging)
1446 return;
1447
1448 if (!info)
1449 return;
1450
1451 self->continue_reorder = info == self->reordered_tab;
1452
1453 if (self->continue_reorder) {
1454 if (self->reorder_animation)
1455 hdy_animation_stop (self->reorder_animation);
1456
1457 reset_reorder_animations (self);
1458
1459 self->reorder_x = (gint) round (self->hover_x - self->drag_offset_x);
1460 self->reorder_y = (gint) round (self->hover_y - self->drag_offset_y);
1461 } else
1462 force_end_reordering (self);
1463
1464 start_autoscroll (self);
1465 self->dragging = TRUE;
1466
1467 if (!self->continue_reorder)
1468 start_reordering (self, info);
1469
1470 if (!self->indirect_reordering) {
1471 GdkDevice *device = gdk_event_get_device (event);
1472
1473 self->drag_seat = gdk_device_get_seat (device);
1474 gdk_seat_grab (self->drag_seat,
1475 self->reorder_window,
1476 GDK_SEAT_CAPABILITY_ALL,
1477 FALSE,
1478 NULL, // FIXME maybe use an actual cursor
1479 event,
1480 prepare_drag_window,
1481 self);
1482 }
1483 }
1484
1485 static void
end_dragging(HdyTabBox * self)1486 end_dragging (HdyTabBox *self)
1487 {
1488 TabInfo *dest_tab;
1489
1490 if (!self->dragging)
1491 return;
1492
1493 self->dragging = FALSE;
1494
1495 end_autoscroll (self);
1496
1497 dest_tab = g_list_nth_data (self->tabs, self->reorder_index);
1498
1499 if (!self->indirect_reordering) {
1500 gint index;
1501
1502 gdk_seat_ungrab (self->drag_seat);
1503 self->drag_seat = NULL;
1504
1505 index = self->reorder_index;
1506
1507 if (!self->pinned)
1508 index += hdy_tab_view_get_n_pinned_pages (self->view);
1509
1510 /* We've already reordered the tab here, no need to do it again */
1511 g_signal_handlers_block_by_func (self->view, page_reordered_cb, self);
1512
1513 hdy_tab_view_reorder_page (self->view, self->reordered_tab->page, index);
1514
1515 g_signal_handlers_unblock_by_func (self->view, page_reordered_cb, self);
1516 }
1517
1518 animate_reordering (self, dest_tab);
1519
1520 self->continue_reorder = FALSE;
1521 }
1522
1523 /* FIXME: workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/3159 */
1524 static void
check_stuck_dragging(HdyTabBox * self)1525 check_stuck_dragging (HdyTabBox *self)
1526 {
1527 if (self->drag_icon) {
1528 gboolean ret;
1529
1530 g_signal_emit_by_name (self, "drag-failed", self->drag_icon->context, GTK_DRAG_RESULT_NO_TARGET, &ret);
1531 g_signal_emit_by_name (self, "drag-end", self->drag_icon->context);
1532 }
1533 }
1534
1535 /* Selection */
1536
1537 static void
reset_focus(HdyTabBox * self)1538 reset_focus (HdyTabBox *self)
1539 {
1540 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
1541
1542 gtk_container_set_focus_child (GTK_CONTAINER (self), NULL);
1543
1544 if (toplevel && GTK_IS_WINDOW (toplevel))
1545 gtk_window_set_focus (GTK_WINDOW (toplevel), NULL);
1546 }
1547
1548 static void
select_page(HdyTabBox * self,HdyTabPage * page)1549 select_page (HdyTabBox *self,
1550 HdyTabPage *page)
1551 {
1552 if (!page) {
1553 self->selected_tab = NULL;
1554
1555 reset_focus (self);
1556
1557 return;
1558 }
1559
1560 self->selected_tab = find_info_for_page (self, page);
1561
1562 if (!self->selected_tab) {
1563 if (gtk_container_get_focus_child (GTK_CONTAINER (self)))
1564 reset_focus (self);
1565
1566 return;
1567 }
1568
1569 if (hdy_tab_bar_tabs_have_visible_focus (self->tab_bar))
1570 gtk_widget_grab_focus (GTK_WIDGET (self->selected_tab->tab));
1571
1572 gtk_container_set_focus_child (GTK_CONTAINER (self),
1573 GTK_WIDGET (self->selected_tab->tab));
1574
1575 if (self->selected_tab->width >= 0)
1576 scroll_to_tab (self, self->selected_tab, FOCUS_ANIMATION_DURATION);
1577 }
1578
1579 /* Opening */
1580
1581 static void
appear_animation_value_cb(gdouble value,gpointer user_data)1582 appear_animation_value_cb (gdouble value,
1583 gpointer user_data)
1584 {
1585 TabInfo *info = user_data;
1586
1587 info->appear_progress = value;
1588
1589 if (GTK_IS_WIDGET (info->tab))
1590 gtk_widget_queue_resize (GTK_WIDGET (info->tab));
1591 }
1592
1593 static void
open_animation_done_cb(gpointer user_data)1594 open_animation_done_cb (gpointer user_data)
1595 {
1596 TabInfo *info = user_data;
1597
1598 g_clear_pointer (&info->appear_animation, hdy_animation_unref);
1599 }
1600
1601 static TabInfo *
create_tab_info(HdyTabBox * self,HdyTabPage * page)1602 create_tab_info (HdyTabBox *self,
1603 HdyTabPage *page)
1604 {
1605 TabInfo *info;
1606
1607 info = g_new0 (TabInfo, 1);
1608 info->page = page;
1609 info->pos = -1;
1610 info->width = -1;
1611 info->tab = hdy_tab_new (self->view, self->pinned);
1612
1613 hdy_tab_set_page (info->tab, page);
1614 hdy_tab_set_inverted (info->tab, self->inverted);
1615
1616 gtk_widget_set_parent (GTK_WIDGET (info->tab), GTK_WIDGET (self));
1617
1618 if (self->window)
1619 gtk_widget_set_parent_window (GTK_WIDGET (info->tab), self->window);
1620
1621 gtk_widget_show (GTK_WIDGET (info->tab));
1622
1623 return info;
1624 }
1625
1626 static void
page_attached_cb(HdyTabBox * self,HdyTabPage * page,gint position)1627 page_attached_cb (HdyTabBox *self,
1628 HdyTabPage *page,
1629 gint position)
1630 {
1631 TabInfo *info;
1632 GList *l;
1633
1634 if (hdy_tab_page_get_pinned (page) != self->pinned)
1635 return;
1636
1637 if (!self->pinned)
1638 position -= hdy_tab_view_get_n_pinned_pages (self->view);
1639
1640 set_tab_resize_mode (self, TAB_RESIZE_NORMAL);
1641 force_end_reordering (self);
1642
1643 info = create_tab_info (self, page);
1644
1645 info->notify_needs_attention_id =
1646 g_signal_connect_object (page,
1647 "notify::needs-attention",
1648 G_CALLBACK (update_visible),
1649 self,
1650 G_CONNECT_SWAPPED);
1651
1652 info->appear_animation =
1653 hdy_animation_new (GTK_WIDGET (self), 0, 1,
1654 OPEN_ANIMATION_DURATION,
1655 hdy_ease_out_cubic,
1656 appear_animation_value_cb,
1657 open_animation_done_cb,
1658 info);
1659
1660 l = find_nth_alive_tab (self, position);
1661 self->tabs = g_list_insert_before (self->tabs, l, info);
1662
1663 self->n_tabs++;
1664
1665 hdy_animation_start (info->appear_animation);
1666
1667 if (page == hdy_tab_view_get_selected_page (self->view))
1668 hdy_tab_box_select_page (self, page);
1669 else
1670 scroll_to_tab_full (self, info, -1, FOCUS_ANIMATION_DURATION, TRUE);
1671 }
1672
1673 /* Closing */
1674
1675 static void
close_animation_done_cb(gpointer user_data)1676 close_animation_done_cb (gpointer user_data)
1677 {
1678 TabInfo *info = user_data;
1679 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1680 HdyTabBox *self = HDY_TAB_BOX (parent);
1681
1682 g_clear_pointer (&info->appear_animation, hdy_animation_unref);
1683
1684 self->tabs = g_list_remove (self->tabs, info);
1685
1686 if (info->reorder_animation)
1687 hdy_animation_stop (info->reorder_animation);
1688
1689 if (self->reorder_animation)
1690 hdy_animation_stop (self->reorder_animation);
1691
1692 if (self->hovered_tab == info)
1693 self->hovered_tab = NULL;
1694
1695 if (self->pressed_tab == info)
1696 self->pressed_tab = NULL;
1697
1698 if (self->reordered_tab == info)
1699 self->reordered_tab = NULL;
1700
1701 remove_and_free_tab_info (info);
1702
1703 self->n_tabs--;
1704 }
1705
1706 static void
page_detached_cb(HdyTabBox * self,HdyTabPage * page)1707 page_detached_cb (HdyTabBox *self,
1708 HdyTabPage *page)
1709 {
1710 TabInfo *info;
1711 GList *page_link;
1712
1713 page_link = find_link_for_page (self, page);
1714
1715 if (!page_link)
1716 return;
1717
1718 info = page_link->data;
1719 page_link = page_link->next;
1720
1721 force_end_reordering (self);
1722
1723 if (self->hovering && !self->pinned) {
1724 gboolean is_last = TRUE;
1725
1726 while (page_link) {
1727 TabInfo *i = page_link->data;
1728 page_link = page_link->next;
1729
1730 if (i->page) {
1731 is_last = FALSE;
1732 break;
1733 }
1734 }
1735
1736 if (is_last)
1737 set_tab_resize_mode (self, self->inverted ? TAB_RESIZE_NORMAL : TAB_RESIZE_FIXED_END_PADDING);
1738 else
1739 set_tab_resize_mode (self, TAB_RESIZE_FIXED_TAB_WIDTH);
1740 }
1741
1742 g_assert (info->page);
1743
1744 if (gtk_widget_is_focus (GTK_WIDGET (info->tab)))
1745 hdy_tab_box_try_focus_selected_tab (self);
1746
1747 if (info == self->selected_tab)
1748 hdy_tab_box_select_page (self, NULL);
1749
1750 hdy_tab_set_page (info->tab, NULL);
1751
1752 if (info->notify_needs_attention_id > 0) {
1753 g_signal_handler_disconnect (info->page, info->notify_needs_attention_id);
1754 info->notify_needs_attention_id = 0;
1755 }
1756
1757 info->page = NULL;
1758
1759 if (info->appear_animation)
1760 hdy_animation_stop (info->appear_animation);
1761
1762 info->appear_animation =
1763 hdy_animation_new (GTK_WIDGET (self), info->appear_progress, 0,
1764 CLOSE_ANIMATION_DURATION,
1765 hdy_ease_out_cubic,
1766 appear_animation_value_cb,
1767 close_animation_done_cb,
1768 info);
1769
1770 hdy_animation_start (info->appear_animation);
1771 }
1772
1773 /* DND */
1774
1775 static gboolean
check_dnd_threshold(HdyTabBox * self)1776 check_dnd_threshold (HdyTabBox *self)
1777 {
1778 gint threshold;
1779 GtkAllocation alloc;
1780
1781 g_object_get (gtk_settings_get_default (),
1782 "gtk-dnd-drag-threshold", &threshold,
1783 NULL);
1784
1785 threshold *= DND_THRESHOLD_MULTIPLIER;
1786
1787 gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
1788
1789 return self->hover_x < alloc.x - threshold ||
1790 self->hover_y < alloc.y - threshold ||
1791 self->hover_x > alloc.x + alloc.width + threshold ||
1792 self->hover_y > alloc.y + alloc.height + threshold;
1793 }
1794
1795 static gint
calculate_placeholder_index(HdyTabBox * self,gint x)1796 calculate_placeholder_index (HdyTabBox *self,
1797 gint x)
1798 {
1799 gint lower, upper, pos, i;
1800 gboolean is_rtl;
1801 GList *l;
1802
1803 get_visible_range (self, &lower, &upper);
1804
1805 x = CLAMP (x, lower, upper);
1806
1807 is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1808
1809 pos = (is_rtl ? self->allocated_width + OVERLAP : -OVERLAP);
1810 i = 0;
1811
1812 for (l = self->tabs; l; l = l->next) {
1813 TabInfo *info = l->data;
1814 int tab_width = predict_tab_width (self, info, TRUE) * (is_rtl ? -1 : 1);
1815
1816 int end = pos + tab_width + calculate_tab_offset (self, info, FALSE);
1817
1818 if ((x <= end && !is_rtl) || (x >= end && is_rtl))
1819 break;
1820
1821 pos += tab_width + (is_rtl ? OVERLAP : -OVERLAP);
1822 i++;
1823 }
1824
1825 return i;
1826 }
1827
1828 static void
insert_animation_value_cb(gdouble value,gpointer user_data)1829 insert_animation_value_cb (gdouble value,
1830 gpointer user_data)
1831 {
1832 TabInfo *info = user_data;
1833 HdyTabBox *self = HDY_TAB_BOX (gtk_widget_get_parent (GTK_WIDGET (info->tab)));
1834
1835 appear_animation_value_cb (value, info);
1836
1837 update_dragging (self);
1838 }
1839
1840 static void
insert_placeholder(HdyTabBox * self,HdyTabPage * page,gint pos)1841 insert_placeholder (HdyTabBox *self,
1842 HdyTabPage *page,
1843 gint pos)
1844 {
1845 TabInfo *info = self->reorder_placeholder;
1846 gdouble initial_progress = 0;
1847
1848 if (info) {
1849 initial_progress = info->appear_progress;
1850
1851 if (info->appear_animation)
1852 hdy_animation_stop (info->appear_animation);
1853 } else {
1854 gint index;
1855
1856 self->placeholder_page = page;
1857
1858 info = create_tab_info (self, page);
1859
1860 gtk_widget_set_opacity (GTK_WIDGET (info->tab), 0);
1861
1862 hdy_tab_set_dragging (info->tab, TRUE);
1863 hdy_tab_set_hovering (info->tab, TRUE);
1864
1865 info->reorder_ignore_bounds = TRUE;
1866
1867 if (self->adjustment) {
1868 gdouble lower, upper, page_size;
1869
1870 g_object_get (self->adjustment,
1871 "lower", &lower,
1872 "upper", &upper,
1873 "page-size", &page_size,
1874 NULL);
1875
1876 if (upper - lower > page_size) {
1877 gtk_widget_get_preferred_width (GTK_WIDGET (info->tab), NULL,
1878 &self->placeholder_scroll_offset);
1879
1880 self->placeholder_scroll_offset /= 2;
1881 } else {
1882 self->placeholder_scroll_offset = 0;
1883 }
1884 }
1885
1886 index = calculate_placeholder_index (self, pos + self->placeholder_scroll_offset);
1887
1888 self->tabs = g_list_insert (self->tabs, info, index);
1889 self->n_tabs++;
1890
1891 self->reorder_placeholder = info;
1892 self->reorder_index = g_list_index (self->tabs, info);
1893
1894 animate_scroll_relative (self, self->placeholder_scroll_offset, OPEN_ANIMATION_DURATION);
1895 }
1896
1897 info->appear_animation =
1898 hdy_animation_new (GTK_WIDGET (self), initial_progress, 1,
1899 OPEN_ANIMATION_DURATION,
1900 hdy_ease_out_cubic,
1901 insert_animation_value_cb,
1902 open_animation_done_cb,
1903 info);
1904
1905 hdy_animation_start (info->appear_animation);
1906 }
1907
1908 static void
replace_animation_done_cb(gpointer user_data)1909 replace_animation_done_cb (gpointer user_data)
1910 {
1911 TabInfo *info = user_data;
1912 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1913 HdyTabBox *self = HDY_TAB_BOX (parent);
1914
1915 g_clear_pointer (&info->appear_animation, hdy_animation_unref);
1916 self->reorder_placeholder = NULL;
1917 self->can_remove_placeholder = TRUE;
1918 }
1919
1920 static void
replace_placeholder(HdyTabBox * self,HdyTabPage * page)1921 replace_placeholder (HdyTabBox *self,
1922 HdyTabPage *page)
1923 {
1924 TabInfo *info = self->reorder_placeholder;
1925 gdouble initial_progress;
1926
1927 self->placeholder_scroll_offset = 0;
1928 gtk_widget_set_opacity (GTK_WIDGET (self->reorder_placeholder->tab), 1);
1929 hdy_tab_set_dragging (info->tab, FALSE);
1930
1931 if (!info->appear_animation) {
1932 self->reorder_placeholder = NULL;
1933
1934 return;
1935 }
1936
1937 initial_progress = info->appear_progress;
1938
1939 self->can_remove_placeholder = FALSE;
1940
1941 hdy_tab_set_page (info->tab, page);
1942 info->page = page;
1943
1944 hdy_animation_stop (info->appear_animation);
1945
1946 info->appear_animation =
1947 hdy_animation_new (GTK_WIDGET (self), initial_progress, 1,
1948 OPEN_ANIMATION_DURATION,
1949 hdy_ease_out_cubic,
1950 appear_animation_value_cb,
1951 replace_animation_done_cb,
1952 info);
1953
1954 hdy_animation_start (info->appear_animation);
1955 }
1956
1957 static void
remove_animation_done_cb(gpointer user_data)1958 remove_animation_done_cb (gpointer user_data)
1959 {
1960 TabInfo *info = user_data;
1961 GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1962 HdyTabBox *self = HDY_TAB_BOX (parent);
1963
1964 g_clear_pointer (&info->appear_animation, hdy_animation_unref);
1965
1966 if (!self->can_remove_placeholder) {
1967 hdy_tab_set_page (info->tab, self->placeholder_page);
1968 info->page = self->placeholder_page;
1969
1970 return;
1971 }
1972
1973 if (self->reordered_tab == info) {
1974 force_end_reordering (self);
1975
1976 if (self->reorder_animation)
1977 hdy_animation_stop (info->reorder_animation);
1978
1979 self->reordered_tab = NULL;
1980 }
1981
1982 if (self->hovered_tab == info)
1983 self->hovered_tab = NULL;
1984
1985 if (self->pressed_tab == info)
1986 self->pressed_tab = NULL;
1987
1988 self->tabs = g_list_remove (self->tabs, info);
1989
1990 remove_and_free_tab_info (info);
1991
1992 self->n_tabs--;
1993
1994 self->reorder_placeholder = NULL;
1995 }
1996
1997 static gboolean
remove_placeholder_scroll_cb(HdyTabBox * self)1998 remove_placeholder_scroll_cb (HdyTabBox *self)
1999 {
2000 animate_scroll_relative (self, -self->placeholder_scroll_offset, CLOSE_ANIMATION_DURATION);
2001 self->placeholder_scroll_offset = 0;
2002
2003 return G_SOURCE_REMOVE;
2004 }
2005
2006 static void
remove_placeholder(HdyTabBox * self)2007 remove_placeholder (HdyTabBox *self)
2008 {
2009 TabInfo *info = self->reorder_placeholder;
2010
2011 if (!info || !info->page)
2012 return;
2013
2014 hdy_tab_set_page (info->tab, NULL);
2015 info->page = NULL;
2016
2017 if (info->appear_animation)
2018 hdy_animation_stop (info->appear_animation);
2019
2020 g_idle_add ((GSourceFunc) remove_placeholder_scroll_cb, self);
2021
2022 info->appear_animation =
2023 hdy_animation_new (GTK_WIDGET (self), info->appear_progress, 0,
2024 CLOSE_ANIMATION_DURATION,
2025 hdy_ease_out_cubic,
2026 appear_animation_value_cb,
2027 remove_animation_done_cb,
2028 info);
2029
2030 hdy_animation_start (info->appear_animation);
2031 }
2032
2033 static HdyTabBox *
get_source_tab_box(GdkDragContext * context)2034 get_source_tab_box (GdkDragContext *context)
2035 {
2036 GtkWidget *source = gtk_drag_get_source_widget (context);
2037
2038 if (!HDY_IS_TAB_BOX (source))
2039 return NULL;
2040
2041 return HDY_TAB_BOX (source);
2042 }
2043
2044 static gboolean
do_drag_drop(HdyTabBox * self,GdkDragContext * context,guint time)2045 do_drag_drop (HdyTabBox *self,
2046 GdkDragContext *context,
2047 guint time)
2048 {
2049 GdkAtom target, tab_target;
2050 HdyTabBox *source_tab_box;
2051 HdyTabPage *page;
2052 gint offset;
2053
2054 target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL);
2055 tab_target = gdk_atom_intern_static_string ("HDY_TAB");
2056
2057 if (target != tab_target)
2058 return GDK_EVENT_PROPAGATE;
2059
2060 source_tab_box = get_source_tab_box (context);
2061
2062 if (!source_tab_box)
2063 return GDK_EVENT_PROPAGATE;
2064
2065 page = source_tab_box->detached_page;
2066 offset = (self->pinned ? 0 : hdy_tab_view_get_n_pinned_pages (self->view));
2067
2068 if (self->reorder_placeholder) {
2069 replace_placeholder (self, page);
2070 end_dragging (self);
2071
2072 g_signal_handlers_block_by_func (self->view, page_attached_cb, self);
2073
2074 hdy_tab_view_attach_page (self->view, page, self->reorder_index + offset);
2075
2076 g_signal_handlers_unblock_by_func (self->view, page_attached_cb, self);
2077 } else {
2078 hdy_tab_view_attach_page (self->view, page, self->reorder_index + offset);
2079 }
2080
2081 source_tab_box->detached_page = NULL;
2082
2083 self->indirect_reordering = FALSE;
2084 gtk_drag_finish (context, TRUE, FALSE, time);
2085
2086 return GDK_EVENT_STOP;
2087 }
2088
2089 static void
detach_into_new_window(HdyTabBox * self,GdkDragContext * context)2090 detach_into_new_window (HdyTabBox *self,
2091 GdkDragContext *context)
2092 {
2093 HdyTabPage *page;
2094 HdyTabView *new_view;
2095
2096 page = self->detached_page;
2097
2098 new_view = hdy_tab_view_create_window (self->view);
2099
2100 if (HDY_IS_TAB_VIEW (new_view))
2101 hdy_tab_view_attach_page (new_view, page, 0);
2102 else
2103 hdy_tab_view_attach_page (self->view, page, self->detached_index);
2104
2105 self->should_detach_into_new_window = FALSE;
2106 }
2107
2108 static gboolean
is_view_in_the_same_group(HdyTabBox * self,HdyTabView * other_view)2109 is_view_in_the_same_group (HdyTabBox *self,
2110 HdyTabView *other_view)
2111 {
2112 /* TODO when we have groups, this should do the actual check */
2113 return TRUE;
2114 }
2115
2116 static gboolean
view_drag_drop_cb(HdyTabBox * self,GdkDragContext * context,gint x,gint y,guint time)2117 view_drag_drop_cb (HdyTabBox *self,
2118 GdkDragContext *context,
2119 gint x,
2120 gint y,
2121 guint time)
2122 {
2123 HdyTabBox *source_tab_box;
2124
2125 if (self->pinned)
2126 return GDK_EVENT_PROPAGATE;
2127
2128 source_tab_box = get_source_tab_box (context);
2129
2130 if (!source_tab_box)
2131 return GDK_EVENT_PROPAGATE;
2132
2133 if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
2134 return GDK_EVENT_PROPAGATE;
2135
2136 self->reorder_index = hdy_tab_view_get_n_pages (self->view) -
2137 hdy_tab_view_get_n_pinned_pages (self->view);
2138
2139 return do_drag_drop (self, context, time);
2140 }
2141
2142 static void
create_drag_icon(HdyTabBox * self,GdkDragContext * context)2143 create_drag_icon (HdyTabBox *self,
2144 GdkDragContext *context)
2145 {
2146 DragIcon *icon;
2147
2148 icon = g_new0 (DragIcon, 1);
2149
2150 icon->window = gtk_window_new (GTK_WINDOW_POPUP);
2151 icon->context = context;
2152
2153 gtk_window_set_screen (GTK_WINDOW (icon->window),
2154 gtk_widget_get_screen (GTK_WIDGET (self)));
2155
2156 icon->width = predict_tab_width (self, self->reordered_tab, FALSE);
2157 icon->target_width = icon->width;
2158
2159 gtk_widget_set_app_paintable (icon->window, TRUE);
2160 gtk_window_set_resizable (GTK_WINDOW (icon->window), FALSE);
2161 gtk_window_set_decorated (GTK_WINDOW (icon->window), FALSE);
2162
2163 gtk_style_context_add_class (gtk_widget_get_style_context (icon->window),
2164 "tab-drag-icon");
2165
2166 icon->tab = hdy_tab_new (self->view, FALSE);
2167 hdy_tab_set_page (icon->tab, self->reordered_tab->page);
2168 hdy_tab_set_dragging (icon->tab, TRUE);
2169 hdy_tab_set_inverted (icon->tab, self->inverted);
2170 gtk_widget_show (GTK_WIDGET (icon->tab));
2171 gtk_widget_set_halign (GTK_WIDGET (icon->tab), GTK_ALIGN_START);
2172
2173 gtk_container_add (GTK_CONTAINER (icon->window), GTK_WIDGET (icon->tab));
2174
2175 gtk_style_context_get_margin (gtk_widget_get_style_context (GTK_WIDGET (icon->tab)),
2176 gtk_widget_get_state_flags (GTK_WIDGET (icon->tab)),
2177 &icon->tab_margin);
2178
2179 hdy_tab_set_display_width (icon->tab, icon->width);
2180 gtk_widget_set_size_request (GTK_WIDGET (icon->tab),
2181 icon->width + icon->tab_margin.left + icon->tab_margin.right,
2182 -1);
2183
2184 icon->hotspot_x = (gint) self->drag_offset_x;
2185 icon->hotspot_y = (gint) self->drag_offset_y;
2186
2187 gtk_drag_set_icon_widget (context, icon->window,
2188 icon->hotspot_x + icon->tab_margin.left,
2189 icon->hotspot_y + icon->tab_margin.top);
2190
2191 self->drag_icon = icon;
2192 }
2193
2194 static void
icon_resize_animation_value_cb(gdouble value,gpointer user_data)2195 icon_resize_animation_value_cb (gdouble value,
2196 gpointer user_data)
2197 {
2198 DragIcon *icon = user_data;
2199 gdouble relative_pos;
2200
2201 relative_pos = (gdouble) icon->hotspot_x / icon->width;
2202
2203 icon->width = (gint) round (value);
2204
2205 hdy_tab_set_display_width (icon->tab, icon->width);
2206 gtk_widget_set_size_request (GTK_WIDGET (icon->tab),
2207 icon->width + icon->tab_margin.left + icon->tab_margin.right,
2208 -1);
2209
2210 icon->hotspot_x = (gint) round (icon->width * relative_pos);
2211
2212 gdk_drag_context_set_hotspot (icon->context,
2213 icon->hotspot_x + icon->tab_margin.left,
2214 icon->hotspot_y + icon->tab_margin.top);
2215
2216 gtk_widget_queue_resize (GTK_WIDGET (icon->window));
2217 }
2218
2219 static void
icon_resize_animation_done_cb(gpointer user_data)2220 icon_resize_animation_done_cb (gpointer user_data)
2221 {
2222 DragIcon *icon = user_data;
2223
2224 g_clear_pointer (&icon->resize_animation, hdy_animation_unref);
2225 }
2226
2227 static void
resize_drag_icon(HdyTabBox * self,gint width)2228 resize_drag_icon (HdyTabBox *self,
2229 gint width)
2230 {
2231 DragIcon *icon = self->drag_icon;
2232
2233 if (width == icon->target_width)
2234 return;
2235
2236 if (icon->resize_animation)
2237 hdy_animation_stop (icon->resize_animation);
2238
2239 icon->target_width = width;
2240
2241 icon->resize_animation =
2242 hdy_animation_new (icon->window, icon->width, width,
2243 ICON_RESIZE_ANIMATION_DURATION,
2244 hdy_ease_out_cubic,
2245 icon_resize_animation_value_cb,
2246 icon_resize_animation_done_cb,
2247 icon);
2248
2249 hdy_animation_start (icon->resize_animation);
2250 }
2251
2252 /* Context menu */
2253
2254 static gboolean
reset_setup_menu_cb(HdyTabBox * self)2255 reset_setup_menu_cb (HdyTabBox *self)
2256 {
2257 g_signal_emit_by_name (self->view, "setup-menu", NULL);
2258
2259 return G_SOURCE_REMOVE;
2260 }
2261
2262 static void
touch_menu_notify_visible_cb(HdyTabBox * self)2263 touch_menu_notify_visible_cb (HdyTabBox *self)
2264 {
2265 if (!self->touch_menu || gtk_widget_get_visible (GTK_WIDGET (self->touch_menu)))
2266 return;
2267
2268 g_idle_add ((GSourceFunc) reset_setup_menu_cb, self);
2269 }
2270
2271 static void
destroy_cb(HdyTabBox * self)2272 destroy_cb (HdyTabBox *self)
2273 {
2274 self->touch_menu = NULL;
2275 }
2276
2277 static void
do_touch_popup(HdyTabBox * self,TabInfo * info)2278 do_touch_popup (HdyTabBox *self,
2279 TabInfo *info)
2280 {
2281 GMenuModel *model = hdy_tab_view_get_menu_model (self->view);
2282
2283 if (!G_IS_MENU_MODEL (model))
2284 return;
2285
2286 g_signal_emit_by_name (self->view, "setup-menu", info->page);
2287
2288 if (!self->touch_menu) {
2289 self->touch_menu = GTK_POPOVER (gtk_popover_new_from_model (GTK_WIDGET (info->tab), model));
2290
2291 g_signal_connect_object (self->touch_menu, "notify::visible",
2292 G_CALLBACK (touch_menu_notify_visible_cb), self,
2293 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2294
2295 g_signal_connect_object (self->touch_menu, "destroy",
2296 G_CALLBACK (destroy_cb), self,
2297 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2298 } else
2299 gtk_popover_set_relative_to (self->touch_menu, GTK_WIDGET (info->tab));
2300
2301 gtk_popover_popup (self->touch_menu);
2302 }
2303
2304 static void
touch_menu_gesture_pressed(HdyTabBox * self)2305 touch_menu_gesture_pressed (HdyTabBox *self)
2306 {
2307 end_dragging (self);
2308
2309 if (self->pressed_tab && self->pressed_tab->page) {
2310 do_touch_popup (self, self->pressed_tab);
2311 gtk_gesture_set_state (self->touch_menu_gesture,
2312 GTK_EVENT_SEQUENCE_CLAIMED);
2313 }
2314
2315 self->pressed = FALSE;
2316 self->pressed_tab = NULL;
2317 }
2318
2319 static void
popup_menu_detach(HdyTabBox * self,GtkMenu * menu)2320 popup_menu_detach (HdyTabBox *self,
2321 GtkMenu *menu)
2322 {
2323 self->context_menu = NULL;
2324 }
2325
2326 static void
popup_menu_deactivate_cb(HdyTabBox * self)2327 popup_menu_deactivate_cb (HdyTabBox *self)
2328 {
2329 self->hovering = FALSE;
2330 update_hover (self);
2331
2332 g_idle_add ((GSourceFunc) reset_setup_menu_cb, self);
2333 }
2334
2335 static void
do_popup(HdyTabBox * self,TabInfo * info,GdkEvent * event)2336 do_popup (HdyTabBox *self,
2337 TabInfo *info,
2338 GdkEvent *event)
2339 {
2340 GMenuModel *model = hdy_tab_view_get_menu_model (self->view);
2341
2342 if (!G_IS_MENU_MODEL (model))
2343 return;
2344
2345 g_signal_emit_by_name (self->view, "setup-menu", info->page);
2346
2347 if (!self->context_menu) {
2348 self->context_menu = GTK_MENU (gtk_menu_new_from_model (model));
2349 gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self->context_menu)),
2350 GTK_STYLE_CLASS_CONTEXT_MENU);
2351
2352 g_signal_connect_object (self->context_menu,
2353 "deactivate",
2354 G_CALLBACK (popup_menu_deactivate_cb),
2355 self,
2356 G_CONNECT_SWAPPED);
2357
2358 gtk_menu_attach_to_widget (self->context_menu, GTK_WIDGET (self),
2359 (GtkMenuDetachFunc) popup_menu_detach);
2360 }
2361
2362 if (event && gdk_event_triggers_context_menu (event))
2363 gtk_menu_popup_at_pointer (self->context_menu, event);
2364 else {
2365 GdkRectangle rect;
2366
2367 rect.x = info->pos;
2368 rect.y = gtk_widget_get_allocated_height (GTK_WIDGET (info->tab));
2369 rect.width = 0;
2370 rect.height = 0;
2371
2372 if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
2373 rect.x += info->width;
2374
2375 gtk_menu_popup_at_rect (self->context_menu,
2376 gtk_widget_get_window (GTK_WIDGET (self)),
2377 &rect,
2378 GDK_GRAVITY_SOUTH_WEST,
2379 GDK_GRAVITY_NORTH_WEST,
2380 event);
2381
2382 gtk_menu_shell_select_first (GTK_MENU_SHELL (self->context_menu), FALSE);
2383 }
2384 }
2385
2386 /* Overrides */
2387
2388 static void
hdy_tab_box_measure(GtkWidget * widget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline)2389 hdy_tab_box_measure (GtkWidget *widget,
2390 GtkOrientation orientation,
2391 gint for_size,
2392 gint *minimum,
2393 gint *natural,
2394 gint *minimum_baseline,
2395 gint *natural_baseline)
2396 {
2397 HdyTabBox *self = HDY_TAB_BOX (widget);
2398 gint min, nat;
2399
2400 if (self->n_tabs == 0) {
2401 if (minimum)
2402 *minimum = 0;
2403
2404 if (natural)
2405 *natural = 0;
2406
2407 if (minimum_baseline)
2408 *minimum_baseline = -1;
2409
2410 if (natural_baseline)
2411 *natural_baseline = -1;
2412
2413 return;
2414 }
2415
2416 if (orientation == GTK_ORIENTATION_HORIZONTAL) {
2417 gint width = self->end_padding - OVERLAP;
2418 GList *l;
2419
2420 for (l = self->tabs; l; l = l->next) {
2421 TabInfo *info = l->data;
2422 gint child_width;
2423
2424 gtk_widget_get_preferred_width (GTK_WIDGET (info->tab), NULL,
2425 &child_width);
2426
2427 width += calculate_tab_width (info, child_width) - OVERLAP;
2428 }
2429
2430 min = nat = MAX (self->last_width, width);
2431 } else {
2432 GList *l;
2433
2434 min = nat = 0;
2435
2436 for (l = self->tabs; l; l = l->next) {
2437 TabInfo *info = l->data;
2438 gint child_min, child_nat;
2439
2440 gtk_widget_get_preferred_height (GTK_WIDGET (info->tab),
2441 &child_min,
2442 &child_nat);
2443
2444 if (child_min > min)
2445 min = child_min;
2446
2447 if (child_nat > nat)
2448 nat = child_nat;
2449 }
2450 }
2451
2452 hdy_css_measure (widget, orientation, &min, &nat);
2453
2454 if (minimum)
2455 *minimum = min;
2456
2457 if (natural)
2458 *natural = nat;
2459
2460 if (minimum_baseline)
2461 *minimum_baseline = -1;
2462
2463 if (natural_baseline)
2464 *natural_baseline = -1;
2465 }
2466
2467 static void
hdy_tab_box_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)2468 hdy_tab_box_get_preferred_width (GtkWidget *widget,
2469 gint *minimum,
2470 gint *natural)
2471 {
2472 hdy_tab_box_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
2473 minimum, natural,
2474 NULL, NULL);
2475 }
2476
2477 static void
hdy_tab_box_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)2478 hdy_tab_box_get_preferred_height (GtkWidget *widget,
2479 gint *minimum,
2480 gint *natural)
2481 {
2482 hdy_tab_box_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
2483 minimum, natural,
2484 NULL, NULL);
2485 }
2486
2487 static void
hdy_tab_box_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum,gint * natural)2488 hdy_tab_box_get_preferred_width_for_height (GtkWidget *widget,
2489 gint height,
2490 gint *minimum,
2491 gint *natural)
2492 {
2493 hdy_tab_box_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
2494 minimum, natural,
2495 NULL, NULL);
2496 }
2497
2498 static void
hdy_tab_box_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum,gint * natural)2499 hdy_tab_box_get_preferred_height_for_width (GtkWidget *widget,
2500 gint width,
2501 gint *minimum,
2502 gint *natural)
2503 {
2504 hdy_tab_box_measure (widget, GTK_ORIENTATION_VERTICAL, width,
2505 minimum, natural,
2506 NULL, NULL);
2507 }
2508
2509 static void
hdy_tab_box_size_allocate(GtkWidget * widget,GtkAllocation * allocation)2510 hdy_tab_box_size_allocate (GtkWidget *widget,
2511 GtkAllocation *allocation)
2512 {
2513 HdyTabBox *self = HDY_TAB_BOX (widget);
2514 gboolean is_rtl;
2515 GList *l;
2516 GtkAllocation child_allocation;
2517 gint pos;
2518
2519 is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
2520
2521 hdy_css_size_allocate_self (widget, allocation);
2522
2523 GTK_WIDGET_CLASS (hdy_tab_box_parent_class)->size_allocate (widget, allocation);
2524
2525 if (gtk_widget_get_realized (widget))
2526 gdk_window_move_resize (self->window,
2527 allocation->x, allocation->y,
2528 allocation->width, allocation->height);
2529
2530 allocation->x = 0;
2531 allocation->y = 0;
2532 hdy_css_size_allocate_children (widget, allocation);
2533
2534 self->allocated_width = allocation->width;
2535
2536 if (!self->n_tabs)
2537 return;
2538
2539 if (self->pinned) {
2540 for (l = self->tabs; l; l = l->next) {
2541 TabInfo *info = l->data;
2542 gint child_width;
2543
2544 gtk_widget_get_preferred_width (GTK_WIDGET (info->tab), NULL, &child_width);
2545
2546 info->width = calculate_tab_width (info, child_width);
2547 }
2548 } else if (self->tab_resize_mode == TAB_RESIZE_FIXED_TAB_WIDTH) {
2549 self->end_padding = allocation->width + OVERLAP;
2550
2551 for (l = self->tabs; l; l = l->next) {
2552 TabInfo *info = l->data;
2553
2554 info->width = calculate_tab_width (info, info->last_width);
2555 self->end_padding -= info->width - OVERLAP;
2556 }
2557 } else {
2558 gint tab_width = get_base_tab_width (self, FALSE);
2559 gint excess = allocation->width + OVERLAP - self->end_padding;
2560
2561 for (l = self->tabs; l; l = l->next) {
2562 TabInfo *info = l->data;
2563
2564 info->width = calculate_tab_width (info, tab_width);
2565 excess -= info->width - OVERLAP;
2566 }
2567
2568 /* Now spread excess width across the tabs */
2569 for (l = self->tabs; l; l = l->next) {
2570 TabInfo *info = l->data;
2571
2572 if (excess >= 0)
2573 break;
2574
2575 info->width--;
2576 excess++;
2577 }
2578 }
2579
2580 pos = allocation->x + (is_rtl ? allocation->width + OVERLAP : -OVERLAP);
2581
2582 for (l = self->tabs; l; l = l->next) {
2583 TabInfo *info = l->data;
2584
2585 if (!info->appear_animation)
2586 hdy_tab_set_display_width (info->tab, info->width);
2587 else if (info->page && info != self->reorder_placeholder)
2588 hdy_tab_set_display_width (info->tab, predict_tab_width (self, info, FALSE));
2589
2590 info->pos = pos + calculate_tab_offset (self, info, FALSE);
2591
2592 if (is_rtl)
2593 info->pos -= info->width;
2594
2595 child_allocation.x = (info == self->reordered_tab) ? 0 : info->pos;
2596 child_allocation.y = allocation->y;
2597 child_allocation.width = info->width;
2598 child_allocation.height = allocation->height;
2599
2600 gtk_widget_size_allocate (GTK_WIDGET (info->tab), &child_allocation);
2601
2602 pos += (is_rtl ? -1 : 1) * (info->width - OVERLAP);
2603 }
2604
2605 if (self->scheduled_scroll.info) {
2606 scroll_to_tab_full (self,
2607 self->scheduled_scroll.info,
2608 self->scheduled_scroll.pos,
2609 self->scheduled_scroll.duration,
2610 self->scheduled_scroll.keep_selected_visible);
2611 self->scheduled_scroll.info = NULL;
2612 }
2613
2614 if (self->scroll_animation) {
2615 hdy_tab_box_set_block_scrolling (self, TRUE);
2616 gtk_adjustment_set_value (self->adjustment,
2617 get_scroll_animation_value (self));
2618 hdy_tab_box_set_block_scrolling (self, FALSE);
2619
2620 if (self->scroll_animation_done) {
2621 self->scroll_animation_done = FALSE;
2622 self->scroll_animation_tab = NULL;
2623 g_clear_pointer (&self->scroll_animation, hdy_animation_unref);
2624 }
2625 }
2626
2627 update_hover (self);
2628 update_visible (self);
2629 }
2630
2631 static gboolean
hdy_tab_box_focus(GtkWidget * widget,GtkDirectionType direction)2632 hdy_tab_box_focus (GtkWidget *widget,
2633 GtkDirectionType direction)
2634 {
2635 HdyTabBox *self = HDY_TAB_BOX (widget);
2636
2637 if (!self->selected_tab)
2638 return GDK_EVENT_PROPAGATE;
2639
2640 return gtk_widget_child_focus (GTK_WIDGET (self->selected_tab->tab), direction);
2641 }
2642
2643 static void
hdy_tab_box_realize(GtkWidget * widget)2644 hdy_tab_box_realize (GtkWidget *widget)
2645 {
2646 HdyTabBox *self = HDY_TAB_BOX (widget);
2647 GtkAllocation allocation;
2648 GdkWindowAttr attributes;
2649 GdkWindowAttributesType attributes_mask;
2650 GList *l;
2651
2652 gtk_widget_set_realized (widget, TRUE);
2653
2654 gtk_widget_get_allocation (widget, &allocation);
2655
2656 attributes.x = allocation.x;
2657 attributes.y = allocation.y;
2658 attributes.width = allocation.width;
2659 attributes.height = allocation.height;
2660 attributes.window_type = GDK_WINDOW_CHILD;
2661 attributes.wclass = GDK_INPUT_OUTPUT;
2662 attributes.visual = gtk_widget_get_visual (widget);
2663 attributes.event_mask = gtk_widget_get_events (widget);
2664 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
2665
2666 self->window = gdk_window_new (gtk_widget_get_parent_window (widget),
2667 &attributes,
2668 attributes_mask);
2669
2670 gtk_widget_set_window (widget, self->window);
2671 gtk_widget_register_window (widget, self->window);
2672
2673 self->reorder_window = gdk_window_new (self->window, &attributes, attributes_mask);
2674 gtk_widget_register_window (widget, self->reorder_window);
2675
2676 for (l = self->tabs; l; l = l->next) {
2677 TabInfo *info = l->data;
2678
2679 gtk_widget_set_parent_window (GTK_WIDGET (info->tab), self->window);
2680 }
2681 }
2682
2683 static void
hdy_tab_box_unrealize(GtkWidget * widget)2684 hdy_tab_box_unrealize (GtkWidget *widget)
2685 {
2686 HdyTabBox *self = HDY_TAB_BOX (widget);
2687
2688 self->window = NULL;
2689
2690 if (self->reorder_window) {
2691 gtk_widget_unregister_window (widget, self->reorder_window);
2692 gdk_window_destroy (self->reorder_window);
2693 self->reorder_window = NULL;
2694 }
2695
2696 if (self->context_menu) {
2697 gtk_widget_destroy (GTK_WIDGET (self->context_menu));
2698 self->context_menu = NULL;
2699 }
2700
2701 if (self->touch_menu) {
2702 gtk_widget_destroy (GTK_WIDGET (self->touch_menu));
2703 self->touch_menu = NULL;
2704 }
2705
2706 GTK_WIDGET_CLASS (hdy_tab_box_parent_class)->unrealize (widget);
2707 }
2708
2709 static void
hdy_tab_box_map(GtkWidget * widget)2710 hdy_tab_box_map (GtkWidget *widget)
2711 {
2712 HdyTabBox *self = HDY_TAB_BOX (widget);
2713
2714 GTK_WIDGET_CLASS (hdy_tab_box_parent_class)->map (widget);
2715
2716 gdk_window_show_unraised (self->window);
2717
2718 if (self->reordered_tab)
2719 gdk_window_show (self->reorder_window);
2720 }
2721
2722 static void
hdy_tab_box_unmap(GtkWidget * widget)2723 hdy_tab_box_unmap (GtkWidget *widget)
2724 {
2725 HdyTabBox *self = HDY_TAB_BOX (widget);
2726
2727 force_end_reordering (self);
2728
2729 if (self->drag_autoscroll_cb_id) {
2730 gtk_widget_remove_tick_callback (widget, self->drag_autoscroll_cb_id);
2731 self->drag_autoscroll_cb_id = 0;
2732 }
2733
2734 if (self->reordered_tab)
2735 gdk_window_hide (self->reorder_window);
2736
2737 self->hovering = FALSE;
2738 update_hover (self);
2739
2740 gdk_window_hide (self->window);
2741
2742 GTK_WIDGET_CLASS (hdy_tab_box_parent_class)->unmap (widget);
2743 }
2744
2745 static void
hdy_tab_box_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)2746 hdy_tab_box_direction_changed (GtkWidget *widget,
2747 GtkTextDirection previous_direction)
2748 {
2749 HdyTabBox *self = HDY_TAB_BOX (widget);
2750 gdouble upper, page_size;
2751
2752 if (!self->adjustment)
2753 return;
2754
2755 if (gtk_widget_get_direction (widget) == previous_direction)
2756 return;
2757
2758 upper = gtk_adjustment_get_upper (self->adjustment);
2759 page_size = gtk_adjustment_get_page_size (self->adjustment);
2760
2761 gtk_adjustment_set_value (self->adjustment,
2762 upper - page_size - self->adjustment_prev_value);
2763 }
2764
2765 static gboolean
hdy_tab_box_draw(GtkWidget * widget,cairo_t * cr)2766 hdy_tab_box_draw (GtkWidget *widget,
2767 cairo_t *cr)
2768 {
2769 HdyTabBox *self = HDY_TAB_BOX (widget);
2770
2771 if (!self->n_tabs)
2772 return GDK_EVENT_PROPAGATE;
2773
2774 hdy_css_draw (widget, cr);
2775
2776 return GTK_WIDGET_CLASS (hdy_tab_box_parent_class)->draw (widget, cr);
2777 }
2778
2779 static gboolean
hdy_tab_box_popup_menu(GtkWidget * widget)2780 hdy_tab_box_popup_menu (GtkWidget *widget)
2781 {
2782 HdyTabBox *self = HDY_TAB_BOX (widget);
2783
2784 if (self->selected_tab && self->selected_tab->page) {
2785 do_popup (self, self->selected_tab, NULL);
2786
2787 return GDK_EVENT_STOP;
2788 }
2789
2790 return GDK_EVENT_PROPAGATE;
2791 }
2792
2793 static gboolean
hdy_tab_box_enter_notify_event(GtkWidget * widget,GdkEventCrossing * event)2794 hdy_tab_box_enter_notify_event (GtkWidget *widget,
2795 GdkEventCrossing *event)
2796 {
2797 HdyTabBox *self = HDY_TAB_BOX (widget);
2798
2799 if (event->window != self->window || event->detail == GDK_NOTIFY_INFERIOR)
2800 return GDK_EVENT_PROPAGATE;
2801
2802 /* enter-notify never happens on touch, so we don't need to check it */
2803 self->hovering = TRUE;
2804
2805 get_widget_coordinates (self, (GdkEvent *) event, &self->hover_x, &self->hover_y);
2806 update_hover (self);
2807
2808 return GDK_EVENT_PROPAGATE;
2809 }
2810
2811 static gboolean
hdy_tab_box_leave_notify_event(GtkWidget * widget,GdkEventCrossing * event)2812 hdy_tab_box_leave_notify_event (GtkWidget *widget,
2813 GdkEventCrossing *event)
2814 {
2815 HdyTabBox *self = HDY_TAB_BOX (widget);
2816
2817 if (event->window != self->window || event->detail == GDK_NOTIFY_INFERIOR)
2818 return GDK_EVENT_PROPAGATE;
2819
2820 self->hovering = FALSE;
2821 update_hover (self);
2822
2823 return GDK_EVENT_PROPAGATE;
2824 }
2825
2826 static gboolean
hdy_tab_box_motion_notify_event(GtkWidget * widget,GdkEventMotion * event)2827 hdy_tab_box_motion_notify_event (GtkWidget *widget,
2828 GdkEventMotion *event)
2829 {
2830 HdyTabBox *self = HDY_TAB_BOX (widget);
2831 GdkDevice *source_device;
2832 GdkInputSource input_source;
2833
2834 source_device = gdk_event_get_source_device ((GdkEvent *) event);
2835 input_source = gdk_device_get_source (source_device);
2836
2837 if (input_source != GDK_SOURCE_TOUCHSCREEN)
2838 self->hovering = TRUE;
2839
2840 get_widget_coordinates (self, (GdkEvent *) event, &self->hover_x, &self->hover_y);
2841
2842 update_hover (self);
2843
2844 if (!self->pressed)
2845 return GDK_EVENT_PROPAGATE;
2846
2847 if (self->pressed_tab &&
2848 self->pressed_button == GDK_BUTTON_PRIMARY &&
2849 gtk_drag_check_threshold (widget,
2850 (gint) self->drag_begin_x,
2851 (gint) self->drag_begin_y,
2852 (gint) self->hover_x,
2853 (gint) self->hover_y))
2854 start_dragging (self, (GdkEvent *) event, self->pressed_tab);
2855
2856 if (!self->dragging)
2857 return GDK_EVENT_PROPAGATE;
2858
2859 self->reorder_x = (gint) round (self->hover_x - self->drag_offset_x);
2860 self->reorder_y = (gint) round (self->hover_y - self->drag_offset_y);
2861
2862 if (!self->pinned &&
2863 self->pressed_tab &&
2864 self->pressed_tab != self->reorder_placeholder &&
2865 self->pressed_tab->page &&
2866 input_source != GDK_SOURCE_TOUCHSCREEN &&
2867 hdy_tab_view_get_n_pages (self->view) > 1 &&
2868 check_dnd_threshold (self)) {
2869 gtk_drag_begin_with_coordinates (widget,
2870 self->source_targets,
2871 GDK_ACTION_MOVE,
2872 (gint) self->pressed_button,
2873 (GdkEvent *) event,
2874 self->reorder_x,
2875 self->reorder_y);
2876
2877 return GDK_EVENT_STOP;
2878 }
2879
2880 update_dragging (self);
2881
2882 return GDK_EVENT_STOP;
2883 }
2884
2885 static gboolean
hdy_tab_box_button_press_event(GtkWidget * widget,GdkEventButton * event)2886 hdy_tab_box_button_press_event (GtkWidget *widget,
2887 GdkEventButton *event)
2888 {
2889 HdyTabBox *self = HDY_TAB_BOX (widget);
2890 gboolean can_grab_focus;
2891
2892 check_stuck_dragging (self);
2893
2894 get_widget_coordinates (self, (GdkEvent *) event, &self->hover_x, &self->hover_y);
2895
2896 update_hover (self);
2897
2898 self->pressed_tab = find_tab_info_at (self, self->hover_x);
2899 self->pressed = TRUE;
2900
2901 if (!self->pressed_tab || !self->pressed_tab->page)
2902 return GDK_EVENT_PROPAGATE;
2903
2904 if (gdk_event_triggers_context_menu ((GdkEvent *) event)) {
2905 do_popup (self, self->pressed_tab, (GdkEvent *) event);
2906
2907 return GDK_EVENT_STOP;
2908 }
2909
2910 self->pressed_button = event->button;
2911
2912 if (self->pressed_button == GDK_BUTTON_MIDDLE) {
2913 hdy_tab_view_close_page (self->view, self->pressed_tab->page);
2914
2915 return GDK_EVENT_STOP;
2916 }
2917
2918 if (self->pressed_button != GDK_BUTTON_PRIMARY)
2919 return GDK_EVENT_PROPAGATE;
2920
2921 if (self->adjustment) {
2922 gint pos = get_tab_position (self, self->pressed_tab);
2923 gdouble value = gtk_adjustment_get_value (self->adjustment);
2924 gdouble page_size = gtk_adjustment_get_page_size (self->adjustment);
2925
2926 if (pos + OVERLAP < value ||
2927 pos + self->pressed_tab->width - OVERLAP > value + page_size) {
2928 scroll_to_tab (self, self->pressed_tab, SCROLL_ANIMATION_DURATION);
2929
2930 return GDK_EVENT_PROPAGATE;
2931 }
2932 }
2933
2934 can_grab_focus = hdy_tab_bar_tabs_have_visible_focus (self->tab_bar);
2935
2936 if (self->pressed_tab == self->selected_tab)
2937 can_grab_focus = TRUE;
2938 else
2939 hdy_tab_view_set_selected_page (self->view, self->pressed_tab->page);
2940
2941 if (can_grab_focus)
2942 gtk_widget_grab_focus (GTK_WIDGET (self->pressed_tab->tab));
2943 else
2944 activate_tab (self);
2945
2946 self->drag_begin_x = self->hover_x;
2947 self->drag_begin_y = self->hover_y;
2948 self->drag_offset_x = self->drag_begin_x - get_tab_position (self, self->pressed_tab);
2949 self->drag_offset_y = self->drag_begin_y;
2950
2951 if (!self->reorder_animation) {
2952 self->reorder_x = (gint) round (self->hover_x - self->drag_offset_x);
2953 self->reorder_y = (gint) round (self->hover_y - self->drag_offset_y);
2954 }
2955
2956 return GDK_EVENT_PROPAGATE;
2957 }
2958
2959 static gboolean
hdy_tab_box_button_release_event(GtkWidget * widget,GdkEventButton * event)2960 hdy_tab_box_button_release_event (GtkWidget *widget,
2961 GdkEventButton *event)
2962 {
2963 HdyTabBox *self = HDY_TAB_BOX (widget);
2964
2965 self->pressed = FALSE;
2966 self->pressed_button = 0;
2967
2968 end_dragging (self);
2969
2970 return GDK_EVENT_PROPAGATE;
2971 }
2972
2973 static gboolean
hdy_tab_box_scroll_event(GtkWidget * widget,GdkEventScroll * event)2974 hdy_tab_box_scroll_event (GtkWidget *widget,
2975 GdkEventScroll *event)
2976 {
2977 HdyTabBox *self = HDY_TAB_BOX (widget);
2978 gdouble page_size, pow_unit, scroll_unit;
2979 GdkDevice *source_device;
2980 GdkInputSource input_source;
2981 gdouble dx, dy;
2982
2983 if (!self->adjustment)
2984 return GDK_EVENT_PROPAGATE;
2985
2986 source_device = gdk_event_get_source_device ((GdkEvent *) event);
2987 input_source = gdk_device_get_source (source_device);
2988
2989 if (input_source != GDK_SOURCE_MOUSE)
2990 return GDK_EVENT_PROPAGATE;
2991
2992 if (!gdk_event_get_scroll_deltas ((GdkEvent *) event, &dx, &dy)) {
2993 switch (event->direction) {
2994 case GDK_SCROLL_UP:
2995 dy = -1;
2996 break;
2997
2998 case GDK_SCROLL_DOWN:
2999 dy = 1;
3000 break;
3001
3002 case GDK_SCROLL_LEFT:
3003 dx = -1;
3004 break;
3005
3006 case GDK_SCROLL_RIGHT:
3007 dx = 1;
3008 break;
3009
3010 case GDK_SCROLL_SMOOTH:
3011 default:
3012 g_assert_not_reached ();
3013 }
3014 }
3015
3016 if (dx != 0)
3017 return GDK_EVENT_PROPAGATE;
3018
3019 page_size = gtk_adjustment_get_page_size (self->adjustment);
3020
3021 /* Copied from gtkrange.c, _gtk_range_get_wheel_delta() */
3022 pow_unit = pow (page_size, 2.0 / 3.0);
3023 scroll_unit = MIN (pow_unit, page_size / 2.0);
3024
3025 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
3026 dy = -dy;
3027
3028 animate_scroll_relative (self, dy * scroll_unit, SCROLL_ANIMATION_DURATION);
3029
3030 return GDK_EVENT_PROPAGATE;
3031 }
3032
3033 static void
hdy_tab_box_drag_begin(GtkWidget * widget,GdkDragContext * context)3034 hdy_tab_box_drag_begin (GtkWidget *widget,
3035 GdkDragContext *context)
3036 {
3037 HdyTabBox *self = HDY_TAB_BOX (widget);
3038 TabInfo *detached_info;
3039 HdyTab *detached_tab;
3040
3041 if (self->pinned)
3042 return;
3043
3044 create_drag_icon (self, context);
3045
3046 self->hovering = TRUE;
3047 self->pressed = FALSE;
3048 self->pressed_button = 0;
3049 self->pressed_tab = NULL;
3050
3051 detached_info = self->reordered_tab;
3052 detached_tab = g_object_ref (detached_info->tab);
3053 self->detached_page = detached_info->page;
3054
3055 self->indirect_reordering = TRUE;
3056
3057 end_dragging (self);
3058 update_hover (self);
3059
3060 gtk_widget_set_opacity (GTK_WIDGET (detached_tab), 0);
3061 self->detached_index = hdy_tab_view_get_page_position (self->view, self->detached_page);
3062
3063 hdy_tab_view_detach_page (self->view, self->detached_page);
3064
3065 self->indirect_reordering = FALSE;
3066
3067 gtk_widget_get_preferred_width (GTK_WIDGET (detached_tab), NULL, &self->placeholder_scroll_offset);
3068 self->placeholder_scroll_offset /= 2;
3069
3070 animate_scroll_relative (self, -self->placeholder_scroll_offset, CLOSE_ANIMATION_DURATION);
3071
3072 g_object_unref (detached_tab);
3073 }
3074
3075 static void
hdy_tab_box_drag_end(GtkWidget * widget,GdkDragContext * context)3076 hdy_tab_box_drag_end (GtkWidget *widget,
3077 GdkDragContext *context)
3078 {
3079 HdyTabBox *self = HDY_TAB_BOX (widget);
3080
3081 if (self->pinned)
3082 return;
3083
3084 if (self->should_detach_into_new_window)
3085 detach_into_new_window (self, context);
3086
3087 self->detached_page = NULL;
3088
3089 if (self->drag_icon) {
3090 gtk_widget_destroy (self->drag_icon->window);
3091 g_clear_pointer (&self->drag_icon, g_free);
3092 }
3093 }
3094
3095 static gboolean
reset_drop_target_tab_cb(HdyTabBox * self)3096 reset_drop_target_tab_cb (HdyTabBox *self)
3097 {
3098 self->reset_drop_target_tab_id = 0;
3099 set_drop_target_tab (self, NULL, FALSE);
3100
3101 return G_SOURCE_REMOVE;
3102 }
3103
3104 static gboolean
hdy_tab_box_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)3105 hdy_tab_box_drag_motion (GtkWidget *widget,
3106 GdkDragContext *context,
3107 gint x,
3108 gint y,
3109 guint time)
3110 {
3111 HdyTabBox *self = HDY_TAB_BOX (widget);
3112 HdyTabBox *source_tab_box;
3113 GdkAtom target, tab_target;
3114
3115 target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL);
3116 tab_target = gdk_atom_intern_static_string ("HDY_TAB");
3117
3118 if (target != tab_target) {
3119 GdkAtom none_target = gdk_atom_intern_static_string ("NONE");
3120 TabInfo *info = find_tab_info_at (self, x);
3121
3122 if (!info) {
3123 if (!self->reset_drop_target_tab_id)
3124 self->reset_drop_target_tab_id =
3125 g_idle_add ((GSourceFunc) reset_drop_target_tab_cb, self);
3126
3127 end_autoscroll (self);
3128
3129 gdk_drag_status (context, 0, time);
3130
3131 return GDK_EVENT_STOP;
3132 }
3133
3134 self->drop_target_x = x;
3135 self->can_accept_drop = target != none_target;
3136 set_drop_target_tab (self, info, self->can_accept_drop);
3137
3138 start_autoscroll (self);
3139
3140 return GDK_EVENT_STOP;
3141 }
3142
3143 if (self->pinned)
3144 return GDK_EVENT_PROPAGATE;
3145
3146 source_tab_box = get_source_tab_box (context);
3147
3148 if (!source_tab_box)
3149 return GDK_EVENT_PROPAGATE;
3150
3151 if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
3152 return GDK_EVENT_PROPAGATE;
3153
3154 self->can_remove_placeholder = FALSE;
3155
3156 if (!self->reorder_placeholder || !self->reorder_placeholder->page) {
3157 HdyTabPage *page = source_tab_box->detached_page;
3158 gdouble center = x - source_tab_box->drag_icon->hotspot_x + source_tab_box->drag_icon->width / 2;
3159
3160 insert_placeholder (self, page, center);
3161
3162 self->indirect_reordering = TRUE;
3163
3164 resize_drag_icon (source_tab_box, predict_tab_width (self, self->reorder_placeholder, TRUE));
3165 hdy_tab_set_display_width (self->reorder_placeholder->tab, source_tab_box->drag_icon->target_width);
3166 hdy_tab_set_inverted (source_tab_box->drag_icon->tab, self->inverted);
3167
3168 self->drag_offset_x = source_tab_box->drag_icon->hotspot_x;
3169 self->drag_offset_y = source_tab_box->drag_icon->hotspot_y;
3170
3171 self->reorder_x = (gint) round (x - source_tab_box->drag_icon->hotspot_x);
3172
3173 start_dragging (self, gtk_get_current_event (), self->reorder_placeholder);
3174
3175 gdk_drag_status (context, GDK_ACTION_MOVE, time);
3176
3177 return GDK_EVENT_STOP;
3178 }
3179
3180 self->reorder_x = (gint) round (x - source_tab_box->drag_icon->hotspot_x);
3181
3182 update_dragging (self);
3183
3184 gdk_drag_status (context, GDK_ACTION_MOVE, time);
3185
3186 return GDK_EVENT_STOP;
3187 }
3188
3189 static void
hdy_tab_box_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time)3190 hdy_tab_box_drag_leave (GtkWidget *widget,
3191 GdkDragContext *context,
3192 guint time)
3193 {
3194 HdyTabBox *self = HDY_TAB_BOX (widget);
3195 HdyTabBox *source_tab_box;
3196 GdkAtom target, tab_target;
3197
3198 target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL);
3199 tab_target = gdk_atom_intern_static_string ("HDY_TAB");
3200
3201 if (target != tab_target) {
3202 if (!self->reset_drop_target_tab_id)
3203 self->reset_drop_target_tab_id =
3204 g_idle_add ((GSourceFunc) reset_drop_target_tab_cb, self);
3205
3206 end_autoscroll (self);
3207
3208 return;
3209 }
3210
3211 if (!self->indirect_reordering)
3212 return;
3213
3214 if (self->pinned)
3215 return;
3216
3217 source_tab_box = get_source_tab_box (context);
3218
3219 if (!source_tab_box)
3220 return;
3221
3222 if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
3223 return;
3224
3225 self->can_remove_placeholder = TRUE;
3226
3227 end_dragging (self);
3228 remove_placeholder (self);
3229
3230 self->indirect_reordering = FALSE;
3231 }
3232
3233 static gboolean
hdy_tab_box_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)3234 hdy_tab_box_drag_drop (GtkWidget *widget,
3235 GdkDragContext *context,
3236 gint x,
3237 gint y,
3238 guint time)
3239 {
3240 HdyTabBox *self = HDY_TAB_BOX (widget);
3241 HdyTabBox *source_tab_box;
3242 GdkAtom target, tab_target;
3243
3244 target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL);
3245 tab_target = gdk_atom_intern_static_string ("HDY_TAB");
3246
3247 if (target != tab_target) {
3248 g_clear_handle_id (&self->reset_drop_target_tab_id, g_source_remove);
3249
3250 gtk_drag_get_data (widget, context, target, time);
3251
3252 return GDK_EVENT_STOP;
3253 }
3254
3255 if (self->pinned)
3256 return GDK_EVENT_PROPAGATE;
3257
3258 source_tab_box = get_source_tab_box (context);
3259
3260 if (!source_tab_box)
3261 return GDK_EVENT_PROPAGATE;
3262
3263 if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
3264 return GDK_EVENT_PROPAGATE;
3265
3266 return do_drag_drop (self, context, time);
3267 }
3268
3269 static gboolean
hdy_tab_box_drag_failed(GtkWidget * widget,GdkDragContext * context,GtkDragResult result)3270 hdy_tab_box_drag_failed (GtkWidget *widget,
3271 GdkDragContext *context,
3272 GtkDragResult result)
3273 {
3274 HdyTabBox *self = HDY_TAB_BOX (widget);
3275
3276 self->should_detach_into_new_window = FALSE;
3277
3278 if (result == GTK_DRAG_RESULT_NO_TARGET) {
3279 detach_into_new_window (self, context);
3280
3281 return GDK_EVENT_STOP;
3282 }
3283
3284 hdy_tab_view_attach_page (self->view,
3285 self->detached_page,
3286 self->detached_index);
3287
3288 self->indirect_reordering = FALSE;
3289
3290 return GDK_EVENT_STOP;
3291 }
3292
3293 static void
hdy_tab_box_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * data,guint info,guint time)3294 hdy_tab_box_drag_data_get (GtkWidget *widget,
3295 GdkDragContext *context,
3296 GtkSelectionData *data,
3297 guint info,
3298 guint time)
3299 {
3300 HdyTabBox *self = HDY_TAB_BOX (widget);
3301 GdkAtom target, rootwindow_target;
3302
3303 target = gtk_selection_data_get_target (data);
3304 rootwindow_target = gdk_atom_intern_static_string ("application/x-rootwindow-drop");
3305
3306 if (target == rootwindow_target) {
3307 self->should_detach_into_new_window = TRUE;
3308 gtk_selection_data_set (data, target, 8, NULL, 0);
3309 }
3310 }
3311
3312 static void
hdy_tab_box_drag_data_received(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * selection_data,guint info,guint time)3313 hdy_tab_box_drag_data_received (GtkWidget *widget,
3314 GdkDragContext *context,
3315 int x,
3316 int y,
3317 GtkSelectionData *selection_data,
3318 guint info,
3319 guint time)
3320 {
3321 HdyTabBox *self = HDY_TAB_BOX (widget);
3322 TabInfo *tab_info = find_tab_info_at (self, x);
3323
3324 g_assert (tab_info);
3325
3326 g_signal_emit (self, signals[SIGNAL_EXTRA_DRAG_DATA_RECEIVED], 0,
3327 tab_info->page,
3328 context, selection_data, info, time);
3329
3330 set_drop_target_tab (self, NULL, FALSE);
3331 }
3332
3333 static void
hdy_tab_box_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)3334 hdy_tab_box_forall (GtkContainer *container,
3335 gboolean include_internals,
3336 GtkCallback callback,
3337 gpointer callback_data)
3338 {
3339 HdyTabBox *self = HDY_TAB_BOX (container);
3340 GList *l;
3341
3342 if (!include_internals)
3343 return;
3344
3345 for (l = self->tabs; l; l = l->next) {
3346 TabInfo *info = l->data;
3347
3348 callback (GTK_WIDGET (info->tab), callback_data);
3349 }
3350 }
3351
3352 static void
hdy_tab_box_dispose(GObject * object)3353 hdy_tab_box_dispose (GObject *object)
3354 {
3355 HdyTabBox *self = HDY_TAB_BOX (object);
3356
3357 g_clear_handle_id (&self->drop_switch_timeout_id, g_source_remove);
3358
3359 self->tab_bar = NULL;
3360 hdy_tab_box_set_view (self, NULL);
3361 hdy_tab_box_set_adjustment (self, NULL);
3362
3363 G_OBJECT_CLASS (hdy_tab_box_parent_class)->dispose (object);
3364 }
3365
3366 static void
hdy_tab_box_finalize(GObject * object)3367 hdy_tab_box_finalize (GObject *object)
3368 {
3369 HdyTabBox *self = (HdyTabBox *) object;
3370
3371 g_clear_object (&self->touch_menu_gesture);
3372 g_clear_pointer (&self->source_targets, gtk_target_list_unref);
3373
3374 G_OBJECT_CLASS (hdy_tab_box_parent_class)->finalize (object);
3375 }
3376
3377 static void
hdy_tab_box_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)3378 hdy_tab_box_get_property (GObject *object,
3379 guint prop_id,
3380 GValue *value,
3381 GParamSpec *pspec)
3382 {
3383 HdyTabBox *self = HDY_TAB_BOX (object);
3384
3385 switch (prop_id) {
3386 case PROP_PINNED:
3387 g_value_set_boolean (value, self->pinned);
3388 break;
3389
3390 case PROP_TAB_BAR:
3391 g_value_set_object (value, self->tab_bar);
3392 break;
3393
3394 case PROP_VIEW:
3395 g_value_set_object (value, self->view);
3396 break;
3397
3398 case PROP_ADJUSTMENT:
3399 g_value_set_object (value, self->adjustment);
3400 break;
3401
3402 case PROP_NEEDS_ATTENTION_LEFT:
3403 g_value_set_boolean (value, self->needs_attention_left);
3404 break;
3405
3406 case PROP_NEEDS_ATTENTION_RIGHT:
3407 g_value_set_boolean (value, self->needs_attention_right);
3408 break;
3409
3410 case PROP_RESIZE_FROZEN:
3411 g_value_set_boolean (value, self->tab_resize_mode != TAB_RESIZE_NORMAL);
3412 break;
3413
3414 default:
3415 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3416 }
3417 }
3418
3419 static void
hdy_tab_box_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)3420 hdy_tab_box_set_property (GObject *object,
3421 guint prop_id,
3422 const GValue *value,
3423 GParamSpec *pspec)
3424 {
3425 HdyTabBox *self = HDY_TAB_BOX (object);
3426
3427 switch (prop_id) {
3428 case PROP_PINNED:
3429 self->pinned = g_value_get_boolean (value);
3430 break;
3431
3432 case PROP_TAB_BAR:
3433 self->tab_bar = g_value_get_object (value);
3434 break;
3435
3436 case PROP_VIEW:
3437 hdy_tab_box_set_view (self, g_value_get_object (value));
3438 break;
3439
3440 case PROP_ADJUSTMENT:
3441 hdy_tab_box_set_adjustment (self, g_value_get_object (value));
3442 break;
3443
3444 default:
3445 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3446 }
3447 }
3448
3449 static void
hdy_tab_box_class_init(HdyTabBoxClass * klass)3450 hdy_tab_box_class_init (HdyTabBoxClass *klass)
3451 {
3452 GObjectClass *object_class = G_OBJECT_CLASS (klass);
3453 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
3454 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
3455 GtkBindingSet *binding_set;
3456
3457 object_class->dispose = hdy_tab_box_dispose;
3458 object_class->finalize = hdy_tab_box_finalize;
3459 object_class->get_property = hdy_tab_box_get_property;
3460 object_class->set_property = hdy_tab_box_set_property;
3461
3462 widget_class->get_preferred_width = hdy_tab_box_get_preferred_width;
3463 widget_class->get_preferred_height = hdy_tab_box_get_preferred_height;
3464 widget_class->get_preferred_width_for_height = hdy_tab_box_get_preferred_width_for_height;
3465 widget_class->get_preferred_height_for_width = hdy_tab_box_get_preferred_height_for_width;
3466 widget_class->size_allocate = hdy_tab_box_size_allocate;
3467 widget_class->focus = hdy_tab_box_focus;
3468 widget_class->realize = hdy_tab_box_realize;
3469 widget_class->unrealize = hdy_tab_box_unrealize;
3470 widget_class->map = hdy_tab_box_map;
3471 widget_class->unmap = hdy_tab_box_unmap;
3472 widget_class->direction_changed = hdy_tab_box_direction_changed;
3473 widget_class->draw = hdy_tab_box_draw;
3474 widget_class->popup_menu = hdy_tab_box_popup_menu;
3475 widget_class->enter_notify_event = hdy_tab_box_enter_notify_event;
3476 widget_class->leave_notify_event = hdy_tab_box_leave_notify_event;
3477 widget_class->motion_notify_event = hdy_tab_box_motion_notify_event;
3478 widget_class->button_press_event = hdy_tab_box_button_press_event;
3479 widget_class->button_release_event = hdy_tab_box_button_release_event;
3480 widget_class->scroll_event = hdy_tab_box_scroll_event;
3481 widget_class->drag_begin = hdy_tab_box_drag_begin;
3482 widget_class->drag_end = hdy_tab_box_drag_end;
3483 widget_class->drag_motion = hdy_tab_box_drag_motion;
3484 widget_class->drag_leave = hdy_tab_box_drag_leave;
3485 widget_class->drag_drop = hdy_tab_box_drag_drop;
3486 widget_class->drag_failed = hdy_tab_box_drag_failed;
3487 widget_class->drag_data_get = hdy_tab_box_drag_data_get;
3488 widget_class->drag_data_received = hdy_tab_box_drag_data_received;
3489
3490 container_class->forall = hdy_tab_box_forall;
3491
3492 props[PROP_PINNED] =
3493 g_param_spec_boolean ("pinned",
3494 _("Pinned"),
3495 _("Pinned"),
3496 FALSE,
3497 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
3498
3499 props[PROP_TAB_BAR] =
3500 g_param_spec_object ("tab-bar",
3501 _("Tab Bar"),
3502 _("Tab Bar"),
3503 HDY_TYPE_TAB_BAR,
3504 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
3505
3506 props[PROP_VIEW] =
3507 g_param_spec_object ("view",
3508 _("View"),
3509 _("View"),
3510 HDY_TYPE_TAB_VIEW,
3511 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3512
3513 props[PROP_ADJUSTMENT] =
3514 g_param_spec_object ("adjustment",
3515 _("Adjustment"),
3516 _("Adjustment"),
3517 GTK_TYPE_ADJUSTMENT,
3518 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3519
3520 props[PROP_NEEDS_ATTENTION_LEFT] =
3521 g_param_spec_boolean ("needs-attention-left",
3522 _("Needs Attention Left"),
3523 _("Needs Attention Left"),
3524 FALSE,
3525 G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
3526
3527 props[PROP_NEEDS_ATTENTION_RIGHT] =
3528 g_param_spec_boolean ("needs-attention-right",
3529 _("Needs Attention Right"),
3530 _("Needs Attention Right"),
3531 FALSE,
3532 G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
3533
3534 props[PROP_RESIZE_FROZEN] =
3535 g_param_spec_boolean ("resize-frozen",
3536 _("Resize Frozen"),
3537 _("Resize Frozen"),
3538 FALSE,
3539 G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
3540
3541 g_object_class_install_properties (object_class, LAST_PROP, props);
3542
3543 signals[SIGNAL_STOP_KINETIC_SCROLLING] =
3544 g_signal_new ("stop-kinetic-scrolling",
3545 G_TYPE_FROM_CLASS (klass),
3546 G_SIGNAL_RUN_LAST,
3547 0,
3548 NULL, NULL, NULL,
3549 G_TYPE_NONE,
3550 0);
3551
3552 signals[SIGNAL_EXTRA_DRAG_DATA_RECEIVED] =
3553 g_signal_new ("extra-drag-data-received",
3554 G_TYPE_FROM_CLASS (klass),
3555 G_SIGNAL_RUN_LAST,
3556 0,
3557 NULL, NULL, NULL,
3558 G_TYPE_NONE,
3559 5,
3560 HDY_TYPE_TAB_PAGE,
3561 GDK_TYPE_DRAG_CONTEXT,
3562 GTK_TYPE_SELECTION_DATA,
3563 G_TYPE_UINT,
3564 G_TYPE_UINT);
3565
3566 signals[SIGNAL_ACTIVATE_TAB] =
3567 g_signal_new ("activate-tab",
3568 G_TYPE_FROM_CLASS (klass),
3569 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3570 0,
3571 NULL, NULL, NULL,
3572 G_TYPE_NONE,
3573 0);
3574
3575 signals[SIGNAL_FOCUS_TAB] =
3576 g_signal_new ("focus-tab",
3577 G_TYPE_FROM_CLASS (klass),
3578 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3579 0,
3580 NULL, NULL, NULL,
3581 G_TYPE_NONE,
3582 2, GTK_TYPE_DIRECTION_TYPE, G_TYPE_BOOLEAN);
3583
3584 signals[SIGNAL_REORDER_TAB] =
3585 g_signal_new ("reorder-tab",
3586 G_TYPE_FROM_CLASS (klass),
3587 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3588 0,
3589 NULL, NULL, NULL,
3590 G_TYPE_NONE,
3591 2, GTK_TYPE_DIRECTION_TYPE, G_TYPE_BOOLEAN);
3592
3593 g_signal_override_class_handler ("activate-tab",
3594 G_TYPE_FROM_CLASS (klass),
3595 G_CALLBACK (activate_tab));
3596
3597 g_signal_override_class_handler ("focus-tab",
3598 G_TYPE_FROM_CLASS (klass),
3599 G_CALLBACK (focus_tab_cb));
3600
3601 g_signal_override_class_handler ("reorder-tab",
3602 G_TYPE_FROM_CLASS (klass),
3603 G_CALLBACK (reorder_tab_cb));
3604
3605 binding_set = gtk_binding_set_by_class (klass);
3606
3607 gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0, "activate-tab", 0);
3608 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, 0, "activate-tab", 0);
3609 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, "activate-tab", 0);
3610 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0, "activate-tab", 0);
3611 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, "activate-tab", 0);
3612
3613 add_focus_bindings (binding_set, GDK_KEY_Page_Up, GTK_DIR_TAB_BACKWARD, FALSE);
3614 add_focus_bindings (binding_set, GDK_KEY_Page_Down, GTK_DIR_TAB_FORWARD, FALSE);
3615 add_focus_bindings (binding_set, GDK_KEY_Home, GTK_DIR_TAB_BACKWARD, TRUE);
3616 add_focus_bindings (binding_set, GDK_KEY_End, GTK_DIR_TAB_FORWARD, TRUE);
3617
3618 add_reorder_bindings (binding_set, GDK_KEY_Left, GTK_DIR_LEFT, FALSE);
3619 add_reorder_bindings (binding_set, GDK_KEY_Right, GTK_DIR_RIGHT, FALSE);
3620 add_reorder_bindings (binding_set, GDK_KEY_Page_Up, GTK_DIR_TAB_BACKWARD, FALSE);
3621 add_reorder_bindings (binding_set, GDK_KEY_Page_Down, GTK_DIR_TAB_FORWARD, FALSE);
3622 add_reorder_bindings (binding_set, GDK_KEY_Home, GTK_DIR_TAB_BACKWARD, TRUE);
3623 add_reorder_bindings (binding_set, GDK_KEY_End, GTK_DIR_TAB_FORWARD, TRUE);
3624
3625 gtk_widget_class_set_css_name (widget_class, "tabbox");
3626 }
3627
3628 static void
hdy_tab_box_init(HdyTabBox * self)3629 hdy_tab_box_init (HdyTabBox *self)
3630 {
3631 self->can_remove_placeholder = TRUE;
3632 self->expand_tabs = TRUE;
3633
3634 gtk_widget_add_events (GTK_WIDGET (self),
3635 GDK_BUTTON_PRESS_MASK |
3636 GDK_BUTTON_RELEASE_MASK |
3637 GDK_BUTTON_MOTION_MASK |
3638 GDK_POINTER_MOTION_MASK |
3639 GDK_TOUCH_MASK |
3640 GDK_ENTER_NOTIFY_MASK |
3641 GDK_LEAVE_NOTIFY_MASK |
3642 GDK_SCROLL_MASK |
3643 GDK_SMOOTH_SCROLL_MASK);
3644
3645 self->touch_menu_gesture = g_object_new (GTK_TYPE_GESTURE_LONG_PRESS,
3646 "widget", GTK_WIDGET (self),
3647 "propagation-phase", GTK_PHASE_CAPTURE,
3648 "touch-only", TRUE,
3649 NULL);
3650
3651 g_signal_connect_object (self->touch_menu_gesture,
3652 "pressed",
3653 G_CALLBACK (touch_menu_gesture_pressed),
3654 self,
3655 G_CONNECT_SWAPPED);
3656
3657 gtk_drag_dest_set (GTK_WIDGET (self),
3658 0,
3659 dst_targets, G_N_ELEMENTS (dst_targets),
3660 GDK_ACTION_MOVE |
3661 GDK_ACTION_COPY |
3662 GDK_ACTION_LINK |
3663 GDK_ACTION_ASK |
3664 GDK_ACTION_PRIVATE);
3665 gtk_drag_dest_set_track_motion (GTK_WIDGET (self), TRUE);
3666
3667 self->source_targets = gtk_target_list_new (src_targets,
3668 G_N_ELEMENTS (src_targets));
3669 }
3670
3671 void
hdy_tab_box_set_view(HdyTabBox * self,HdyTabView * view)3672 hdy_tab_box_set_view (HdyTabBox *self,
3673 HdyTabView *view)
3674 {
3675 g_return_if_fail (HDY_IS_TAB_BOX (self));
3676 g_return_if_fail (HDY_IS_TAB_VIEW (view) || view == NULL);
3677
3678 if (view == self->view)
3679 return;
3680
3681 if (self->view) {
3682 force_end_reordering (self);
3683
3684 g_signal_handlers_disconnect_by_func (self->view, page_attached_cb, self);
3685 g_signal_handlers_disconnect_by_func (self->view, page_detached_cb, self);
3686 g_signal_handlers_disconnect_by_func (self->view, page_reordered_cb, self);
3687
3688 if (!self->pinned)
3689 g_signal_handlers_disconnect_by_func (self->view, view_drag_drop_cb, self);
3690
3691 g_list_free_full (self->tabs, (GDestroyNotify) remove_and_free_tab_info);
3692 self->tabs = NULL;
3693 self->n_tabs = 0;
3694 }
3695
3696 self->view = view;
3697
3698 if (self->view) {
3699 int i, n_pages = hdy_tab_view_get_n_pages (self->view);
3700
3701 for (i = n_pages - 1; i >= 0; i--)
3702 page_attached_cb (self, hdy_tab_view_get_nth_page (self->view, i), 0);
3703
3704 g_signal_connect_object (self->view, "page-attached", G_CALLBACK (page_attached_cb), self, G_CONNECT_SWAPPED);
3705 g_signal_connect_object (self->view, "page-detached", G_CALLBACK (page_detached_cb), self, G_CONNECT_SWAPPED);
3706 g_signal_connect_object (self->view, "page-reordered", G_CALLBACK (page_reordered_cb), self, G_CONNECT_SWAPPED);
3707
3708 if (!self->pinned)
3709 g_signal_connect_object (self->view, "drag-drop", G_CALLBACK (view_drag_drop_cb), self, G_CONNECT_SWAPPED);
3710 }
3711
3712 gtk_widget_queue_allocate (GTK_WIDGET (self));
3713
3714 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VIEW]);
3715 }
3716
3717 void
hdy_tab_box_set_adjustment(HdyTabBox * self,GtkAdjustment * adjustment)3718 hdy_tab_box_set_adjustment (HdyTabBox *self,
3719 GtkAdjustment *adjustment)
3720 {
3721 g_return_if_fail (HDY_IS_TAB_BOX (self));
3722 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment) || adjustment == NULL);
3723
3724 if (adjustment == self->adjustment)
3725 return;
3726
3727 if (self->adjustment) {
3728 g_signal_handlers_disconnect_by_func (self->adjustment, adjustment_value_changed_cb, self);
3729 g_signal_handlers_disconnect_by_func (self->adjustment, update_visible, self);
3730 }
3731
3732 g_set_object (&self->adjustment, adjustment);
3733
3734 if (self->adjustment) {
3735 g_signal_connect_object (self->adjustment, "value-changed", G_CALLBACK (adjustment_value_changed_cb), self, G_CONNECT_SWAPPED);
3736 g_signal_connect_object (self->adjustment, "notify::page-size", G_CALLBACK (update_visible), self, G_CONNECT_SWAPPED);
3737 }
3738
3739 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ADJUSTMENT]);
3740 }
3741
3742 void
hdy_tab_box_set_block_scrolling(HdyTabBox * self,gboolean block_scrolling)3743 hdy_tab_box_set_block_scrolling (HdyTabBox *self,
3744 gboolean block_scrolling)
3745 {
3746 g_return_if_fail (HDY_IS_TAB_BOX (self));
3747
3748 self->block_scrolling = block_scrolling;
3749 }
3750
3751 void
hdy_tab_box_attach_page(HdyTabBox * self,HdyTabPage * page,gint position)3752 hdy_tab_box_attach_page (HdyTabBox *self,
3753 HdyTabPage *page,
3754 gint position)
3755 {
3756 g_return_if_fail (HDY_IS_TAB_BOX (self));
3757 g_return_if_fail (HDY_IS_TAB_PAGE (page));
3758
3759 page_attached_cb (self, page, position);
3760 }
3761
3762 void
hdy_tab_box_detach_page(HdyTabBox * self,HdyTabPage * page)3763 hdy_tab_box_detach_page (HdyTabBox *self,
3764 HdyTabPage *page)
3765 {
3766 g_return_if_fail (HDY_IS_TAB_BOX (self));
3767 g_return_if_fail (HDY_IS_TAB_PAGE (page));
3768
3769 page_detached_cb (self, page);
3770 }
3771
3772 void
hdy_tab_box_select_page(HdyTabBox * self,HdyTabPage * page)3773 hdy_tab_box_select_page (HdyTabBox *self,
3774 HdyTabPage *page)
3775 {
3776 g_return_if_fail (HDY_IS_TAB_BOX (self));
3777 g_return_if_fail (HDY_IS_TAB_PAGE (page) || page == NULL);
3778
3779 select_page (self, page);
3780 }
3781
3782 void
hdy_tab_box_try_focus_selected_tab(HdyTabBox * self)3783 hdy_tab_box_try_focus_selected_tab (HdyTabBox *self)
3784 {
3785 g_return_if_fail (HDY_IS_TAB_BOX (self));
3786
3787 if (self->selected_tab)
3788 gtk_widget_grab_focus (GTK_WIDGET (self->selected_tab->tab));
3789 }
3790
3791 gboolean
hdy_tab_box_is_page_focused(HdyTabBox * self,HdyTabPage * page)3792 hdy_tab_box_is_page_focused (HdyTabBox *self,
3793 HdyTabPage *page)
3794 {
3795 TabInfo *info;
3796
3797 g_return_val_if_fail (HDY_IS_TAB_BOX (self), FALSE);
3798 g_return_val_if_fail (HDY_IS_TAB_PAGE (page), FALSE);
3799
3800 info = find_info_for_page (self, page);
3801
3802 return info && gtk_widget_is_focus (GTK_WIDGET (info->tab));
3803 }
3804
3805 void
hdy_tab_box_set_extra_drag_dest_targets(HdyTabBox * self,GtkTargetList * extra_drag_dest_targets)3806 hdy_tab_box_set_extra_drag_dest_targets (HdyTabBox *self,
3807 GtkTargetList *extra_drag_dest_targets)
3808 {
3809 GtkTargetList *list;
3810 GtkTargetEntry *table;
3811 gint n_targets;
3812
3813 g_return_if_fail (HDY_IS_TAB_BOX (self));
3814
3815 list = gtk_target_list_new (NULL, 0);
3816 table = gtk_target_table_new_from_list (extra_drag_dest_targets, &n_targets);
3817
3818 gtk_target_list_add_table (list, dst_targets, G_N_ELEMENTS (dst_targets));
3819 gtk_target_list_add_table (list, table, n_targets);
3820
3821 gtk_drag_dest_set_target_list (GTK_WIDGET (self), list);
3822
3823 gtk_target_list_unref (list);
3824 gtk_target_table_free (table, n_targets);
3825 }
3826
3827 gboolean
hdy_tab_box_get_expand_tabs(HdyTabBox * self)3828 hdy_tab_box_get_expand_tabs (HdyTabBox *self)
3829 {
3830 g_return_val_if_fail (HDY_IS_TAB_BOX (self), FALSE);
3831
3832 return self->expand_tabs;
3833 }
3834
3835 void
hdy_tab_box_set_expand_tabs(HdyTabBox * self,gboolean expand_tabs)3836 hdy_tab_box_set_expand_tabs (HdyTabBox *self,
3837 gboolean expand_tabs)
3838 {
3839 g_return_if_fail (HDY_IS_TAB_BOX (self));
3840
3841 expand_tabs = !!expand_tabs;
3842
3843 if (expand_tabs == self->expand_tabs)
3844 return;
3845
3846 self->expand_tabs = expand_tabs;
3847
3848 gtk_widget_queue_resize (GTK_WIDGET (self));
3849 }
3850
3851 gboolean
hdy_tab_box_get_inverted(HdyTabBox * self)3852 hdy_tab_box_get_inverted (HdyTabBox *self)
3853 {
3854 g_return_val_if_fail (HDY_IS_TAB_BOX (self), FALSE);
3855
3856 return self->inverted;
3857 }
3858
3859 void
hdy_tab_box_set_inverted(HdyTabBox * self,gboolean inverted)3860 hdy_tab_box_set_inverted (HdyTabBox *self,
3861 gboolean inverted)
3862 {
3863 GList *l;
3864
3865 g_return_if_fail (HDY_IS_TAB_BOX (self));
3866
3867 inverted = !!inverted;
3868
3869 if (inverted == self->inverted)
3870 return;
3871
3872 self->inverted = inverted;
3873
3874 for (l = self->tabs; l; l = l->next) {
3875 TabInfo *info = l->data;
3876
3877 hdy_tab_set_inverted (info->tab, inverted);
3878 }
3879 }
3880