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