1 /*
2 * notebook.c - this file is part of Geany, a fast and lightweight IDE
3 *
4 * Copyright 2006 The Geany contributors
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 /*
22 * Notebook tab Drag 'n' Drop reordering and tab management.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include "notebook.h"
30
31 #include "callbacks.h"
32 #include "documentprivate.h"
33 #include "geanyobject.h"
34 #include "keybindings.h"
35 #include "main.h"
36 #include "support.h"
37 #include "ui_utils.h"
38 #include "utils.h"
39
40 #include "gtkcompat.h"
41
42 #include <gdk/gdkkeysyms.h>
43
44
45 #define GEANY_DND_NOTEBOOK_TAB_TYPE "geany_dnd_notebook_tab"
46
47 static const GtkTargetEntry drag_targets[] =
48 {
49 {GEANY_DND_NOTEBOOK_TAB_TYPE, GTK_TARGET_SAME_APP | GTK_TARGET_SAME_WIDGET, 0}
50 };
51
52 static GtkTargetEntry files_drop_targets[] = {
53 { "STRING", 0, 0 },
54 { "UTF8_STRING", 0, 0 },
55 { "text/plain", 0, 0 },
56 { "text/uri-list", 0, 0 }
57 };
58
59 static const gsize MAX_MRU_DOCS = 20;
60 static GQueue *mru_docs = NULL;
61 static guint mru_pos = 0;
62
63 static gboolean switch_in_progress = FALSE;
64 static GtkWidget *switch_dialog = NULL;
65 static GtkWidget *switch_dialog_label = NULL;
66
67
68 static void
69 notebook_page_reordered_cb(GtkNotebook *notebook, GtkWidget *child, guint page_num,
70 gpointer user_data);
71
72 static void
73 on_window_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context,
74 gint x, gint y, GtkSelectionData *data, guint target_type,
75 guint event_time, gpointer user_data);
76
77 static void
78 notebook_tab_close_clicked_cb(GtkButton *button, gpointer user_data);
79
80 static void setup_tab_dnd(void);
81
82
update_mru_docs_head(GeanyDocument * doc)83 static void update_mru_docs_head(GeanyDocument *doc)
84 {
85 if (doc)
86 {
87 g_queue_remove(mru_docs, doc);
88 g_queue_push_head(mru_docs, doc);
89
90 if (g_queue_get_length(mru_docs) > MAX_MRU_DOCS)
91 g_queue_pop_tail(mru_docs);
92 }
93 }
94
95
96 /* before the tab changes, add the current document to the MRU list */
on_notebook_switch_page(GtkNotebook * notebook,gpointer page,guint page_num,gpointer user_data)97 static void on_notebook_switch_page(GtkNotebook *notebook,
98 gpointer page, guint page_num, gpointer user_data)
99 {
100 GeanyDocument *new;
101
102 new = document_get_from_page(page_num);
103
104 /* insert the very first document (when adding the second document
105 * and switching to it) */
106 if (g_queue_get_length(mru_docs) == 0 && gtk_notebook_get_n_pages(notebook) == 2)
107 update_mru_docs_head(document_get_current());
108
109 if (!switch_in_progress)
110 update_mru_docs_head(new);
111 }
112
113
on_document_close(GObject * obj,GeanyDocument * doc)114 static void on_document_close(GObject *obj, GeanyDocument *doc)
115 {
116 if (! main_status.quitting)
117 {
118 g_queue_remove(mru_docs, doc);
119 /* this prevents the pop up window from showing when there's a single
120 * document */
121 if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)) == 2)
122 g_queue_clear(mru_docs);
123 }
124 }
125
126
ui_minimal_dialog_new(GtkWindow * parent,const gchar * title)127 static GtkWidget *ui_minimal_dialog_new(GtkWindow *parent, const gchar *title)
128 {
129 GtkWidget *dialog;
130
131 dialog = gtk_window_new(GTK_WINDOW_POPUP);
132
133 if (parent)
134 {
135 gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
136 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
137 }
138 gtk_window_set_title(GTK_WINDOW(dialog), title);
139 gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
140 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
141
142 gtk_widget_set_name(dialog, "GeanyDialog");
143 return dialog;
144 }
145
146
is_modifier_key(guint keyval)147 static gboolean is_modifier_key(guint keyval)
148 {
149 switch (keyval)
150 {
151 case GDK_Shift_L:
152 case GDK_Shift_R:
153 case GDK_Control_L:
154 case GDK_Control_R:
155 case GDK_Meta_L:
156 case GDK_Meta_R:
157 case GDK_Alt_L:
158 case GDK_Alt_R:
159 case GDK_Super_L:
160 case GDK_Super_R:
161 case GDK_Hyper_L:
162 case GDK_Hyper_R:
163 return TRUE;
164 default:
165 return FALSE;
166 }
167 }
168
169
on_key_release_event(GtkWidget * widget,GdkEventKey * ev,gpointer user_data)170 static gboolean on_key_release_event(GtkWidget *widget, GdkEventKey *ev, gpointer user_data)
171 {
172 /* user may have rebound keybinding to a different modifier than Ctrl, so check all */
173 if (switch_in_progress && is_modifier_key(ev->keyval))
174 {
175 GeanyDocument *doc;
176
177 switch_in_progress = FALSE;
178
179 if (switch_dialog)
180 {
181 gtk_widget_destroy(switch_dialog);
182 switch_dialog = NULL;
183 }
184
185 doc = document_get_current();
186 update_mru_docs_head(doc);
187 mru_pos = 0;
188 document_check_disk_status(doc, TRUE);
189 }
190 return FALSE;
191 }
192
193
create_switch_dialog(void)194 static GtkWidget *create_switch_dialog(void)
195 {
196 GtkWidget *dialog, *widget, *vbox;
197
198 dialog = ui_minimal_dialog_new(GTK_WINDOW(main_widgets.window), _("Switch to Document"));
199 gtk_window_set_decorated(GTK_WINDOW(dialog), FALSE);
200 gtk_window_set_default_size(GTK_WINDOW(dialog), 200, -1);
201
202 vbox = gtk_vbox_new(FALSE, 6);
203 gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
204 gtk_container_add(GTK_CONTAINER(dialog), vbox);
205
206 widget = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON);
207 gtk_container_add(GTK_CONTAINER(vbox), widget);
208
209 widget = gtk_label_new(NULL);
210 gtk_label_set_justify(GTK_LABEL(widget), GTK_JUSTIFY_CENTER);
211 gtk_container_add(GTK_CONTAINER(vbox), widget);
212 switch_dialog_label = widget;
213
214 g_signal_connect(dialog, "key-release-event", G_CALLBACK(on_key_release_event), NULL);
215 return dialog;
216 }
217
218
update_filename_label(void)219 static void update_filename_label(void)
220 {
221 guint i;
222 gchar *msg = NULL;
223 guint queue_length;
224 GeanyDocument *doc;
225
226 if (!switch_dialog)
227 {
228 switch_dialog = create_switch_dialog();
229 gtk_widget_show_all(switch_dialog);
230 }
231
232 queue_length = g_queue_get_length(mru_docs);
233 for (i = mru_pos; (i <= mru_pos + 3) && (doc = g_queue_peek_nth(mru_docs, i % queue_length)); i++)
234 {
235 gchar *basename;
236
237 basename = g_path_get_basename(DOC_FILENAME(doc));
238 if (i == mru_pos)
239 msg = g_markup_printf_escaped ("<b>%s</b>", basename);
240 else if (i % queue_length == mru_pos) /* && i != mru_pos */
241 {
242 /* We have wrapped around and got to the starting document again */
243 g_free(basename);
244 break;
245 }
246 else
247 {
248 SETPTR(basename, g_markup_printf_escaped ("\n%s", basename));
249 SETPTR(msg, g_strconcat(msg, basename, NULL));
250 }
251 g_free(basename);
252 }
253 gtk_label_set_markup(GTK_LABEL(switch_dialog_label), msg);
254 g_free(msg);
255 }
256
257
on_switch_timeout(G_GNUC_UNUSED gpointer data)258 static gboolean on_switch_timeout(G_GNUC_UNUSED gpointer data)
259 {
260 if (!switch_in_progress || switch_dialog)
261 {
262 return FALSE;
263 }
264
265 update_filename_label();
266 return FALSE;
267 }
268
269
notebook_switch_tablastused(void)270 void notebook_switch_tablastused(void)
271 {
272 GeanyDocument *last_doc;
273 gboolean switch_start = !switch_in_progress;
274
275 mru_pos += 1;
276 last_doc = g_queue_peek_nth(mru_docs, mru_pos);
277
278 if (! DOC_VALID(last_doc))
279 {
280 utils_beep();
281 mru_pos = 0;
282 last_doc = g_queue_peek_nth(mru_docs, mru_pos);
283 }
284 if (! DOC_VALID(last_doc))
285 return;
286
287 switch_in_progress = TRUE;
288 document_show_tab(last_doc);
289
290 /* if there's a modifier key, we can switch back in MRU order each time unless
291 * the key is released */
292 if (switch_start)
293 g_timeout_add(600, on_switch_timeout, NULL);
294 else
295 update_filename_label();
296 }
297
298
notebook_switch_in_progress(void)299 gboolean notebook_switch_in_progress(void)
300 {
301 return switch_in_progress;
302 }
303
304
focus_sci(GtkWidget * widget,GdkEventButton * event,gpointer user_data)305 static gboolean focus_sci(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
306 {
307 GeanyDocument *doc = document_get_current();
308
309 if (doc != NULL && event->button == 1)
310 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
311
312 return FALSE;
313 }
314
315
gtk_notebook_show_arrows(GtkNotebook * notebook)316 static gboolean gtk_notebook_show_arrows(GtkNotebook *notebook)
317 {
318 return gtk_notebook_get_scrollable(notebook);
319 #if 0
320 /* To get this working we would need to define at least the first two fields of
321 * GtkNotebookPage since it is a private field. The better way would be to
322 * subclass GtkNotebook.
323 struct _FakeGtkNotebookPage
324 {
325 GtkWidget *child;
326 GtkWidget *tab_label;
327 };
328 */
329 gboolean show_arrow = FALSE;
330 GList *children;
331
332 if (! notebook->scrollable)
333 return FALSE;
334
335 children = notebook->children;
336 while (children)
337 {
338 struct _FakeGtkNotebookPage *page = children->data;
339
340 if (page->tab_label && ! gtk_widget_get_child_visible(page->tab_label))
341 show_arrow = TRUE;
342
343 children = children->next;
344 }
345 return show_arrow;
346 #endif
347 }
348
349
is_position_on_tab_bar(GtkNotebook * notebook,GdkEventButton * event)350 static gboolean is_position_on_tab_bar(GtkNotebook *notebook, GdkEventButton *event)
351 {
352 GtkWidget *page;
353 GtkWidget *tab;
354 GtkWidget *nb;
355 GtkPositionType tab_pos;
356 gint scroll_arrow_hlength, scroll_arrow_vlength;
357 gdouble x, y;
358
359 page = gtk_notebook_get_nth_page(notebook, 0);
360 g_return_val_if_fail(page != NULL, FALSE);
361
362 tab = gtk_notebook_get_tab_label(notebook, page);
363 g_return_val_if_fail(tab != NULL, FALSE);
364
365 tab_pos = gtk_notebook_get_tab_pos(notebook);
366 nb = GTK_WIDGET(notebook);
367
368 gtk_widget_style_get(GTK_WIDGET(notebook), "scroll-arrow-hlength", &scroll_arrow_hlength,
369 "scroll-arrow-vlength", &scroll_arrow_vlength, NULL);
370
371 if (! gdk_event_get_coords((GdkEvent*) event, &x, &y))
372 {
373 x = event->x;
374 y = event->y;
375 }
376
377 switch (tab_pos)
378 {
379 case GTK_POS_TOP:
380 case GTK_POS_BOTTOM:
381 {
382 if (event->y >= 0 && event->y <= gtk_widget_get_allocated_height(tab))
383 {
384 if (! gtk_notebook_show_arrows(notebook) || (
385 x > scroll_arrow_hlength &&
386 x < gtk_widget_get_allocated_width(nb) - scroll_arrow_hlength))
387 return TRUE;
388 }
389 break;
390 }
391 case GTK_POS_LEFT:
392 case GTK_POS_RIGHT:
393 {
394 if (event->x >= 0 && event->x <= gtk_widget_get_allocated_width(tab))
395 {
396 if (! gtk_notebook_show_arrows(notebook) || (
397 y > scroll_arrow_vlength &&
398 y < gtk_widget_get_allocated_height(nb) - scroll_arrow_vlength))
399 return TRUE;
400 }
401 }
402 }
403
404 return FALSE;
405 }
406
407
tab_bar_menu_activate_cb(GtkMenuItem * menuitem,gpointer data)408 static void tab_bar_menu_activate_cb(GtkMenuItem *menuitem, gpointer data)
409 {
410 GeanyDocument *doc = data;
411
412 if (! DOC_VALID(doc))
413 return;
414
415 document_show_tab(doc);
416 }
417
418
on_open_in_new_window_activate(GtkMenuItem * menuitem,gpointer user_data)419 static void on_open_in_new_window_activate(GtkMenuItem *menuitem, gpointer user_data)
420 {
421 GeanyDocument *doc = user_data;
422 gchar *doc_path;
423
424 g_return_if_fail(doc->is_valid);
425
426 doc_path = utils_get_locale_from_utf8(doc->file_name);
427 utils_start_new_geany_instance(doc_path);
428 g_free(doc_path);
429 }
430
431
has_tabs_on_right(GeanyDocument * doc)432 static gboolean has_tabs_on_right(GeanyDocument *doc)
433 {
434 GtkNotebook *nb = GTK_NOTEBOOK(main_widgets.notebook);
435 gint total_pages = gtk_notebook_get_n_pages(nb);
436 gint doc_page = document_get_notebook_page(doc);
437 return total_pages > (doc_page + 1);
438 }
439
440
on_close_documents_right_activate(GtkMenuItem * menuitem,GeanyDocument * doc)441 static void on_close_documents_right_activate(GtkMenuItem *menuitem, GeanyDocument *doc)
442 {
443 g_return_if_fail(has_tabs_on_right(doc));
444 GtkNotebook *nb = GTK_NOTEBOOK(main_widgets.notebook);
445 gint current_page = gtk_notebook_get_current_page(nb);
446 gint doc_page = document_get_notebook_page(doc);
447 for (gint i = doc_page + 1; i < gtk_notebook_get_n_pages(nb); )
448 {
449 if (! document_close(document_get_from_page(i)))
450 i++; // only increment if tab wasn't closed
451 }
452 /* keep the current tab to the original one unless it has been closed, in
453 * which case use the activated one */
454 gtk_notebook_set_current_page(nb, MIN(current_page, doc_page));
455 }
456
457
show_tab_bar_popup_menu(GdkEventButton * event,GeanyDocument * doc)458 static void show_tab_bar_popup_menu(GdkEventButton *event, GeanyDocument *doc)
459 {
460 GtkWidget *menu_item;
461 static GtkWidget *menu = NULL;
462
463 if (menu == NULL)
464 menu = gtk_menu_new();
465
466 /* clear the old menu items */
467 gtk_container_foreach(GTK_CONTAINER(menu), (GtkCallback) gtk_widget_destroy, NULL);
468
469 ui_menu_add_document_items(GTK_MENU(menu), document_get_current(),
470 G_CALLBACK(tab_bar_menu_activate_cb));
471
472 menu_item = gtk_separator_menu_item_new();
473 gtk_widget_show(menu_item);
474 gtk_container_add(GTK_CONTAINER(menu), menu_item);
475
476 menu_item = ui_image_menu_item_new(GTK_STOCK_OPEN, _("Open in New _Window"));
477 gtk_widget_show(menu_item);
478 gtk_container_add(GTK_CONTAINER(menu), menu_item);
479 g_signal_connect(menu_item, "activate",
480 G_CALLBACK(on_open_in_new_window_activate), doc);
481 /* disable if not on disk */
482 if (doc == NULL || !doc->real_path)
483 gtk_widget_set_sensitive(menu_item, FALSE);
484
485 menu_item = gtk_separator_menu_item_new();
486 gtk_widget_show(menu_item);
487 gtk_container_add(GTK_CONTAINER(menu), menu_item);
488
489 menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL);
490 gtk_widget_show(menu_item);
491 gtk_container_add(GTK_CONTAINER(menu), menu_item);
492 g_signal_connect(menu_item, "activate", G_CALLBACK(notebook_tab_close_clicked_cb), doc);
493 gtk_widget_set_sensitive(GTK_WIDGET(menu_item), (doc != NULL));
494
495 menu_item = ui_image_menu_item_new(GTK_STOCK_CLOSE, _("Close Ot_her Documents"));
496 gtk_widget_show(menu_item);
497 gtk_container_add(GTK_CONTAINER(menu), menu_item);
498 g_signal_connect(menu_item, "activate", G_CALLBACK(on_close_other_documents1_activate), doc);
499 gtk_widget_set_sensitive(GTK_WIDGET(menu_item), (doc != NULL));
500
501 menu_item = ui_image_menu_item_new(GTK_STOCK_CLOSE, _("Close Documents to the _Right"));
502 gtk_widget_show(menu_item);
503 gtk_container_add(GTK_CONTAINER(menu), menu_item);
504 g_signal_connect(menu_item, "activate", G_CALLBACK(on_close_documents_right_activate), doc);
505 gtk_widget_set_sensitive(GTK_WIDGET(menu_item), doc != NULL && has_tabs_on_right(doc));
506
507 menu_item = ui_image_menu_item_new(GTK_STOCK_CLOSE, _("C_lose All"));
508 gtk_widget_show(menu_item);
509 gtk_container_add(GTK_CONTAINER(menu), menu_item);
510 g_signal_connect(menu_item, "activate", G_CALLBACK(on_close_all1_activate), NULL);
511
512 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
513 }
514
515
notebook_tab_bar_click_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)516 static gboolean notebook_tab_bar_click_cb(GtkWidget *widget, GdkEventButton *event,
517 gpointer user_data)
518 {
519 if (event->type == GDK_2BUTTON_PRESS)
520 {
521 GtkNotebook *notebook = GTK_NOTEBOOK(widget);
522 GtkWidget *event_widget = gtk_get_event_widget((GdkEvent *) event);
523 GtkWidget *child = gtk_notebook_get_nth_page(notebook, gtk_notebook_get_current_page(notebook));
524
525 /* ignore events from the content of the page (impl. stolen from GTK2 tab scrolling)
526 * TODO: we should also ignore notebook's action widgets, but that's more work and
527 * we don't have any of them yet anyway -- and GTK 2.16 don't have those actions. */
528 if (event_widget == NULL || event_widget == child || gtk_widget_is_ancestor(event_widget, child))
529 return FALSE;
530
531 if (is_position_on_tab_bar(notebook, event))
532 {
533 document_new_file(NULL, NULL, NULL);
534 return TRUE;
535 }
536 }
537 /* right-click is also handled here if it happened on the notebook tab bar but not
538 * on a tab directly */
539 else if (event->button == 3)
540 {
541 show_tab_bar_popup_menu(event, NULL);
542 return TRUE;
543 }
544 return FALSE;
545 }
546
547
notebook_init(void)548 void notebook_init(void)
549 {
550 g_signal_connect_after(main_widgets.notebook, "button-press-event",
551 G_CALLBACK(notebook_tab_bar_click_cb), NULL);
552
553 g_signal_connect(main_widgets.notebook, "drag-data-received",
554 G_CALLBACK(on_window_drag_data_received), NULL);
555
556 mru_docs = g_queue_new();
557 g_signal_connect(main_widgets.notebook, "switch-page",
558 G_CALLBACK(on_notebook_switch_page), NULL);
559 g_signal_connect(geany_object, "document-close",
560 G_CALLBACK(on_document_close), NULL);
561
562 /* in case the switch dialog misses an event while drawing the dialog */
563 g_signal_connect(main_widgets.window, "key-release-event", G_CALLBACK(on_key_release_event), NULL);
564
565 setup_tab_dnd();
566 }
567
568
notebook_free(void)569 void notebook_free(void)
570 {
571 g_queue_free(mru_docs);
572 }
573
574
setup_tab_dnd(void)575 static void setup_tab_dnd(void)
576 {
577 GtkWidget *notebook = main_widgets.notebook;
578
579 g_signal_connect(notebook, "page-reordered", G_CALLBACK(notebook_page_reordered_cb), NULL);
580 }
581
582
583 static void
notebook_page_reordered_cb(GtkNotebook * notebook,GtkWidget * child,guint page_num,gpointer user_data)584 notebook_page_reordered_cb(GtkNotebook *notebook, GtkWidget *child, guint page_num,
585 gpointer user_data)
586 {
587 /* Not necessary to update open files treeview if it's sorted.
588 * Note: if enabled, it's best to move the item instead of recreating all items. */
589 /*sidebar_openfiles_update_all();*/
590 }
591
592
593 /* call this after the number of tabs in main_widgets.notebook changes. */
tab_count_changed(void)594 static void tab_count_changed(void)
595 {
596 switch (gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)))
597 {
598 case 0:
599 /* Enables DnD for dropping files into the empty notebook widget */
600 gtk_drag_dest_set(main_widgets.notebook, GTK_DEST_DEFAULT_ALL,
601 files_drop_targets, G_N_ELEMENTS(files_drop_targets),
602 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
603 break;
604
605 case 1:
606 /* Disables DnD for dropping files into the notebook widget and enables the DnD for moving file
607 * tabs. Files can still be dropped into the notebook widget because it will be handled by the
608 * active Scintilla Widget (only dropping to the tab bar is not possible but it should be ok) */
609 gtk_drag_dest_set(main_widgets.notebook, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
610 drag_targets, G_N_ELEMENTS(drag_targets), GDK_ACTION_MOVE);
611 break;
612 }
613 }
614
615
notebook_tab_click(GtkWidget * widget,GdkEventButton * event,gpointer data)616 static gboolean notebook_tab_click(GtkWidget *widget, GdkEventButton *event, gpointer data)
617 {
618 guint state;
619 GeanyDocument *doc = (GeanyDocument *) data;
620
621 /* toggle additional widgets on double click */
622 if (event->type == GDK_2BUTTON_PRESS)
623 {
624 if (interface_prefs.notebook_double_click_hides_widgets)
625 on_menu_toggle_all_additional_widgets1_activate(NULL, NULL);
626
627 return TRUE; /* stop other handlers like notebook_tab_bar_click_cb() */
628 }
629 /* close tab on middle click */
630 if (event->button == 2)
631 {
632 document_close(doc);
633 return TRUE; /* stop other handlers like notebook_tab_bar_click_cb() */
634 }
635 /* switch last used tab on ctrl-click */
636 state = keybindings_get_modifiers(event->state);
637 if (event->button == 1 && state == GEANY_PRIMARY_MOD_MASK)
638 {
639 keybindings_send_command(GEANY_KEY_GROUP_NOTEBOOK,
640 GEANY_KEYS_NOTEBOOK_SWITCHTABLASTUSED);
641 return TRUE;
642 }
643 /* right-click is first handled here if it happened on a notebook tab */
644 if (event->button == 3)
645 {
646 show_tab_bar_popup_menu(event, doc);
647 return TRUE;
648 }
649
650 return FALSE;
651 }
652
653
notebook_tab_close_button_style_set(GtkWidget * btn,GtkRcStyle * prev_style,gpointer data)654 static void notebook_tab_close_button_style_set(GtkWidget *btn, GtkRcStyle *prev_style,
655 gpointer data)
656 {
657 gint w, h;
658
659 gtk_icon_size_lookup_for_settings(gtk_widget_get_settings(btn), GTK_ICON_SIZE_MENU, &w, &h);
660 gtk_widget_set_size_request(btn, w + 2, h + 2);
661 }
662
663
664 /* Returns page number of notebook page, or -1 on error
665 *
666 * Note: the widget added to the notebook is *not* shown by this function, so you have to call
667 * something like `gtk_widget_show(document_get_notebook_child(doc))` when finished setting up the
668 * document. This is necessary because when the notebook tab is added, the document isn't ready
669 * yet, and we need the notebook to emit ::switch-page after it actually is. Actually this
670 * doesn't prevent the signal to me emitted straight when we insert the page (this looks like a
671 * GTK bug), but it emits it again when showing the child, and it's all we need. */
notebook_new_tab(GeanyDocument * this)672 gint notebook_new_tab(GeanyDocument *this)
673 {
674 GtkWidget *hbox, *ebox, *vbox;
675 gint tabnum;
676 GtkWidget *page;
677 gint cur_page;
678
679 g_return_val_if_fail(this != NULL, -1);
680
681 /* page is packed into a vbox so we can stack infobars above it */
682 vbox = gtk_vbox_new(FALSE, 0);
683 page = GTK_WIDGET(this->editor->sci);
684 gtk_box_pack_start(GTK_BOX(vbox), page, TRUE, TRUE, 0);
685
686 this->priv->tab_label = gtk_label_new(NULL);
687
688 /* get button press events for the tab label and the space between it and
689 * the close button, if any */
690 ebox = gtk_event_box_new();
691 gtk_widget_set_has_window(ebox, FALSE);
692 g_signal_connect(ebox, "button-press-event", G_CALLBACK(notebook_tab_click), this);
693 /* focus the current document after clicking on a tab */
694 g_signal_connect_after(ebox, "button-release-event",
695 G_CALLBACK(focus_sci), NULL);
696
697 hbox = gtk_hbox_new(FALSE, 2);
698 gtk_box_pack_start(GTK_BOX(hbox), this->priv->tab_label, FALSE, FALSE, 0);
699 gtk_container_add(GTK_CONTAINER(ebox), hbox);
700
701 if (file_prefs.show_tab_cross)
702 {
703 GtkWidget *image, *btn, *align;
704
705 btn = gtk_button_new();
706 gtk_button_set_relief(GTK_BUTTON(btn), GTK_RELIEF_NONE);
707 gtk_button_set_focus_on_click(GTK_BUTTON(btn), FALSE);
708 gtk_widget_set_name(btn, "geany-close-tab-button");
709
710 image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
711 gtk_container_add(GTK_CONTAINER(btn), image);
712
713 align = gtk_alignment_new(1.0, 0.5, 0.0, 0.0);
714 gtk_container_add(GTK_CONTAINER(align), btn);
715 gtk_box_pack_start(GTK_BOX(hbox), align, TRUE, TRUE, 0);
716
717 g_signal_connect(btn, "clicked", G_CALLBACK(notebook_tab_close_clicked_cb), this);
718 /* button overrides event box, so make middle click on button also close tab */
719 g_signal_connect(btn, "button-press-event", G_CALLBACK(notebook_tab_click), this);
720 /* handle style modification to keep button small as possible even when theme change */
721 g_signal_connect(btn, "style-set", G_CALLBACK(notebook_tab_close_button_style_set), NULL);
722 }
723
724 gtk_widget_show_all(ebox);
725
726 document_update_tab_label(this);
727
728 if (file_prefs.tab_order_beside)
729 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets.notebook));
730 else
731 cur_page = file_prefs.tab_order_ltr ? -2 /* hack: -2 + 1 = -1, last page */ : 0;
732 if (file_prefs.tab_order_ltr)
733 tabnum = gtk_notebook_insert_page_menu(GTK_NOTEBOOK(main_widgets.notebook), vbox,
734 ebox, NULL, cur_page + 1);
735 else
736 tabnum = gtk_notebook_insert_page_menu(GTK_NOTEBOOK(main_widgets.notebook), vbox,
737 ebox, NULL, cur_page);
738
739 tab_count_changed();
740
741 /* enable tab DnD */
742 gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(main_widgets.notebook), vbox, TRUE);
743
744 return tabnum;
745 }
746
747
748 static void
notebook_tab_close_clicked_cb(GtkButton * button,gpointer data)749 notebook_tab_close_clicked_cb(GtkButton *button, gpointer data)
750 {
751 GeanyDocument *doc = (GeanyDocument *) data;
752
753 document_close(doc);
754 }
755
756
757 /* Always use this instead of gtk_notebook_remove_page(). */
notebook_remove_page(gint page_num)758 void notebook_remove_page(gint page_num)
759 {
760 gint page = gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets.notebook));
761
762 if (page_num == page)
763 {
764 if (file_prefs.tab_order_ltr)
765 page += 1;
766 else if (page > 0) /* never go negative, it would select the last page */
767 page -= 1;
768
769 if (file_prefs.tab_close_switch_to_mru)
770 {
771 GeanyDocument *last_doc;
772
773 last_doc = g_queue_peek_nth(mru_docs, 0);
774 if (DOC_VALID(last_doc))
775 page = document_get_notebook_page(last_doc);
776 }
777
778 gtk_notebook_set_current_page(GTK_NOTEBOOK(main_widgets.notebook), page);
779 }
780
781 /* now remove the page (so we don't temporarily switch to the previous page) */
782 gtk_notebook_remove_page(GTK_NOTEBOOK(main_widgets.notebook), page_num);
783
784 tab_count_changed();
785 }
786
787
788 static void
on_window_drag_data_received(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,GtkSelectionData * data,guint target_type,guint event_time,gpointer user_data)789 on_window_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context,
790 gint x, gint y, GtkSelectionData *data, guint target_type,
791 guint event_time, gpointer user_data)
792 {
793 gboolean success = FALSE;
794 gint length = gtk_selection_data_get_length(data);
795
796 if (length > 0 && gtk_selection_data_get_format(data) == 8)
797 {
798 document_open_file_list((const gchar *)gtk_selection_data_get_data(data), length);
799
800 success = TRUE;
801 }
802 gtk_drag_finish(drag_context, success, FALSE, event_time);
803 }
804