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