1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright © 2002 Christophe Fergeau
4  *  Copyright © 2003, 2004 Marco Pesenti Gritti
5  *  Copyright © 2003, 2004, 2005 Christian Persch
6  *    (ephy-notebook.c)
7  *
8  *  Copyright © 2008 Free Software Foundation, Inc.
9  *    (caja-notebook.c)
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2, or (at your option)
14  *  any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  */
26 
27 #include <config.h>
28 
29 #include <glib/gi18n.h>
30 #include <gio/gio.h>
31 #include <gtk/gtk.h>
32 
33 #include <libcaja-private/caja-dnd.h>
34 
35 #include "caja-notebook.h"
36 #include "caja-navigation-window.h"
37 #include "caja-window-manage-views.h"
38 #include "caja-window-private.h"
39 #include "caja-window-slot.h"
40 #include "caja-navigation-window-pane.h"
41 
42 #define AFTER_ALL_TABS -1
43 
44 static void caja_notebook_constructed (GObject *object);
45 
46 static int caja_notebook_insert_page (GtkNotebook *notebook,
47                                       GtkWidget   *child,
48                                       GtkWidget   *tab_label,
49                                       GtkWidget   *menu_label,
50                                       int          position);
51 
52 static void caja_notebook_remove (GtkContainer  *container,
53                                   GtkWidget     *tab_widget);
54 
55 static gboolean caja_notebook_scroll_event (GtkWidget      *widget,
56                                             GdkEventScroll *event);
57 
58 enum
59 {
60     TAB_CLOSE_REQUEST,
61     LAST_SIGNAL
62 };
63 
64 static guint signals[LAST_SIGNAL] = { 0 };
65 
66 G_DEFINE_TYPE (CajaNotebook, caja_notebook, GTK_TYPE_NOTEBOOK);
67 
68 static void
caja_notebook_class_init(CajaNotebookClass * klass)69 caja_notebook_class_init (CajaNotebookClass *klass)
70 {
71     GObjectClass *object_class = G_OBJECT_CLASS (klass);
72     GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
73     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
74     GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
75 
76     object_class->constructed = caja_notebook_constructed;
77 
78     container_class->remove = caja_notebook_remove;
79 
80     widget_class->scroll_event = caja_notebook_scroll_event;
81 
82     notebook_class->insert_page = caja_notebook_insert_page;
83 
84     signals[TAB_CLOSE_REQUEST] =
85         g_signal_new ("tab-close-request",
86                       G_OBJECT_CLASS_TYPE (object_class),
87                       G_SIGNAL_RUN_LAST,
88                       G_STRUCT_OFFSET (CajaNotebookClass, tab_close_request),
89                       NULL, NULL,
90                       g_cclosure_marshal_VOID__OBJECT,
91                       G_TYPE_NONE,
92                       1,
93                       CAJA_TYPE_WINDOW_SLOT);
94 }
95 
96 static gint
find_tab_num_at_pos(CajaNotebook * notebook,gint abs_x,gint abs_y)97 find_tab_num_at_pos (CajaNotebook *notebook, gint abs_x, gint abs_y)
98 {
99     GtkPositionType tab_pos;
100     int page_num = 0;
101     GtkNotebook *nb = GTK_NOTEBOOK (notebook);
102     GtkWidget *page;
103     GtkAllocation allocation;
104 
105     tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook));
106 
107     while ((page = gtk_notebook_get_nth_page (nb, page_num)))
108     {
109         GtkWidget *tab;
110         gint max_x, max_y;
111         gint x_root, y_root;
112 
113         tab = gtk_notebook_get_tab_label (nb, page);
114         g_return_val_if_fail (tab != NULL, -1);
115 
116         if (!gtk_widget_get_mapped (GTK_WIDGET (tab)))
117         {
118             page_num++;
119             continue;
120         }
121 
122         gdk_window_get_origin (gtk_widget_get_window (tab),
123                                &x_root, &y_root);
124         gtk_widget_get_allocation (tab, &allocation);
125 
126         max_x = x_root + allocation.x + allocation.width;
127         max_y = y_root + allocation.y + allocation.height;
128 
129         if (((tab_pos == GTK_POS_TOP)
130                 || (tab_pos == GTK_POS_BOTTOM))
131                 &&(abs_x<=max_x))
132         {
133             return page_num;
134         }
135         else if (((tab_pos == GTK_POS_LEFT)
136                   || (tab_pos == GTK_POS_RIGHT))
137                  && (abs_y<=max_y))
138         {
139             return page_num;
140         }
141 
142         page_num++;
143     }
144     return AFTER_ALL_TABS;
145 }
146 
147 static gboolean
button_press_cb(CajaNotebook * notebook,GdkEventButton * event,gpointer data)148 button_press_cb (CajaNotebook *notebook,
149                  GdkEventButton *event,
150                  gpointer data)
151 {
152     int tab_clicked;
153 
154     tab_clicked = find_tab_num_at_pos (notebook, event->x_root, event->y_root);
155 
156     if (event->type == GDK_BUTTON_PRESS &&
157             (event->button == 3 || event->button == 2) &&
158             (event->state & gtk_accelerator_get_default_mod_mask ()) == 0)
159     {
160         if (tab_clicked == -1)
161         {
162             /* consume event, so that we don't pop up the context menu when
163              * the mouse if not over a tab label
164              */
165             return TRUE;
166         }
167 
168         /* switch to the page the mouse is over, but don't consume the event */
169         gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), tab_clicked);
170     }
171 
172     return FALSE;
173 }
174 
175 static void
caja_notebook_init(CajaNotebook * notebook)176 caja_notebook_init (CajaNotebook *notebook)
177 {
178     GtkStyleContext *context;
179 
180     context = gtk_widget_get_style_context (GTK_WIDGET (notebook));
181     gtk_style_context_add_class (context, "caja-notebook");
182 
183     gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
184     gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
185     gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
186 
187     g_signal_connect (notebook, "button-press-event",
188                       (GCallback)button_press_cb, NULL);
189 }
190 
191 static void
caja_notebook_constructed(GObject * object)192 caja_notebook_constructed (GObject *object)
193 {
194     GtkWidget *widget = GTK_WIDGET (object);
195 
196     G_OBJECT_CLASS (caja_notebook_parent_class)->constructed (object);
197 
198     /* Necessary for scroll events */
199     gtk_widget_add_events (widget, GDK_SCROLL_MASK);
200 }
201 
202 
203 void
caja_notebook_sync_loading(CajaNotebook * notebook,CajaWindowSlot * slot)204 caja_notebook_sync_loading (CajaNotebook *notebook,
205                             CajaWindowSlot *slot)
206 {
207     GtkWidget *tab_label, *spinner, *icon;
208     gboolean active;
209 
210     g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
211     g_return_if_fail (CAJA_IS_WINDOW_SLOT (slot));
212 
213     tab_label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook), slot->content_box);
214     g_return_if_fail (GTK_IS_WIDGET (tab_label));
215 
216     spinner = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "spinner"));
217     icon = GTK_WIDGET (g_object_get_data (G_OBJECT (tab_label), "icon"));
218     g_return_if_fail (spinner != NULL && icon != NULL);
219 
220     active = FALSE;
221     g_object_get (spinner, "active", &active, NULL);
222     if (active == slot->allow_stop)
223     {
224         return;
225     }
226 
227     if (slot->allow_stop)
228     {
229         gtk_widget_hide (icon);
230         gtk_widget_show (spinner);
231         gtk_spinner_start (GTK_SPINNER (spinner));
232     }
233     else
234     {
235         gtk_spinner_stop (GTK_SPINNER (spinner));
236         gtk_widget_hide (spinner);
237         gtk_widget_show (icon);
238     }
239 }
240 
241 void
caja_notebook_sync_tab_label(CajaNotebook * notebook,CajaWindowSlot * slot)242 caja_notebook_sync_tab_label (CajaNotebook *notebook,
243                               CajaWindowSlot *slot)
244 {
245     GtkWidget *hbox, *label;
246 
247     g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
248     g_return_if_fail (CAJA_IS_WINDOW_SLOT (slot));
249     g_return_if_fail (GTK_IS_WIDGET (slot->content_box));
250 
251     hbox = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook), slot->content_box);
252     g_return_if_fail (GTK_IS_WIDGET (hbox));
253 
254     label = GTK_WIDGET (g_object_get_data (G_OBJECT (hbox), "label"));
255     g_return_if_fail (GTK_IS_WIDGET (label));
256 
257     gtk_label_set_text (GTK_LABEL (label), slot->title);
258 
259     if (slot->location != NULL)
260     {
261         char *location_name;
262 
263         /* Set the tooltip on the label's parent (the tab label hbox),
264          * so it covers all of the tab label.
265          */
266         location_name = g_file_get_parse_name (slot->location);
267         gtk_widget_set_tooltip_text (gtk_widget_get_parent (label), location_name);
268         g_free (location_name);
269     }
270     else
271     {
272         gtk_widget_set_tooltip_text (gtk_widget_get_parent (label), NULL);
273     }
274 }
275 
276 static void
close_button_clicked_cb(GtkWidget * widget,CajaWindowSlot * slot)277 close_button_clicked_cb (GtkWidget *widget,
278                          CajaWindowSlot *slot)
279 {
280     GtkWidget *notebook;
281 
282     notebook = gtk_widget_get_ancestor (slot->content_box, CAJA_TYPE_NOTEBOOK);
283     if (notebook != NULL)
284     {
285         g_signal_emit (notebook, signals[TAB_CLOSE_REQUEST], 0, slot);
286     }
287 }
288 
289 static GtkWidget *
build_tab_label(CajaNotebook * nb,CajaWindowSlot * slot)290 build_tab_label (CajaNotebook *nb, CajaWindowSlot *slot)
291 {
292     CajaDragSlotProxyInfo *drag_info;
293     GtkWidget *hbox, *label, *close_button, *image, *spinner, *icon;
294 
295     /* set hbox spacing and label padding (see below) so that there's an
296      * equal amount of space around the label */
297     hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
298     gtk_widget_show (hbox);
299 
300     /* setup load feedback */
301     spinner = gtk_spinner_new ();
302     gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
303 
304     /* setup site icon, empty by default */
305     icon = gtk_image_new ();
306     gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
307     /* don't show the icon */
308 
309     /* setup label */
310     label = gtk_label_new (NULL);
311     gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
312     gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
313     gtk_label_set_xalign (GTK_LABEL (label), 0.0);
314     gtk_label_set_yalign (GTK_LABEL (label), 0.5);
315 
316     gtk_widget_set_margin_start (label, 0);
317     gtk_widget_set_margin_end (label, 0);
318     gtk_widget_set_margin_top (label, 0);
319     gtk_widget_set_margin_bottom (label, 0);
320 
321     gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
322     gtk_widget_show (label);
323 
324     /* setup close button */
325     close_button = gtk_button_new ();
326     gtk_button_set_relief (GTK_BUTTON (close_button),
327                            GTK_RELIEF_NONE);
328     /* don't allow focus on the close button */
329     gtk_widget_set_focus_on_click (close_button, FALSE);
330 
331     gtk_widget_set_name (close_button, "caja-tab-close-button");
332 
333     image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
334     gtk_widget_set_tooltip_text (close_button, _("Close tab"));
335     g_signal_connect_object (close_button, "clicked",
336                              G_CALLBACK (close_button_clicked_cb), slot, 0);
337 
338     gtk_container_add (GTK_CONTAINER (close_button), image);
339     gtk_widget_show (image);
340 
341     gtk_box_pack_start (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
342     gtk_widget_show (close_button);
343 
344     drag_info = g_new0 (CajaDragSlotProxyInfo, 1);
345     drag_info->target_slot = slot;
346     g_object_set_data_full (G_OBJECT (hbox), "proxy-drag-info",
347                             drag_info, (GDestroyNotify) g_free);
348 
349     caja_drag_slot_proxy_init (hbox, drag_info);
350 
351     g_object_set_data (G_OBJECT (hbox), "label", label);
352     g_object_set_data (G_OBJECT (hbox), "spinner", spinner);
353     g_object_set_data (G_OBJECT (hbox), "icon", icon);
354     g_object_set_data (G_OBJECT (hbox), "close-button", close_button);
355 
356     return hbox;
357 }
358 
359 static int
caja_notebook_insert_page(GtkNotebook * gnotebook,GtkWidget * tab_widget,GtkWidget * tab_label,GtkWidget * menu_label,int position)360 caja_notebook_insert_page (GtkNotebook *gnotebook,
361                            GtkWidget *tab_widget,
362                            GtkWidget *tab_label,
363                            GtkWidget *menu_label,
364                            int position)
365 {
366     g_assert (GTK_IS_WIDGET (tab_widget));
367 
368     position = GTK_NOTEBOOK_CLASS (caja_notebook_parent_class)->insert_page (gnotebook,
369                tab_widget,
370                tab_label,
371                menu_label,
372                position);
373 
374     gtk_notebook_set_show_tabs (gnotebook,
375                                 gtk_notebook_get_n_pages (gnotebook) > 1);
376     gtk_notebook_set_tab_reorderable (gnotebook, tab_widget, TRUE);
377 
378     return position;
379 }
380 
381 int
caja_notebook_add_tab(CajaNotebook * notebook,CajaWindowSlot * slot,int position,gboolean jump_to)382 caja_notebook_add_tab (CajaNotebook *notebook,
383                        CajaWindowSlot *slot,
384                        int position,
385                        gboolean jump_to)
386 {
387     GtkNotebook *gnotebook = GTK_NOTEBOOK (notebook);
388     GtkWidget *tab_label;
389 
390     g_return_val_if_fail (CAJA_IS_NOTEBOOK (notebook), -1);
391     g_return_val_if_fail (CAJA_IS_WINDOW_SLOT (slot), -1);
392 
393     tab_label = build_tab_label (notebook, slot);
394 
395     position = gtk_notebook_insert_page (GTK_NOTEBOOK (notebook),
396                                          slot->content_box,
397                                          tab_label,
398                                          position);
399 
400     gtk_container_child_set (GTK_CONTAINER (notebook),
401                              slot->content_box,
402                              "tab-expand", TRUE,
403                              NULL);
404 
405     caja_notebook_sync_tab_label (notebook, slot);
406     caja_notebook_sync_loading (notebook, slot);
407 
408 
409     /* FIXME gtk bug! */
410     /* FIXME: this should be fixed in gtk 2.12; check & remove this! */
411     /* The signal handler may have reordered the tabs */
412     position = gtk_notebook_page_num (gnotebook, slot->content_box);
413 
414     if (jump_to)
415     {
416         gtk_notebook_set_current_page (gnotebook, position);
417 
418     }
419 
420     return position;
421 }
422 
423 static void
caja_notebook_remove(GtkContainer * container,GtkWidget * tab_widget)424 caja_notebook_remove (GtkContainer *container,
425                       GtkWidget *tab_widget)
426 {
427     GtkNotebook *gnotebook = GTK_NOTEBOOK (container);
428     GTK_CONTAINER_CLASS (caja_notebook_parent_class)->remove (container, tab_widget);
429 
430     gtk_notebook_set_show_tabs (gnotebook,
431                                 gtk_notebook_get_n_pages (gnotebook) > 1);
432 
433 }
434 
435 void
caja_notebook_reorder_current_child_relative(CajaNotebook * notebook,int offset)436 caja_notebook_reorder_current_child_relative (CajaNotebook *notebook,
437         int offset)
438 {
439     GtkNotebook *gnotebook;
440     GtkWidget *child;
441     int page;
442 
443     g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
444 
445     if (!caja_notebook_can_reorder_current_child_relative (notebook, offset))
446     {
447         return;
448     }
449 
450     gnotebook = GTK_NOTEBOOK (notebook);
451 
452     page = gtk_notebook_get_current_page (gnotebook);
453     child = gtk_notebook_get_nth_page (gnotebook, page);
454     gtk_notebook_reorder_child (gnotebook, child, page + offset);
455 }
456 
457 void
caja_notebook_set_current_page_relative(CajaNotebook * notebook,int offset)458 caja_notebook_set_current_page_relative (CajaNotebook *notebook,
459         int offset)
460 {
461     GtkNotebook *gnotebook;
462     int page;
463 
464     g_return_if_fail (CAJA_IS_NOTEBOOK (notebook));
465 
466     if (!caja_notebook_can_set_current_page_relative (notebook, offset))
467     {
468         return;
469     }
470 
471     gnotebook = GTK_NOTEBOOK (notebook);
472 
473     page = gtk_notebook_get_current_page (gnotebook);
474     gtk_notebook_set_current_page (gnotebook, page + offset);
475 
476 }
477 
478 static gboolean
caja_notebook_is_valid_relative_position(CajaNotebook * notebook,int offset)479 caja_notebook_is_valid_relative_position (CajaNotebook *notebook,
480         int offset)
481 {
482     GtkNotebook *gnotebook;
483     int page;
484     int n_pages;
485 
486     gnotebook = GTK_NOTEBOOK (notebook);
487 
488     page = gtk_notebook_get_current_page (gnotebook);
489     n_pages = gtk_notebook_get_n_pages (gnotebook) - 1;
490     if (page < 0 ||
491             (offset < 0 && page < -offset) ||
492             (offset > 0 && page > n_pages - offset))
493     {
494         return FALSE;
495     }
496 
497     return TRUE;
498 }
499 
500 gboolean
caja_notebook_can_reorder_current_child_relative(CajaNotebook * notebook,int offset)501 caja_notebook_can_reorder_current_child_relative (CajaNotebook *notebook,
502         int offset)
503 {
504     g_return_val_if_fail (CAJA_IS_NOTEBOOK (notebook), FALSE);
505 
506     return caja_notebook_is_valid_relative_position (notebook, offset);
507 }
508 
509 gboolean
caja_notebook_can_set_current_page_relative(CajaNotebook * notebook,int offset)510 caja_notebook_can_set_current_page_relative (CajaNotebook *notebook,
511         int offset)
512 {
513     g_return_val_if_fail (CAJA_IS_NOTEBOOK (notebook), FALSE);
514 
515     return caja_notebook_is_valid_relative_position (notebook, offset);
516 }
517 
518 /* Tab scrolling was removed from GtkNotebook in gtk 3, so reimplement it here */
519 static gboolean
caja_notebook_scroll_event(GtkWidget * widget,GdkEventScroll * event)520 caja_notebook_scroll_event (GtkWidget      *widget,
521                             GdkEventScroll *event)
522 {
523     GtkNotebook *notebook = GTK_NOTEBOOK (widget);
524     gboolean (* scroll_event) (GtkWidget *, GdkEventScroll *) =
525         GTK_WIDGET_CLASS (caja_notebook_parent_class)->scroll_event;
526     GtkWidget *child, *event_widget, *action_widget;
527 
528     if ((event->state & gtk_accelerator_get_default_mod_mask ()) != 0)
529         goto chain_up;
530 
531     child = gtk_notebook_get_nth_page (notebook, gtk_notebook_get_current_page (notebook));
532     if (child == NULL)
533         goto chain_up;
534 
535     event_widget = gtk_get_event_widget ((GdkEvent *) event);
536 
537     /* Ignore scroll events from the content of the page */
538     if (event_widget == NULL || event_widget == child || gtk_widget_is_ancestor (event_widget, child))
539         goto chain_up;
540 
541     /* And also from the action widgets */
542     action_widget = gtk_notebook_get_action_widget (notebook, GTK_PACK_START);
543     if (event_widget == action_widget || (action_widget != NULL && gtk_widget_is_ancestor (event_widget, action_widget)))
544         goto chain_up;
545 
546     action_widget = gtk_notebook_get_action_widget (notebook, GTK_PACK_END);
547     if (event_widget == action_widget || (action_widget != NULL && gtk_widget_is_ancestor (event_widget, action_widget)))
548         goto chain_up;
549 
550     switch (event->direction) {
551         case GDK_SCROLL_RIGHT:
552         case GDK_SCROLL_DOWN:
553             gtk_notebook_next_page (notebook);
554             return TRUE;
555         case GDK_SCROLL_LEFT:
556             case GDK_SCROLL_UP:
557             gtk_notebook_prev_page (notebook);
558             return TRUE;
559         case GDK_SCROLL_SMOOTH:
560             switch (gtk_notebook_get_tab_pos (notebook)) {
561                 case GTK_POS_LEFT:
562                 case GTK_POS_RIGHT:
563                     if (event->delta_y > 0)
564                         gtk_notebook_next_page (notebook);
565                     else if (event->delta_y < 0)
566                         gtk_notebook_prev_page (notebook);
567                     break;
568                 case GTK_POS_TOP:
569                 case GTK_POS_BOTTOM:
570                     if (event->delta_x > 0)
571                         gtk_notebook_next_page (notebook);
572                     else if (event->delta_x < 0)
573                         gtk_notebook_prev_page (notebook);
574                     break;
575             }
576             return TRUE;
577     }
578 
579 chain_up:
580     if (scroll_event)
581         return scroll_event (widget, event);
582 
583     return FALSE;
584 }
585