Make editor menu initially hidden
[geany-mirror.git] / src / notebook.c
blob0f9e880b2bfdb3585cf9eb269b536d09f473e65f
1 /*
2 * notebook.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2006-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2011 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 * Notebook tab Drag 'n' Drop reordering and tab management.
26 #include "geany.h"
27 #include "notebook.h"
28 #include "document.h"
29 #include "editor.h"
30 #include "documentprivate.h"
31 #include "ui_utils.h"
32 #include "sidebar.h"
33 #include "support.h"
34 #include "callbacks.h"
35 #include "utils.h"
36 #include "keybindings.h"
38 #define GEANY_DND_NOTEBOOK_TAB_TYPE "geany_dnd_notebook_tab"
40 static const GtkTargetEntry drag_targets[] =
42 {GEANY_DND_NOTEBOOK_TAB_TYPE, GTK_TARGET_SAME_APP | GTK_TARGET_SAME_WIDGET, 0}
45 static GtkTargetEntry files_drop_targets[] = {
46 { "STRING", 0, 0 },
47 { "UTF8_STRING", 0, 0 },
48 { "text/plain", 0, 0 },
49 { "text/uri-list", 0, 0 }
53 static void
54 notebook_page_reordered_cb(GtkNotebook *notebook, GtkWidget *child, guint page_num,
55 gpointer user_data);
57 static void
58 on_window_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context,
59 gint x, gint y, GtkSelectionData *data, guint target_type,
60 guint event_time, gpointer user_data);
62 static void
63 notebook_tab_close_clicked_cb(GtkButton *button, gpointer user_data);
65 static void setup_tab_dnd(void);
68 static gboolean focus_sci(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
70 GeanyDocument *doc = document_get_current();
72 if (doc != NULL && event->button == 1)
73 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
75 return FALSE;
79 static gboolean gtk_notebook_show_arrows(GtkNotebook *notebook)
81 return notebook->scrollable;
82 #if 0
83 /* To get this working we would need to define at least the first two fields of
84 * GtkNotebookPage since it is a private field. The better way would be to
85 * subclass GtkNotebook.
86 struct _FakeGtkNotebookPage
88 GtkWidget *child;
89 GtkWidget *tab_label;
92 gboolean show_arrow = FALSE;
93 GList *children;
95 if (! notebook->scrollable)
96 return FALSE;
98 children = notebook->children;
99 while (children)
101 struct _FakeGtkNotebookPage *page = children->data;
103 if (page->tab_label && ! gtk_widget_get_child_visible(page->tab_label))
104 show_arrow = TRUE;
106 children = children->next;
108 return show_arrow;
109 #endif
113 static gboolean is_position_on_tab_bar(GtkNotebook *notebook, GdkEventButton *event)
115 GtkWidget *page;
116 GtkWidget *tab;
117 GtkWidget *nb;
118 GtkPositionType tab_pos;
119 gint scroll_arrow_hlength, scroll_arrow_vlength;
120 gdouble x, y;
122 page = gtk_notebook_get_nth_page(notebook, 0);
123 g_return_val_if_fail(page != NULL, FALSE);
125 tab = gtk_notebook_get_tab_label(notebook, page);
126 g_return_val_if_fail(tab != NULL, FALSE);
128 tab_pos = gtk_notebook_get_tab_pos(notebook);
129 nb = GTK_WIDGET(notebook);
131 gtk_widget_style_get(GTK_WIDGET(notebook), "scroll-arrow-hlength", &scroll_arrow_hlength,
132 "scroll-arrow-vlength", &scroll_arrow_vlength, NULL);
134 if (! gdk_event_get_coords((GdkEvent*) event, &x, &y))
136 x = event->x;
137 y = event->y;
140 switch (tab_pos)
142 case GTK_POS_TOP:
143 case GTK_POS_BOTTOM:
145 if (event->y >= 0 && event->y <= tab->allocation.height)
147 if (! gtk_notebook_show_arrows(notebook) || (
148 x > scroll_arrow_hlength &&
149 x < nb->allocation.width - scroll_arrow_hlength))
150 return TRUE;
152 break;
154 case GTK_POS_LEFT:
155 case GTK_POS_RIGHT:
157 if (event->x >= 0 && event->x <= tab->allocation.width)
159 if (! gtk_notebook_show_arrows(notebook) || (
160 y > scroll_arrow_vlength &&
161 y < nb->allocation.height - scroll_arrow_vlength))
162 return TRUE;
167 return FALSE;
171 static void tab_bar_menu_activate_cb(GtkMenuItem *menuitem, gpointer data)
173 GeanyDocument *doc = data;
175 if (! DOC_VALID(doc))
176 return;
178 document_show_tab(doc);
182 static void on_open_in_new_window_activate(GtkMenuItem *menuitem, gpointer user_data)
184 gchar *geany_path;
185 GeanyDocument *doc = user_data;
187 g_return_if_fail(doc->is_valid);
189 geany_path = g_find_program_in_path("geany");
191 if (geany_path)
193 gchar *doc_path = utils_get_locale_from_utf8(doc->file_name);
194 gchar *argv[] = {geany_path, "-i", doc_path, NULL};
195 GError *err = NULL;
197 if (!utils_spawn_async(NULL, argv, NULL, 0, NULL, NULL, NULL, &err))
199 g_printerr("Unable to open new window: %s", err->message);
200 g_error_free(err);
202 g_free(doc_path);
203 g_free(geany_path);
205 else
206 g_printerr("Unable to find 'geany'");
210 static void show_tab_bar_popup_menu(GdkEventButton *event, GtkWidget *page)
212 GtkWidget *menu_item;
213 static GtkWidget *menu = NULL;
214 GeanyDocument *doc = NULL;
215 gint page_num;
217 if (menu == NULL)
218 menu = gtk_menu_new();
220 /* clear the old menu items */
221 gtk_container_foreach(GTK_CONTAINER(menu), (GtkCallback) gtk_widget_destroy, NULL);
223 ui_menu_add_document_items(GTK_MENU(menu), document_get_current(),
224 G_CALLBACK(tab_bar_menu_activate_cb));
226 menu_item = gtk_separator_menu_item_new();
227 gtk_widget_show(menu_item);
228 gtk_container_add(GTK_CONTAINER(menu), menu_item);
230 if (page != NULL)
232 page_num = gtk_notebook_page_num(GTK_NOTEBOOK(main_widgets.notebook), page);
233 doc = document_get_from_page(page_num);
236 menu_item = ui_image_menu_item_new(GTK_STOCK_OPEN, "Open in New _Window");
237 gtk_widget_show(menu_item);
238 gtk_container_add(GTK_CONTAINER(menu), menu_item);
239 g_signal_connect(menu_item, "activate",
240 G_CALLBACK(on_open_in_new_window_activate), doc);
241 /* disable if not on disk */
242 if (doc == NULL || !doc->real_path)
243 gtk_widget_set_sensitive(menu_item, FALSE);
245 menu_item = gtk_separator_menu_item_new();
246 gtk_widget_show(menu_item);
247 gtk_container_add(GTK_CONTAINER(menu), menu_item);
249 menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL);
250 gtk_widget_show(menu_item);
251 gtk_container_add(GTK_CONTAINER(menu), menu_item);
252 g_signal_connect(menu_item, "activate", G_CALLBACK(notebook_tab_close_clicked_cb), page);
253 gtk_widget_set_sensitive(GTK_WIDGET(menu_item), (page != NULL));
255 menu_item = ui_image_menu_item_new(GTK_STOCK_CLOSE, _("Close Ot_her Documents"));
256 gtk_widget_show(menu_item);
257 gtk_container_add(GTK_CONTAINER(menu), menu_item);
258 g_signal_connect(menu_item, "activate", G_CALLBACK(on_close_other_documents1_activate), page);
259 gtk_widget_set_sensitive(GTK_WIDGET(menu_item), (page != NULL));
261 menu_item = ui_image_menu_item_new(GTK_STOCK_CLOSE, _("C_lose All"));
262 gtk_widget_show(menu_item);
263 gtk_container_add(GTK_CONTAINER(menu), menu_item);
264 g_signal_connect(menu_item, "activate", G_CALLBACK(on_close_all1_activate), NULL);
266 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
270 static gboolean notebook_tab_bar_click_cb(GtkWidget *widget, GdkEventButton *event,
271 gpointer user_data)
273 if (event->type == GDK_2BUTTON_PRESS)
275 /* accessing ::event_window is a little hacky but we need to make sure the click
276 * was in the tab bar and not inside the child */
277 if (event->window != GTK_NOTEBOOK(main_widgets.notebook)->event_window)
278 return FALSE;
280 if (is_position_on_tab_bar(GTK_NOTEBOOK(widget), event))
282 document_new_file(NULL, NULL, NULL);
283 return TRUE;
286 /* right-click is also handled here if it happened on the notebook tab bar but not
287 * on a tab directly */
288 else if (event->button == 3)
290 show_tab_bar_popup_menu(event, NULL);
291 return TRUE;
293 return FALSE;
297 void notebook_init()
299 /* Individual style for the tab close buttons */
300 gtk_rc_parse_string(
301 "style \"geany-close-tab-button-style\" {\n"
302 " GtkWidget::focus-padding = 0\n"
303 " GtkWidget::focus-line-width = 0\n"
304 " xthickness = 0\n"
305 " ythickness = 0\n"
306 "}\n"
307 "widget \"*.geany-close-tab-button\" style \"geany-close-tab-button-style\""
310 g_signal_connect_after(main_widgets.notebook, "button-press-event",
311 G_CALLBACK(notebook_tab_bar_click_cb), NULL);
313 g_signal_connect(main_widgets.notebook, "drag-data-received",
314 G_CALLBACK(on_window_drag_data_received), NULL);
316 setup_tab_dnd();
320 static void setup_tab_dnd()
322 GtkWidget *notebook = main_widgets.notebook;
324 g_signal_connect(notebook, "page-reordered", G_CALLBACK(notebook_page_reordered_cb), NULL);
328 static void
329 notebook_page_reordered_cb(GtkNotebook *notebook, GtkWidget *child, guint page_num,
330 gpointer user_data)
332 /* Not necessary to update open files treeview if it's sorted.
333 * Note: if enabled, it's best to move the item instead of recreating all items. */
334 /*sidebar_openfiles_update_all();*/
338 /* call this after the number of tabs in main_widgets.notebook changes. */
339 static void tab_count_changed(void)
341 switch (gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook)))
343 case 0:
344 /* Enables DnD for dropping files into the empty notebook widget */
345 gtk_drag_dest_set(main_widgets.notebook, GTK_DEST_DEFAULT_ALL,
346 files_drop_targets, G_N_ELEMENTS(files_drop_targets),
347 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
348 break;
350 case 1:
351 /* Disables DnD for dropping files into the notebook widget and enables the DnD for moving file
352 * tabs. Files can still be dropped into the notebook widget because it will be handled by the
353 * active Scintilla Widget (only dropping to the tab bar is not possible but it should be ok) */
354 gtk_drag_dest_set(main_widgets.notebook, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
355 drag_targets, G_N_ELEMENTS(drag_targets), GDK_ACTION_MOVE);
356 break;
361 static gboolean notebook_tab_click(GtkWidget *widget, GdkEventButton *event, gpointer data)
363 guint state;
365 /* toggle additional widgets on double click */
366 if (event->type == GDK_2BUTTON_PRESS)
368 if (interface_prefs.notebook_double_click_hides_widgets)
369 on_menu_toggle_all_additional_widgets1_activate(NULL, NULL);
371 return TRUE; /* stop other handlers like notebook_tab_bar_click_cb() */
373 /* close tab on middle click */
374 if (event->button == 2)
376 document_remove_page(gtk_notebook_page_num(GTK_NOTEBOOK(main_widgets.notebook),
377 GTK_WIDGET(data)));
378 return TRUE; /* stop other handlers like notebook_tab_bar_click_cb() */
380 /* switch last used tab on ctrl-click */
381 state = event->state & gtk_accelerator_get_default_mod_mask();
382 if (event->button == 1 && state == GDK_CONTROL_MASK)
384 keybindings_send_command(GEANY_KEY_GROUP_NOTEBOOK,
385 GEANY_KEYS_NOTEBOOK_SWITCHTABLASTUSED);
386 return TRUE;
388 /* right-click is first handled here if it happened on a notebook tab */
389 if (event->button == 3)
391 show_tab_bar_popup_menu(event, data);
392 return TRUE;
395 return FALSE;
399 static void notebook_tab_close_button_style_set(GtkWidget *btn, GtkRcStyle *prev_style,
400 gpointer data)
402 gint w, h;
404 gtk_icon_size_lookup_for_settings(gtk_widget_get_settings(btn), GTK_ICON_SIZE_MENU, &w, &h);
405 gtk_widget_set_size_request(btn, w + 2, h + 2);
409 /* Returns page number of notebook page, or -1 on error */
410 gint notebook_new_tab(GeanyDocument *this)
412 GtkWidget *hbox, *ebox;
413 gint tabnum;
414 GtkWidget *page;
415 gint cur_page;
417 g_return_val_if_fail(this != NULL, -1);
419 page = GTK_WIDGET(this->editor->sci);
421 this->priv->tab_label = gtk_label_new(NULL);
423 /* get button press events for the tab label and the space between it and
424 * the close button, if any */
425 ebox = gtk_event_box_new();
426 GTK_WIDGET_SET_FLAGS(ebox, GTK_NO_WINDOW);
427 g_signal_connect(ebox, "button-press-event", G_CALLBACK(notebook_tab_click), page);
428 /* focus the current document after clicking on a tab */
429 g_signal_connect_after(ebox, "button-release-event",
430 G_CALLBACK(focus_sci), NULL);
432 hbox = gtk_hbox_new(FALSE, 2);
433 gtk_box_pack_start(GTK_BOX(hbox), this->priv->tab_label, FALSE, FALSE, 0);
434 gtk_container_add(GTK_CONTAINER(ebox), hbox);
436 if (file_prefs.show_tab_cross)
438 GtkWidget *image, *btn, *align;
440 btn = gtk_button_new();
441 gtk_button_set_relief(GTK_BUTTON(btn), GTK_RELIEF_NONE);
442 gtk_button_set_focus_on_click(GTK_BUTTON(btn), FALSE);
443 gtk_widget_set_name(btn, "geany-close-tab-button");
445 image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
446 gtk_container_add(GTK_CONTAINER(btn), image);
448 align = gtk_alignment_new(1.0, 0.5, 0.0, 0.0);
449 gtk_container_add(GTK_CONTAINER(align), btn);
450 gtk_box_pack_start(GTK_BOX(hbox), align, TRUE, TRUE, 0);
452 g_signal_connect(btn, "clicked", G_CALLBACK(notebook_tab_close_clicked_cb), page);
453 /* button overrides event box, so make middle click on button also close tab */
454 g_signal_connect(btn, "button-press-event", G_CALLBACK(notebook_tab_click), page);
455 /* handle style modification to keep button small as possible even when theme change */
456 g_signal_connect(btn, "style-set", G_CALLBACK(notebook_tab_close_button_style_set), NULL);
459 gtk_widget_show_all(ebox);
461 document_update_tab_label(this);
463 if (file_prefs.tab_order_beside)
464 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets.notebook));
465 else
466 cur_page = file_prefs.tab_order_ltr ? -2 /* hack: -2 + 1 = -1, last page */ : 0;
467 if (file_prefs.tab_order_ltr)
468 tabnum = gtk_notebook_insert_page_menu(GTK_NOTEBOOK(main_widgets.notebook), page,
469 ebox, NULL, cur_page + 1);
470 else
471 tabnum = gtk_notebook_insert_page_menu(GTK_NOTEBOOK(main_widgets.notebook), page,
472 ebox, NULL, cur_page);
474 tab_count_changed();
476 /* enable tab DnD */
477 gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(main_widgets.notebook), page, TRUE);
479 return tabnum;
483 static void
484 notebook_tab_close_clicked_cb(GtkButton *button, gpointer user_data)
486 gint cur_page = gtk_notebook_page_num(GTK_NOTEBOOK(main_widgets.notebook),
487 GTK_WIDGET(user_data));
489 document_remove_page(cur_page);
493 /* Always use this instead of gtk_notebook_remove_page(). */
494 void notebook_remove_page(gint page_num)
496 gint curpage = gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets.notebook));
498 /* Focus the next page, not the previous */
499 if (curpage == page_num && file_prefs.tab_order_ltr)
501 gtk_notebook_set_current_page(GTK_NOTEBOOK(main_widgets.notebook), curpage + 1);
504 /* now remove the page (so we don't temporarily switch to the previous page) */
505 gtk_notebook_remove_page(GTK_NOTEBOOK(main_widgets.notebook), page_num);
507 tab_count_changed();
511 static void
512 on_window_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context,
513 gint x, gint y, GtkSelectionData *data, guint target_type,
514 guint event_time, gpointer user_data)
516 gboolean success = FALSE;
518 if (data->length > 0 && data->format == 8)
520 if (drag_context->action == GDK_ACTION_ASK)
522 drag_context->action = GDK_ACTION_COPY;
525 document_open_file_list((const gchar *)data->data, data->length);
527 success = TRUE;
529 gtk_drag_finish(drag_context, success, FALSE, event_time);