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 (¬ebook->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