1 /*
2  * xed-notebook.c
3  * This file is part of xed
4  *
5  * Copyright (C) 2005 - Paolo Maggi
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 /*
24  * Modified by the xed Team, 2005. See the AUTHORS file for a
25  * list of people on the xed Team.
26  * See the ChangeLog files for a list of changes.
27  */
28 
29 /* This file is a modified version of the epiphany file ephy-notebook.c
30  * Here the relevant copyright:
31  *
32  *  Copyright (C) 2002 Christophe Fergeau
33  *  Copyright (C) 2003 Marco Pesenti Gritti
34  *  Copyright (C) 2003, 2004 Christian Persch
35  *
36  */
37 
38 #include <config.h>
39 #include <glib-object.h>
40 #include <glib/gi18n.h>
41 #include <gtk/gtk.h>
42 
43 #include "xed-notebook.h"
44 #include "xed-tab.h"
45 #include "xed-tab-label.h"
46 #include "xed-marshal.h"
47 #include "xed-window.h"
48 
49 #define AFTER_ALL_TABS -1
50 #define NOT_IN_APP_WINDOWS -2
51 
52 struct _XedNotebookPrivate
53 {
54     GSettings *ui_settings;
55     GList *focused_pages;
56     gulong motion_notify_handler_id;
57     gint x_start;
58     gint y_start;
59     gint drag_in_progress : 1;
60     gint close_buttons_sensitive : 1;
61     gint tab_drag_and_drop_enabled : 1;
62     gint tab_scrolling_enabled : 1;
63     guint destroy_has_run : 1;
64 };
65 
66 G_DEFINE_TYPE_WITH_PRIVATE (XedNotebook, xed_notebook, GTK_TYPE_NOTEBOOK)
67 
68 static void xed_notebook_finalize (GObject *object);
69 
70 static gboolean xed_notebook_change_current_page (GtkNotebook *notebook,
71                                                   gint         offset);
72 
73 static void move_current_tab_to_another_notebook  (XedNotebook    *src,
74                                                    XedNotebook    *dest,
75                                                    GdkEventMotion *event,
76                                                    gint            dest_position);
77 
78 /* Local variables */
79 static GdkCursor *cursor = NULL;
80 
81 /* Signals */
82 enum
83 {
84     TAB_ADDED,
85     TAB_REMOVED,
86     TABS_REORDERED,
87     TAB_DETACHED,
88     TAB_CLOSE_REQUEST,
89     LAST_SIGNAL
90 };
91 
92 static guint signals[LAST_SIGNAL] = { 0 };
93 
94 static void
xed_notebook_dispose(GObject * object)95 xed_notebook_dispose (GObject *object)
96 {
97     XedNotebook *notebook = XED_NOTEBOOK (object);
98 
99     if (!notebook->priv->destroy_has_run)
100     {
101         GList *children, *l;
102 
103         children = gtk_container_get_children (GTK_CONTAINER (notebook));
104 
105         for (l = children; l != NULL; l = g_list_next (l))
106         {
107             xed_notebook_remove_tab (notebook, XED_TAB (l->data));
108         }
109 
110         g_list_free (children);
111         notebook->priv->destroy_has_run = TRUE;
112     }
113 
114     g_clear_object (&notebook->priv->ui_settings);
115 
116     G_OBJECT_CLASS (xed_notebook_parent_class)->dispose (object);
117 }
118 
119 static void
xed_notebook_class_init(XedNotebookClass * klass)120 xed_notebook_class_init (XedNotebookClass *klass)
121 {
122     GObjectClass *object_class = G_OBJECT_CLASS (klass);
123     GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
124 
125     object_class->finalize = xed_notebook_finalize;
126     object_class->dispose = xed_notebook_dispose;
127 
128     notebook_class->change_current_page = xed_notebook_change_current_page;
129 
130     signals[TAB_ADDED] =
131         g_signal_new ("tab_added",
132                       G_OBJECT_CLASS_TYPE (object_class),
133                       G_SIGNAL_RUN_FIRST,
134                       G_STRUCT_OFFSET (XedNotebookClass, tab_added),
135                       NULL, NULL,
136                       g_cclosure_marshal_VOID__OBJECT,
137                       G_TYPE_NONE,
138                       1,
139                       XED_TYPE_TAB);
140     signals[TAB_REMOVED] =
141         g_signal_new ("tab_removed",
142                       G_OBJECT_CLASS_TYPE (object_class),
143                       G_SIGNAL_RUN_FIRST,
144                       G_STRUCT_OFFSET (XedNotebookClass, tab_removed),
145                       NULL, NULL,
146                       g_cclosure_marshal_VOID__OBJECT,
147                       G_TYPE_NONE,
148                       1,
149                       XED_TYPE_TAB);
150     signals[TAB_DETACHED] =
151         g_signal_new ("tab_detached",
152                       G_OBJECT_CLASS_TYPE (object_class),
153                       G_SIGNAL_RUN_FIRST,
154                       G_STRUCT_OFFSET (XedNotebookClass, tab_detached),
155                       NULL, NULL,
156                       g_cclosure_marshal_VOID__OBJECT,
157                       G_TYPE_NONE,
158                       1,
159                       XED_TYPE_TAB);
160     signals[TABS_REORDERED] =
161         g_signal_new ("tabs_reordered",
162                       G_OBJECT_CLASS_TYPE (object_class),
163                       G_SIGNAL_RUN_FIRST,
164                       G_STRUCT_OFFSET (XedNotebookClass, tabs_reordered),
165                       NULL, NULL,
166                       g_cclosure_marshal_VOID__VOID,
167                       G_TYPE_NONE,
168                       0);
169     signals[TAB_CLOSE_REQUEST] =
170         g_signal_new ("tab-close-request",
171                       G_OBJECT_CLASS_TYPE (object_class),
172                       G_SIGNAL_RUN_LAST,
173                       G_STRUCT_OFFSET (XedNotebookClass, tab_close_request),
174                       NULL, NULL,
175                       g_cclosure_marshal_VOID__OBJECT,
176                       G_TYPE_NONE,
177                       1,
178                       XED_TYPE_TAB);
179 }
180 
181 static XedNotebook *
find_notebook_at_pointer(gint abs_x,gint abs_y)182 find_notebook_at_pointer (gint abs_x,
183                           gint abs_y)
184 {
185     GdkSeat *seat;
186     GdkDevice *device;
187     GdkWindow *win_at_pointer;
188     GdkWindow *toplevel_win;
189     gpointer toplevel = NULL;
190     gint x, y;
191 
192     seat = gdk_display_get_default_seat (gdk_display_get_default ());
193     device = gdk_seat_get_pointer (seat);
194     win_at_pointer = gdk_device_get_window_at_position (device, &x, &y);
195 
196     if (win_at_pointer == NULL)
197     {
198         /* We are outside all windows of the same application */
199         return NULL;
200     }
201 
202     toplevel_win = gdk_window_get_toplevel (win_at_pointer);
203 
204     /* get the GtkWidget which owns the toplevel GdkWindow */
205     gdk_window_get_user_data (toplevel_win, &toplevel);
206 
207     /* toplevel should be an XedWindow */
208     if ((toplevel != NULL) && XED_IS_WINDOW (toplevel))
209     {
210         return XED_NOTEBOOK (_xed_window_get_notebook (XED_WINDOW (toplevel)));
211     }
212 
213     /* We are outside all windows containing a notebook */
214     return NULL;
215 }
216 
217 static gboolean
is_in_notebook_window(XedNotebook * notebook,gint abs_x,gint abs_y)218 is_in_notebook_window (XedNotebook *notebook,
219                        gint         abs_x,
220                        gint         abs_y)
221 {
222     XedNotebook *nb_at_pointer;
223 
224     g_return_val_if_fail (notebook != NULL, FALSE);
225 
226     nb_at_pointer = find_notebook_at_pointer (abs_x, abs_y);
227 
228     return (nb_at_pointer == notebook);
229 }
230 
231 static gint
find_tab_num_at_pos(XedNotebook * notebook,gint abs_x,gint abs_y)232 find_tab_num_at_pos (XedNotebook *notebook,
233                      gint         abs_x,
234                      gint         abs_y)
235 {
236     GtkPositionType tab_pos;
237     int page_num = 0;
238     GtkNotebook *nb = GTK_NOTEBOOK (notebook);
239     GtkWidget *page;
240 
241     tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook));
242 
243     /* For some reason unfullscreen + quick click can
244        cause a wrong click event to be reported to the tab */
245     if (!is_in_notebook_window (notebook, abs_x, abs_y))
246     {
247         return NOT_IN_APP_WINDOWS;
248     }
249 
250     while ((page = gtk_notebook_get_nth_page (nb, page_num)) != NULL)
251     {
252         GtkAllocation allocation;
253         GtkWidget *tab;
254         gint min_x, min_y;
255         gint max_x, max_y;
256         gint x_root, y_root;
257 
258         tab = gtk_notebook_get_tab_label (nb, page);
259         g_return_val_if_fail (tab != NULL, AFTER_ALL_TABS);
260 
261         if (!gtk_widget_get_mapped (tab))
262         {
263             ++page_num;
264             continue;
265         }
266 
267         gdk_window_get_origin (GDK_WINDOW (gtk_widget_get_window (tab)), &x_root, &y_root);
268 
269         gtk_widget_get_allocation(tab, &allocation);
270 
271         min_x = x_root + allocation.x;
272         max_x = x_root + allocation.x + allocation.width;
273         min_y = y_root + allocation.y;
274         max_y = y_root + allocation.y + allocation.height;
275 
276         if (((tab_pos == GTK_POS_TOP) ||
277              (tab_pos == GTK_POS_BOTTOM)) &&
278             (abs_x <= max_x) &&
279             (abs_y >= min_y) &&
280             (abs_y <= max_y))
281         {
282             return page_num;
283         }
284         else if (((tab_pos == GTK_POS_LEFT) ||
285                   (tab_pos == GTK_POS_RIGHT)) &&
286                  (abs_y <= max_y) &&
287                  (abs_x >= min_x) &&
288                  (abs_x <= max_x))
289         {
290             return page_num;
291         }
292 
293         ++page_num;
294     }
295 
296     return AFTER_ALL_TABS;
297 }
298 
299 static gint
find_notebook_and_tab_at_pos(gint abs_x,gint abs_y,XedNotebook ** notebook,gint * page_num)300 find_notebook_and_tab_at_pos (gint          abs_x,
301                               gint          abs_y,
302                               XedNotebook **notebook,
303                               gint         *page_num)
304 {
305     *notebook = find_notebook_at_pointer (abs_x, abs_y);
306     if (*notebook == NULL)
307     {
308         return NOT_IN_APP_WINDOWS;
309     }
310 
311     *page_num = find_tab_num_at_pos (*notebook, abs_x, abs_y);
312 
313     if (*page_num < 0)
314     {
315         return *page_num;
316     }
317     else
318     {
319         return 0;
320     }
321 }
322 
323 /**
324  * xed_notebook_move_tab:
325  * @src: a #XedNotebook
326  * @dest: a #XedNotebook
327  * @tab: a #XedTab
328  * @dest_position: the position for @tab
329  *
330  * Moves @tab from @src to @dest.
331  * If dest_position is greater than or equal to the number of tabs
332  * of the destination nootebook or negative, tab will be moved to the
333  * end of the tabs.
334  */
335 void
xed_notebook_move_tab(XedNotebook * src,XedNotebook * dest,XedTab * tab,gint dest_position)336 xed_notebook_move_tab (XedNotebook *src,
337                        XedNotebook *dest,
338                        XedTab      *tab,
339                        gint         dest_position)
340 {
341     g_return_if_fail (XED_IS_NOTEBOOK (src));
342     g_return_if_fail (XED_IS_NOTEBOOK (dest));
343     g_return_if_fail (src != dest);
344     g_return_if_fail (XED_IS_TAB (tab));
345 
346     /* make sure the tab isn't destroyed while we move it */
347     g_object_ref (tab);
348     xed_notebook_remove_tab (src, tab);
349     xed_notebook_add_tab (dest, tab, dest_position, TRUE);
350     g_object_unref (tab);
351 }
352 
353 /**
354  * xed_notebook_reorder_tab:
355  * @src: a #XedNotebook
356  * @tab: a #XedTab
357  * @dest_position: the position for @tab
358  *
359  * Reorders the page containing @tab, so that it appears in @dest_position position.
360  * If dest_position is greater than or equal to the number of tabs
361  * of the destination notebook or negative, tab will be moved to the
362  * end of the tabs.
363  */
364 void
xed_notebook_reorder_tab(XedNotebook * src,XedTab * tab,gint dest_position)365 xed_notebook_reorder_tab (XedNotebook *src,
366                           XedTab      *tab,
367                           gint         dest_position)
368 {
369     gint old_position;
370 
371     g_return_if_fail (XED_IS_NOTEBOOK (src));
372     g_return_if_fail (XED_IS_TAB (tab));
373 
374     old_position = gtk_notebook_page_num (GTK_NOTEBOOK (src), GTK_WIDGET (tab));
375 
376     if (old_position == dest_position)
377     {
378         return;
379     }
380 
381     gtk_notebook_reorder_child (GTK_NOTEBOOK (src), GTK_WIDGET (tab), dest_position);
382 
383     if (!src->priv->drag_in_progress)
384     {
385         g_signal_emit (G_OBJECT (src), signals[TABS_REORDERED], 0);
386     }
387 }
388 
389 static void
drag_start(XedNotebook * notebook,guint32 time)390 drag_start (XedNotebook *notebook,
391             guint32      time)
392 {
393     notebook->priv->drag_in_progress = TRUE;
394 
395     /* get a new cursor, if necessary */
396     /* FIXME multi-head */
397     if (cursor == NULL)
398     {
399         cursor = gdk_cursor_new (GDK_FLEUR);
400     }
401 
402     /* grab the pointer */
403     gtk_grab_add (GTK_WIDGET (notebook));
404 
405     /* FIXME multi-head */
406     if (!gdk_pointer_is_grabbed ())
407     {
408         gdk_pointer_grab (gtk_widget_get_window (GTK_WIDGET (notebook)),
409                           FALSE,
410                           GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
411                           NULL,
412                           cursor,
413                           time);
414     }
415 }
416 
417 static void
drag_stop(XedNotebook * notebook)418 drag_stop (XedNotebook *notebook)
419 {
420     if (notebook->priv->drag_in_progress)
421     {
422         g_signal_emit (G_OBJECT (notebook), signals[TABS_REORDERED], 0);
423     }
424 
425     notebook->priv->drag_in_progress = FALSE;
426     if (notebook->priv->motion_notify_handler_id != 0)
427     {
428         g_signal_handler_disconnect (G_OBJECT (notebook), notebook->priv->motion_notify_handler_id);
429         notebook->priv->motion_notify_handler_id = 0;
430     }
431 }
432 
433 /* This function is only called during dnd, we don't need to emit TABS_REORDERED
434  * here, instead we do it on drag_stop
435  */
436 static void
move_current_tab(XedNotebook * notebook,gint dest_position)437 move_current_tab (XedNotebook *notebook,
438                   gint         dest_position)
439 {
440     gint cur_page_num;
441 
442     cur_page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
443 
444     if (dest_position != cur_page_num)
445     {
446         GtkWidget *cur_tab;
447 
448         cur_tab = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), cur_page_num);
449         xed_notebook_reorder_tab (XED_NOTEBOOK (notebook), XED_TAB (cur_tab), dest_position);
450     }
451 }
452 
453 static gboolean
motion_notify_cb(XedNotebook * notebook,GdkEventMotion * event,gpointer data)454 motion_notify_cb (XedNotebook    *notebook,
455                   GdkEventMotion *event,
456                   gpointer        data)
457 {
458     XedNotebook *dest;
459     gint page_num;
460     gint result;
461 
462     if (notebook->priv->drag_in_progress == FALSE)
463     {
464         if (notebook->priv->tab_drag_and_drop_enabled == FALSE)
465         {
466             return FALSE;
467         }
468 
469         if (gtk_drag_check_threshold (GTK_WIDGET (notebook),
470                                       notebook->priv->x_start,
471                                       notebook->priv->y_start,
472                                       event->x_root,
473                                       event->y_root))
474         {
475             drag_start (notebook, event->time);
476             return TRUE;
477         }
478 
479         return FALSE;
480     }
481 
482     result = find_notebook_and_tab_at_pos ((gint)event->x_root, (gint)event->y_root, &dest, &page_num);
483 
484     if (result != NOT_IN_APP_WINDOWS)
485     {
486         if (dest != notebook)
487         {
488             move_current_tab_to_another_notebook (notebook, dest, event, page_num);
489         }
490         else
491         {
492             g_return_val_if_fail (page_num >= -1, FALSE);
493             move_current_tab (notebook, page_num);
494         }
495     }
496 
497     return FALSE;
498 }
499 
500 static void
move_current_tab_to_another_notebook(XedNotebook * src,XedNotebook * dest,GdkEventMotion * event,gint dest_position)501 move_current_tab_to_another_notebook (XedNotebook    *src,
502                                       XedNotebook    *dest,
503                                       GdkEventMotion *event,
504                                       gint            dest_position)
505 {
506     XedTab *tab;
507     gint cur_page;
508 
509     /* This is getting tricky, the tab was dragged in a notebook
510      * in another window of the same app, we move the tab
511      * to that new notebook, and let this notebook handle the
512      * drag
513      */
514     g_return_if_fail (XED_IS_NOTEBOOK (dest));
515     g_return_if_fail (dest != src);
516 
517     cur_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (src));
518     tab = XED_TAB (gtk_notebook_get_nth_page (GTK_NOTEBOOK (src), cur_page));
519 
520     /* stop drag in origin window */
521     /* ungrab the pointer if it's grabbed */
522     drag_stop (src);
523     if (gdk_pointer_is_grabbed ())
524     {
525         gdk_pointer_ungrab (event->time);
526     }
527     gtk_grab_remove (GTK_WIDGET (src));
528 
529     xed_notebook_move_tab (src, dest, tab, dest_position);
530 
531     /* start drag handling in dest notebook */
532     dest->priv->motion_notify_handler_id = g_signal_connect (G_OBJECT (dest), "motion-notify-event",
533                                                              G_CALLBACK (motion_notify_cb), NULL);
534 
535     drag_start (dest, event->time);
536 }
537 
538 static gboolean
button_release_cb(XedNotebook * notebook,GdkEventButton * event,gpointer data)539 button_release_cb (XedNotebook    *notebook,
540                    GdkEventButton *event,
541                    gpointer        data)
542 {
543     gboolean ret_val = FALSE;
544 
545     if (notebook->priv->drag_in_progress)
546     {
547         gint cur_page_num;
548         GtkWidget *cur_page;
549 
550         cur_page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
551         cur_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), cur_page_num);
552 
553         /* CHECK: I don't follow the code here -- Paolo  */
554         if (!is_in_notebook_window (notebook, event->x_root, event->y_root) &&
555             (gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)) > 1))
556         {
557             /* Tab was detached */
558             g_signal_emit (G_OBJECT (notebook), signals[TAB_DETACHED], 0, cur_page);
559         }
560 
561         /* ungrab the pointer if it's grabbed */
562         if (gdk_pointer_is_grabbed ())
563         {
564             gdk_pointer_ungrab (event->time);
565         }
566         gtk_grab_remove (GTK_WIDGET (notebook));
567     }
568     else if ((event->type == GDK_BUTTON_RELEASE) && (event->button == 8))
569     {
570         gtk_notebook_prev_page (GTK_NOTEBOOK (notebook));
571 
572         ret_val = TRUE;
573     }
574     else if ((event->type == GDK_BUTTON_RELEASE) && (event->button == 9))
575     {
576         gtk_notebook_next_page (GTK_NOTEBOOK (notebook));
577 
578         ret_val = TRUE;
579     }
580 
581     /* This must be called even if a drag isn't happening */
582     drag_stop (notebook);
583 
584     return ret_val;
585 }
586 
587 static gboolean
button_press_cb(XedNotebook * notebook,GdkEventButton * event,gpointer data)588 button_press_cb (XedNotebook    *notebook,
589                  GdkEventButton *event,
590                  gpointer        data)
591 {
592     gint tab_clicked;
593 
594     if (notebook->priv->drag_in_progress)
595     {
596         return TRUE;
597     }
598 
599     tab_clicked = find_tab_num_at_pos (notebook, event->x_root, event->y_root);
600 
601     if ((event->button == 1) && (event->type == GDK_BUTTON_PRESS) && (tab_clicked >= 0))
602     {
603         notebook->priv->x_start = event->x_root;
604         notebook->priv->y_start = event->y_root;
605 
606         notebook->priv->motion_notify_handler_id = g_signal_connect (G_OBJECT (notebook), "motion-notify-event",
607                                                                      G_CALLBACK (motion_notify_cb), NULL);
608     }
609     else if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3 || event->button == 2))
610     {
611         if (tab_clicked == -1)
612         {
613             // CHECK: do we really need it?
614 
615             /* consume event, so that we don't pop up the context menu when
616              * the mouse if not over a tab label
617              */
618             return TRUE;
619         }
620         else
621         {
622             /* Switch to the page the mouse is over, but don't consume the event */
623             gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), tab_clicked);
624         }
625     }
626 
627     return FALSE;
628 }
629 
630 static gboolean
notebook_scroll_event_cb(XedNotebook * notebook,GdkEventScroll * event,gpointer data)631 notebook_scroll_event_cb (XedNotebook    *notebook,
632                           GdkEventScroll *event,
633                           gpointer        data)
634 {
635     GtkWidget *event_widget;
636 
637     if (!notebook->priv->tab_scrolling_enabled)
638     {
639         return TRUE;
640     }
641 
642     event_widget = gtk_get_event_widget ((GdkEvent *) event);
643 
644     if (event_widget == NULL)
645     {
646         return FALSE;
647     }
648 
649     switch (event->direction)
650     {
651         case GDK_SCROLL_DOWN:
652         case GDK_SCROLL_LEFT:
653             gtk_notebook_prev_page (GTK_NOTEBOOK (notebook));
654             break;
655         case GDK_SCROLL_UP:
656         case GDK_SCROLL_RIGHT:
657             gtk_notebook_next_page (GTK_NOTEBOOK (notebook));
658             break;
659         default:
660             break;
661     }
662 
663     return TRUE;
664 }
665 
666 /**
667  * xed_notebook_new:
668  *
669  * Creates a new #XedNotebook object.
670  *
671  * Returns: a new #XedNotebook
672  */
673 GtkWidget *
xed_notebook_new(void)674 xed_notebook_new (void)
675 {
676     return GTK_WIDGET (g_object_new (XED_TYPE_NOTEBOOK, NULL));
677 }
678 
679 static void
xed_notebook_switch_page_cb(GtkNotebook * notebook,GtkWidget * page,guint page_num,gpointer data)680 xed_notebook_switch_page_cb (GtkNotebook *notebook,
681                              GtkWidget   *page,
682                              guint        page_num,
683                              gpointer     data)
684 {
685     XedNotebook *nb = XED_NOTEBOOK (notebook);
686     GtkWidget *child;
687     XedView *view;
688 
689     child = gtk_notebook_get_nth_page (notebook, page_num);
690 
691     /* Remove the old page, we dont want to grow unnecessarily
692      * the list */
693     if (nb->priv->focused_pages)
694     {
695         nb->priv->focused_pages = g_list_remove (nb->priv->focused_pages, child);
696     }
697 
698     nb->priv->focused_pages = g_list_append (nb->priv->focused_pages, child);
699 
700     /* give focus to the view */
701     view = xed_tab_get_view (XED_TAB (child));
702     gtk_widget_grab_focus (GTK_WIDGET (view));
703 }
704 
705 static void
xed_notebook_init(XedNotebook * notebook)706 xed_notebook_init (XedNotebook *notebook)
707 {
708     notebook->priv = xed_notebook_get_instance_private (notebook);
709 
710     notebook->priv->close_buttons_sensitive = TRUE;
711     notebook->priv->tab_drag_and_drop_enabled = TRUE;
712     notebook->priv->ui_settings = g_settings_new ("org.x.editor.preferences.ui");
713     notebook->priv->tab_scrolling_enabled = g_settings_get_boolean (notebook->priv->ui_settings, "enable-tab-scrolling");
714 
715     gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
716     gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
717     gtk_container_set_border_width (GTK_CONTAINER (notebook), 0);
718 
719     g_signal_connect (notebook, "button-press-event",
720                       (GCallback)button_press_cb, NULL);
721     g_signal_connect (notebook, "button-release-event",
722                       (GCallback)button_release_cb, NULL);
723     gtk_widget_add_events (GTK_WIDGET (notebook), GDK_BUTTON1_MOTION_MASK);
724 
725     g_signal_connect_after (G_OBJECT (notebook), "switch_page",
726                             G_CALLBACK (xed_notebook_switch_page_cb), NULL);
727 
728     gtk_widget_add_events (GTK_WIDGET (notebook), GDK_SCROLL_MASK);
729     g_signal_connect (notebook, "scroll-event",
730                       G_CALLBACK (notebook_scroll_event_cb), NULL);
731 }
732 
733 static void
xed_notebook_finalize(GObject * object)734 xed_notebook_finalize (GObject *object)
735 {
736     XedNotebook *notebook = XED_NOTEBOOK (object);
737 
738     g_list_free (notebook->priv->focused_pages);
739 
740     G_OBJECT_CLASS (xed_notebook_parent_class)->finalize (object);
741 }
742 
743 /*
744  * We need to override this because when we don't show the tabs, like in
745  * fullscreen we need to have wrap around too
746  */
747 static gboolean
xed_notebook_change_current_page(GtkNotebook * notebook,gint offset)748 xed_notebook_change_current_page (GtkNotebook *notebook,
749                                   gint         offset)
750 {
751     gboolean wrap_around;
752     gint current;
753 
754     current = gtk_notebook_get_current_page (notebook);
755 
756     if (current != -1)
757     {
758         current = current + offset;
759 
760         g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
761                       "gtk-keynav-wrap-around", &wrap_around, NULL);
762 
763         if (wrap_around)
764         {
765             if (current < 0)
766             {
767                 current = gtk_notebook_get_n_pages (notebook) - 1;
768             }
769             else if (current >= gtk_notebook_get_n_pages (notebook))
770             {
771                 current = 0;
772             }
773         }
774 
775         gtk_notebook_set_current_page (notebook, current);
776     }
777     else
778     {
779         gtk_widget_error_bell (GTK_WIDGET (notebook));
780     }
781 
782     return TRUE;
783 }
784 
785 static void
close_button_clicked_cb(XedTabLabel * tab_label,XedNotebook * notebook)786 close_button_clicked_cb (XedTabLabel *tab_label,
787                          XedNotebook *notebook)
788 {
789     XedTab *tab;
790 
791     tab = xed_tab_label_get_tab (tab_label);
792     g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, tab);
793 }
794 
795 static GtkWidget *
create_tab_label(XedNotebook * nb,XedTab * tab)796 create_tab_label (XedNotebook *nb,
797                   XedTab      *tab)
798 {
799     GtkWidget *tab_label;
800 
801     tab_label = xed_tab_label_new (tab);
802 
803     g_signal_connect (tab_label, "close-clicked",
804                       G_CALLBACK (close_button_clicked_cb), nb);
805 
806     g_object_set_data (G_OBJECT (tab), "tab-label", tab_label);
807 
808     return tab_label;
809 }
810 
811 static GtkWidget *
get_tab_label(XedTab * tab)812 get_tab_label (XedTab *tab)
813 {
814     GtkWidget *tab_label;
815 
816     tab_label = GTK_WIDGET (g_object_get_data (G_OBJECT (tab), "tab-label"));
817     g_return_val_if_fail (tab_label != NULL, NULL);
818 
819     return tab_label;
820 }
821 
822 static void
remove_tab_label(XedNotebook * nb,XedTab * tab)823 remove_tab_label (XedNotebook *nb,
824                   XedTab      *tab)
825 {
826     GtkWidget *tab_label;
827 
828     tab_label = get_tab_label (tab);
829 
830     g_signal_handlers_disconnect_by_func (tab_label, G_CALLBACK (close_button_clicked_cb), nb);
831 
832     g_object_set_data (G_OBJECT (tab), "tab-label", NULL);
833 }
834 
835 /**
836  * xed_notebook_add_tab:
837  * @nb: a #XedNotebook
838  * @tab: a #XedTab
839  * @position: the position where the @tab should be added
840  * @jump_to: %TRUE to set the @tab as active
841  *
842  * Adds the specified @tab to the @nb.
843  */
844 void
xed_notebook_add_tab(XedNotebook * nb,XedTab * tab,gint position,gboolean jump_to)845 xed_notebook_add_tab (XedNotebook *nb,
846                       XedTab      *tab,
847                       gint         position,
848                       gboolean     jump_to)
849 {
850     GtkWidget *tab_label;
851 
852     g_return_if_fail (XED_IS_NOTEBOOK (nb));
853     g_return_if_fail (XED_IS_TAB (tab));
854 
855     tab_label = create_tab_label (nb, tab);
856     gtk_notebook_insert_page (GTK_NOTEBOOK (nb), GTK_WIDGET (tab), tab_label, position);
857 
858     g_signal_emit (G_OBJECT (nb), signals[TAB_ADDED], 0, tab);
859 
860     /* The signal handler may have reordered the tabs */
861     position = gtk_notebook_page_num (GTK_NOTEBOOK (nb), GTK_WIDGET (tab));
862 
863     if (jump_to)
864     {
865         XedView *view;
866 
867         gtk_notebook_set_current_page (GTK_NOTEBOOK (nb), position);
868         g_object_set_data (G_OBJECT (tab), "jump_to", GINT_TO_POINTER (jump_to));
869         view = xed_tab_get_view (tab);
870 
871         gtk_widget_grab_focus (GTK_WIDGET (view));
872     }
873 }
874 
875 static void
smart_tab_switching_on_closure(XedNotebook * nb,XedTab * tab)876 smart_tab_switching_on_closure (XedNotebook *nb,
877                                 XedTab      *tab)
878 {
879     gboolean jump_to;
880 
881     jump_to = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tab), "jump_to"));
882 
883     if (!jump_to || !nb->priv->focused_pages)
884     {
885         gtk_notebook_next_page (GTK_NOTEBOOK (nb));
886     }
887     else
888     {
889         GList *l;
890         GtkWidget *child;
891         int page_num;
892 
893         /* activate the last focused tab */
894         l = g_list_last (nb->priv->focused_pages);
895         child = GTK_WIDGET (l->data);
896         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (nb), child);
897         gtk_notebook_set_current_page (GTK_NOTEBOOK (nb), page_num);
898     }
899 }
900 
901 static void
remove_tab(XedTab * tab,XedNotebook * nb)902 remove_tab (XedTab      *tab,
903             XedNotebook *nb)
904 {
905     gint position;
906 
907     position = gtk_notebook_page_num (GTK_NOTEBOOK (nb), GTK_WIDGET (tab));
908 
909     /* we ref the tab so that it's still alive while the tabs_removed
910      * signal is processed.
911      */
912     g_object_ref (tab);
913 
914     remove_tab_label (nb, tab);
915     gtk_notebook_remove_page (GTK_NOTEBOOK (nb), position);
916 
917     g_signal_emit (G_OBJECT (nb), signals[TAB_REMOVED], 0, tab);
918 
919     g_object_unref (tab);
920 }
921 
922 /**
923  * xed_notebook_remove_tab:
924  * @nb: a #XedNotebook
925  * @tab: a #XedTab
926  *
927  * Removes @tab from @nb.
928  */
929 void
xed_notebook_remove_tab(XedNotebook * nb,XedTab * tab)930 xed_notebook_remove_tab (XedNotebook *nb,
931                          XedTab      *tab)
932 {
933     gint position, curr;
934 
935     g_return_if_fail (XED_IS_NOTEBOOK (nb));
936     g_return_if_fail (XED_IS_TAB (tab));
937 
938     /* Remove the page from the focused pages list */
939     nb->priv->focused_pages =  g_list_remove (nb->priv->focused_pages, tab);
940 
941     position = gtk_notebook_page_num (GTK_NOTEBOOK (nb), GTK_WIDGET (tab));
942     curr = gtk_notebook_get_current_page (GTK_NOTEBOOK (nb));
943 
944     if (position == curr)
945     {
946         smart_tab_switching_on_closure (nb, tab);
947     }
948 
949     remove_tab (tab, nb);
950 }
951 
952 /**
953  * xed_notebook_remove_all_tabs:
954  * @nb: a #XedNotebook
955  *
956  * Removes all #XedTab from @nb.
957  */
958 void
xed_notebook_remove_all_tabs(XedNotebook * nb)959 xed_notebook_remove_all_tabs (XedNotebook *nb)
960 {
961     g_return_if_fail (XED_IS_NOTEBOOK (nb));
962 
963     g_list_free (nb->priv->focused_pages);
964     nb->priv->focused_pages = NULL;
965 
966     gtk_container_foreach (GTK_CONTAINER (nb), (GtkCallback)remove_tab, nb);
967 }
968 
969 /**
970  * xed_notebook_get_all_tabs:
971  * @nb: a #XedNotebook
972  *
973  * Gets all #XedTab from @nb.
974  * Returns: (element-type GtkWidget) (transfer container): #GList of all tabs
975  */
976 GList *
xed_notebook_get_all_tabs(XedNotebook * nb)977 xed_notebook_get_all_tabs (XedNotebook *nb)
978 {
979     GList *children = NULL;
980 
981     g_return_val_if_fail (XED_IS_NOTEBOOK (nb), NULL);
982 
983     children = gtk_container_get_children (GTK_CONTAINER (nb));
984 
985     return children;
986 }
987 
988 static void
set_close_buttons_sensitivity(XedTab * tab,XedNotebook * nb)989 set_close_buttons_sensitivity (XedTab      *tab,
990                                XedNotebook *nb)
991 {
992     GtkWidget *tab_label;
993 
994     tab_label = get_tab_label (tab);
995 
996     xed_tab_label_set_close_button_sensitive (XED_TAB_LABEL (tab_label), nb->priv->close_buttons_sensitive);
997 }
998 
999 /**
1000  * xed_notebook_set_close_buttons_sensitive:
1001  * @nb: a #XedNotebook
1002  * @sensitive: %TRUE to make the buttons sensitive
1003  *
1004  * Sets whether the close buttons in the tabs of @nb are sensitive.
1005  */
1006 void
xed_notebook_set_close_buttons_sensitive(XedNotebook * nb,gboolean sensitive)1007 xed_notebook_set_close_buttons_sensitive (XedNotebook *nb,
1008                                           gboolean     sensitive)
1009 {
1010     g_return_if_fail (XED_IS_NOTEBOOK (nb));
1011 
1012     sensitive = (sensitive != FALSE);
1013 
1014     if (sensitive == nb->priv->close_buttons_sensitive)
1015     {
1016         return;
1017     }
1018 
1019     nb->priv->close_buttons_sensitive = sensitive;
1020 
1021     gtk_container_foreach (GTK_CONTAINER (nb), (GtkCallback)set_close_buttons_sensitivity, nb);
1022 }
1023 
1024 /**
1025  * xed_notebook_get_close_buttons_sensitive:
1026  * @nb: a #XedNotebook
1027  *
1028  * Whether the close buttons are sensitive.
1029  *
1030  * Returns: %TRUE if the close buttons are sensitive
1031  */
1032 gboolean
xed_notebook_get_close_buttons_sensitive(XedNotebook * nb)1033 xed_notebook_get_close_buttons_sensitive (XedNotebook *nb)
1034 {
1035     g_return_val_if_fail (XED_IS_NOTEBOOK (nb), TRUE);
1036 
1037     return nb->priv->close_buttons_sensitive;
1038 }
1039 
1040 /**
1041  * xed_notebook_set_tab_drag_and_drop_enabled:
1042  * @nb: a #XedNotebook
1043  * @enable: %TRUE to enable the drag and drop
1044  *
1045  * Sets whether drag and drop of tabs in the @nb is enabled.
1046  */
1047 void
xed_notebook_set_tab_drag_and_drop_enabled(XedNotebook * nb,gboolean enable)1048 xed_notebook_set_tab_drag_and_drop_enabled (XedNotebook *nb,
1049                                             gboolean     enable)
1050 {
1051     g_return_if_fail (XED_IS_NOTEBOOK (nb));
1052 
1053     enable = (enable != FALSE);
1054 
1055     if (enable == nb->priv->tab_drag_and_drop_enabled)
1056     {
1057         return;
1058     }
1059 
1060     nb->priv->tab_drag_and_drop_enabled = enable;
1061 }
1062 
1063 /**
1064  * xed_notebook_get_tab_drag_and_drop_enabled:
1065  * @nb: a #XedNotebook
1066  *
1067  * Whether the drag and drop is enabled in the @nb.
1068  *
1069  * Returns: %TRUE if the drag and drop is enabled.
1070  */
1071 gboolean
xed_notebook_get_tab_drag_and_drop_enabled(XedNotebook * nb)1072 xed_notebook_get_tab_drag_and_drop_enabled (XedNotebook *nb)
1073 {
1074     g_return_val_if_fail (XED_IS_NOTEBOOK (nb), TRUE);
1075 
1076     return nb->priv->tab_drag_and_drop_enabled;
1077 }
1078 
1079 /**
1080  * xed_notebook_set_tab_scrolling_enabled:
1081  * @nb: a #XedNotebook
1082  * @enable: %TRUE to enable tab scrolling
1083  *
1084  * Sets whether tab scrolling in the @nb is enabled.
1085  */
1086 void
xed_notebook_set_tab_scrolling_enabled(XedNotebook * nb,gboolean enable)1087 xed_notebook_set_tab_scrolling_enabled (XedNotebook *nb,
1088                                         gboolean     enable)
1089 {
1090     g_return_if_fail (XED_IS_NOTEBOOK (nb));
1091 
1092     enable = (enable != FALSE);
1093 
1094     if (enable == nb->priv->tab_scrolling_enabled)
1095     {
1096         return;
1097     }
1098 
1099     nb->priv->tab_scrolling_enabled = enable;
1100 }
1101 
1102 /**
1103  * xed_notebook_get_tab_scrolling_enabled:
1104  * @nb: a #XedNotebook
1105  *
1106  * Whether notebook tab scrolling is enabled
1107  *
1108  * Returns: %TRUE if tab scrolling is enabled
1109  */
1110 gboolean
xed_notebook_get_tab_scrolling_enabled(XedNotebook * nb)1111 xed_notebook_get_tab_scrolling_enabled (XedNotebook *nb)
1112 {
1113     g_return_val_if_fail (XED_IS_NOTEBOOK (nb), TRUE);
1114 
1115     return nb->priv->tab_scrolling_enabled;
1116 }
1117