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