Fix auto-displaying of sidebar tab bar when sidebar is reshown.
[geany-mirror.git] / src / sidebar.c
blob6fc85a3a690f6f3a7d4cd7cedeb6693133d2bb67
1 /*
2 * sidebar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $Id$
25 * Sidebar related code for the Symbol list and Open files GtkTreeViews.
28 #include <string.h>
30 #include "geany.h"
31 #include "support.h"
32 #include "callbacks.h"
33 #include "sidebar.h"
34 #include "document.h"
35 #include "editor.h"
36 #include "documentprivate.h"
37 #include "filetypes.h"
38 #include "utils.h"
39 #include "ui_utils.h"
40 #include "symbols.h"
41 #include "navqueue.h"
42 #include "project.h"
43 #include "stash.h"
44 #include "keyfile.h"
45 #include "sciwrappers.h"
46 #include "search.h"
48 #include <gdk/gdkkeysyms.h>
51 SidebarTreeviews tv = {NULL, NULL, NULL};
52 /* while typeahead searching, editor should not get focus */
53 static gboolean may_steal_focus = FALSE;
55 static struct
57 GtkWidget *close;
58 GtkWidget *save;
59 GtkWidget *reload;
60 GtkWidget *show_paths;
61 GtkWidget *find_in_files;
63 doc_items = {NULL, NULL, NULL, NULL, NULL};
65 static struct
67 GtkTreeSelection *selection;
68 guint keyval;
69 } selection_change = {NULL, 0};
71 enum
73 TREEVIEW_SYMBOL = 0,
74 TREEVIEW_OPENFILES
77 enum
79 OPENFILES_ACTION_REMOVE = 0,
80 OPENFILES_ACTION_SAVE,
81 OPENFILES_ACTION_RELOAD
84 /* documents tree model columns */
85 enum
87 DOCUMENTS_ICON,
88 DOCUMENTS_SHORTNAME, /* dirname for parents, basename for children */
89 DOCUMENTS_DOCUMENT,
90 DOCUMENTS_COLOR,
91 DOCUMENTS_FILENAME /* full filename */
94 static GtkTreeStore *store_openfiles;
95 static GtkWidget *openfiles_popup_menu;
96 static gboolean documents_show_paths;
97 static GtkWidget *tag_window; /* scrolled window that holds the symbol list GtkTreeView */
99 /* callback prototypes */
100 static gboolean on_openfiles_tree_selection_changed(gpointer data);
101 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data);
102 static gboolean on_taglist_tree_selection_changed(gpointer data);
103 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
104 gpointer user_data);
105 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
106 gpointer user_data);
107 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data);
108 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data);
109 static void documents_menu_update(GtkTreeSelection *selection);
110 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
111 guint page_num, gpointer data);
114 /* the prepare_* functions are document-related, but I think they fit better here than in document.c */
115 static void prepare_taglist(GtkWidget *tree, GtkTreeStore *store)
117 GtkCellRenderer *text_renderer, *icon_renderer;
118 GtkTreeViewColumn *column;
119 GtkTreeSelection *selection;
121 text_renderer = gtk_cell_renderer_text_new();
122 icon_renderer = gtk_cell_renderer_pixbuf_new();
123 column = gtk_tree_view_column_new();
125 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
126 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", SYMBOLS_COLUMN_ICON, NULL);
127 g_object_set(icon_renderer, "xalign", 0.0, NULL);
129 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
130 gtk_tree_view_column_set_attributes(column, text_renderer, "text", SYMBOLS_COLUMN_NAME, NULL);
131 g_object_set(text_renderer, "yalign", 0.5, NULL);
132 gtk_tree_view_column_set_title(column, _("Symbols"));
134 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
135 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
137 ui_widget_modify_font_from_string(tree, interface_prefs.tagbar_font);
139 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
140 g_object_unref(store);
142 g_signal_connect(tree, "button-press-event",
143 G_CALLBACK(sidebar_button_press_cb), NULL);
144 g_signal_connect(tree, "key-press-event",
145 G_CALLBACK(sidebar_key_press_cb), NULL);
147 if (gtk_check_version(2, 12, 0) == NULL)
149 g_object_set(tree, "show-expanders", interface_prefs.show_symbol_list_expanders, NULL);
150 if (! interface_prefs.show_symbol_list_expanders)
151 g_object_set(tree, "level-indentation", 10, NULL);
152 /* Tooltips */
153 g_object_set(tree,
154 "has-tooltip", TRUE,
155 "tooltip-column", SYMBOLS_COLUMN_TOOLTIP, NULL);
158 /* selection handling */
159 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
160 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
161 /* callback for changed selection not necessary, will be handled by button-press-event */
165 static gboolean
166 on_default_tag_tree_button_press_event(GtkWidget *widget, GdkEventButton *event,
167 gpointer user_data)
169 if (event->button == 3)
171 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
172 event->button, event->time);
173 return TRUE;
175 return FALSE;
179 static void create_default_tag_tree(void)
181 GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(tag_window);
182 GtkWidget *label;
184 /* default_tag_tree is a GtkViewPort with a GtkLabel inside it */
185 tv.default_tag_tree = gtk_viewport_new(
186 gtk_scrolled_window_get_hadjustment(scrolled_window),
187 gtk_scrolled_window_get_vadjustment(scrolled_window));
188 label = gtk_label_new(_("No tags found"));
189 gtk_misc_set_alignment(GTK_MISC(label), 0.1f, 0.01f);
190 gtk_container_add(GTK_CONTAINER(tv.default_tag_tree), label);
191 gtk_widget_show_all(tv.default_tag_tree);
192 g_signal_connect(tv.default_tag_tree, "button-press-event",
193 G_CALLBACK(on_default_tag_tree_button_press_event), NULL);
194 g_object_ref((gpointer)tv.default_tag_tree); /* to hold it after removing */
198 /* update = rescan the tags for doc->filename */
199 void sidebar_update_tag_list(GeanyDocument *doc, gboolean update)
201 if (gtk_bin_get_child(GTK_BIN(tag_window)))
202 gtk_container_remove(GTK_CONTAINER(tag_window), gtk_bin_get_child(GTK_BIN(tag_window)));
204 if (tv.default_tag_tree == NULL)
205 create_default_tag_tree();
207 /* show default empty tag tree if there are no tags */
208 if (doc == NULL || doc->file_type == NULL || ! filetype_has_tags(doc->file_type))
210 gtk_container_add(GTK_CONTAINER(tag_window), tv.default_tag_tree);
211 return;
214 if (update)
215 { /* updating the tag list in the left tag window */
216 if (doc->priv->tag_tree == NULL)
218 doc->priv->tag_store = gtk_tree_store_new(
219 SYMBOLS_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING);
220 doc->priv->tag_tree = gtk_tree_view_new();
221 prepare_taglist(doc->priv->tag_tree, doc->priv->tag_store);
222 gtk_widget_show(doc->priv->tag_tree);
223 g_object_ref((gpointer)doc->priv->tag_tree); /* to hold it after removing */
226 doc->has_tags = symbols_recreate_tag_list(doc, SYMBOLS_SORT_USE_PREVIOUS);
229 if (doc->has_tags)
231 gtk_container_add(GTK_CONTAINER(tag_window), doc->priv->tag_tree);
233 else
235 gtk_container_add(GTK_CONTAINER(tag_window), tv.default_tag_tree);
240 /* cleverly sorts documents by their short name */
241 static gint documents_sort_func(GtkTreeModel *model, GtkTreeIter *iter_a,
242 GtkTreeIter *iter_b, gpointer data)
244 gchar *key_a, *key_b;
245 gchar *name_a, *name_b;
246 gint cmp;
248 gtk_tree_model_get(model, iter_a, DOCUMENTS_SHORTNAME, &name_a, -1);
249 key_a = g_utf8_collate_key_for_filename(name_a, -1);
250 g_free(name_a);
251 gtk_tree_model_get(model, iter_b, DOCUMENTS_SHORTNAME, &name_b, -1);
252 key_b = g_utf8_collate_key_for_filename(name_b, -1);
253 g_free(name_b);
254 cmp = strcmp(key_a, key_b);
255 g_free(key_b);
256 g_free(key_a);
258 return cmp;
262 /* does some preparing things to the open files list widget */
263 static void prepare_openfiles(void)
265 GtkCellRenderer *icon_renderer;
266 GtkCellRenderer *text_renderer;
267 GtkTreeViewColumn *column;
268 GtkTreeSelection *selection;
269 GtkTreeSortable *sortable;
271 tv.tree_openfiles = ui_lookup_widget(main_widgets.window, "treeview6");
273 /* store the icon and the short filename to show, and the index as reference,
274 * the colour (black/red/green) and the full name for the tooltip */
275 store_openfiles = gtk_tree_store_new(5, GDK_TYPE_PIXBUF, G_TYPE_STRING,
276 G_TYPE_POINTER, GDK_TYPE_COLOR, G_TYPE_STRING);
277 gtk_tree_view_set_model(GTK_TREE_VIEW(tv.tree_openfiles), GTK_TREE_MODEL(store_openfiles));
279 /* set policy settings for the scolledwindow around the treeview again, because glade
280 * doesn't keep the settings */
281 gtk_scrolled_window_set_policy(
282 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets.window, "scrolledwindow7")),
283 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
285 icon_renderer = gtk_cell_renderer_pixbuf_new();
286 text_renderer = gtk_cell_renderer_text_new();
287 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
288 column = gtk_tree_view_column_new();
289 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
290 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", DOCUMENTS_ICON, NULL);
291 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
292 gtk_tree_view_column_set_attributes(column, text_renderer, "text", DOCUMENTS_SHORTNAME,
293 "foreground-gdk", DOCUMENTS_COLOR, NULL);
294 gtk_tree_view_append_column(GTK_TREE_VIEW(tv.tree_openfiles), column);
295 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv.tree_openfiles), FALSE);
297 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv.tree_openfiles),
298 DOCUMENTS_SHORTNAME);
300 /* sort opened filenames in the store_openfiles treeview */
301 sortable = GTK_TREE_SORTABLE(GTK_TREE_MODEL(store_openfiles));
302 gtk_tree_sortable_set_sort_func(sortable, DOCUMENTS_SHORTNAME, documents_sort_func, NULL, NULL);
303 gtk_tree_sortable_set_sort_column_id(sortable, DOCUMENTS_SHORTNAME, GTK_SORT_ASCENDING);
305 ui_widget_modify_font_from_string(tv.tree_openfiles, interface_prefs.tagbar_font);
307 /* GTK 2.12 tooltips */
308 if (gtk_check_version(2, 12, 0) == NULL)
309 g_object_set(tv.tree_openfiles, "has-tooltip", TRUE, "tooltip-column", DOCUMENTS_FILENAME, NULL);
311 /* selection handling */
312 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
313 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
314 g_object_unref(store_openfiles);
316 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "button-press-event",
317 G_CALLBACK(sidebar_button_press_cb), NULL);
318 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "key-press-event",
319 G_CALLBACK(sidebar_key_press_cb), NULL);
323 /* iter should be toplevel */
324 static gboolean find_tree_iter_dir(GtkTreeIter *iter, const gchar *dir)
326 GeanyDocument *doc;
327 gchar *name;
328 gboolean result;
330 if (utils_str_equal(dir, "."))
331 dir = GEANY_STRING_UNTITLED;
333 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
334 g_return_val_if_fail(!doc, FALSE);
336 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_SHORTNAME, &name, -1);
338 result = utils_str_equal(name, dir);
339 g_free(name);
341 return result;
345 static gchar *get_doc_folder(const gchar *path)
347 gchar *tmp_dirname = g_strdup(path);
348 gchar *project_base_path;
349 gchar *dirname = NULL;
350 const gchar *home_dir = g_get_home_dir();
351 const gchar *rest;
353 /* replace the project base path with the project name */
354 project_base_path = project_get_base_path();
356 if (project_base_path != NULL)
358 gsize len = strlen(project_base_path);
360 if (project_base_path[len-1] == G_DIR_SEPARATOR)
361 project_base_path[len-1] = '\0';
363 /* check whether the dir name matches or uses the project base path */
364 if (g_str_has_prefix(tmp_dirname, project_base_path))
366 rest = tmp_dirname + len;
367 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
369 dirname = g_strdup_printf("%s%s", app->project->name, rest);
372 g_free(project_base_path);
374 if (dirname == NULL)
376 dirname = tmp_dirname;
378 /* If matches home dir, replace with tilde */
379 if (home_dir && *home_dir != 0 && g_str_has_prefix(dirname, home_dir))
381 rest = dirname + strlen(home_dir);
382 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
384 dirname = g_strdup_printf("~%s", rest);
385 g_free(tmp_dirname);
389 else
390 g_free(tmp_dirname);
392 return dirname;
396 static GtkTreeIter *get_doc_parent(GeanyDocument *doc)
398 gchar *path;
399 gchar *dirname = NULL;
400 static GtkTreeIter parent;
401 GtkTreeModel *model = GTK_TREE_MODEL(store_openfiles);
402 static GdkPixbuf *dir_icon = NULL;
404 if (!documents_show_paths)
405 return NULL;
407 path = g_path_get_dirname(DOC_FILENAME(doc));
408 dirname = get_doc_folder(path);
410 if (gtk_tree_model_get_iter_first(model, &parent))
414 if (find_tree_iter_dir(&parent, dirname))
416 g_free(dirname);
417 g_free(path);
418 return &parent;
421 while (gtk_tree_model_iter_next(model, &parent));
423 /* no match, add dir parent */
424 if (!dir_icon)
425 dir_icon = ui_get_mime_icon("inode/directory", GTK_ICON_SIZE_MENU);
427 gtk_tree_store_append(store_openfiles, &parent, NULL);
428 gtk_tree_store_set(store_openfiles, &parent, DOCUMENTS_ICON, dir_icon,
429 DOCUMENTS_FILENAME, path,
430 DOCUMENTS_SHORTNAME, doc->file_name ? dirname : GEANY_STRING_UNTITLED, -1);
432 g_free(dirname);
433 g_free(path);
434 return &parent;
438 /* Also sets doc->priv->iter.
439 * This is called recursively in sidebar_openfiles_update_all(). */
440 void sidebar_openfiles_add(GeanyDocument *doc)
442 GtkTreeIter *iter = &doc->priv->iter;
443 GtkTreeIter *parent = get_doc_parent(doc);
444 gchar *basename;
445 const GdkColor *color = document_get_status_color(doc);
446 static GdkPixbuf *file_icon = NULL;
448 gtk_tree_store_append(store_openfiles, iter, parent);
450 /* check if new parent */
451 if (parent && gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), parent) == 1)
453 GtkTreePath *path;
455 /* expand parent */
456 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), parent);
457 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
458 gtk_tree_path_free(path);
460 if (!file_icon)
461 file_icon = ui_get_mime_icon("text/plain", GTK_ICON_SIZE_MENU);
463 basename = g_path_get_basename(DOC_FILENAME(doc));
464 gtk_tree_store_set(store_openfiles, iter,
465 DOCUMENTS_ICON, (doc->file_type && doc->file_type->icon) ? doc->file_type->icon : file_icon,
466 DOCUMENTS_SHORTNAME, basename, DOCUMENTS_DOCUMENT, doc, DOCUMENTS_COLOR, color,
467 DOCUMENTS_FILENAME, DOC_FILENAME(doc), -1);
468 g_free(basename);
472 static void openfiles_remove(GeanyDocument *doc)
474 GtkTreeIter *iter = &doc->priv->iter;
475 GtkTreeIter parent;
477 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter) &&
478 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), &parent) == 1)
479 gtk_tree_store_remove(store_openfiles, &parent);
480 else
481 gtk_tree_store_remove(store_openfiles, iter);
485 void sidebar_openfiles_update(GeanyDocument *doc)
487 GtkTreeIter *iter = &doc->priv->iter;
488 gchar *fname;
490 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_FILENAME, &fname, -1);
492 if (utils_str_equal(fname, DOC_FILENAME(doc)))
494 /* just update color and the icon */
495 const GdkColor *color = document_get_status_color(doc);
496 GdkPixbuf *icon = doc->file_type->icon;
498 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_COLOR, color, -1);
499 if (icon)
500 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_ICON, icon, -1);
502 else
504 /* path has changed, so remove and re-add */
505 GtkTreeSelection *treesel;
506 gboolean sel;
508 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
509 sel = gtk_tree_selection_iter_is_selected(treesel, &doc->priv->iter);
510 openfiles_remove(doc);
512 sidebar_openfiles_add(doc);
513 if (sel)
514 gtk_tree_selection_select_iter(treesel, &doc->priv->iter);
516 g_free(fname);
520 void sidebar_openfiles_update_all()
522 guint i, page_count;
523 GeanyDocument *doc;
525 gtk_tree_store_clear(store_openfiles);
526 page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
527 for (i = 0; i < page_count; i++)
529 doc = document_get_from_page(i);
530 if (G_UNLIKELY(doc == NULL))
531 continue;
533 sidebar_openfiles_add(doc);
538 void sidebar_remove_document(GeanyDocument *doc)
540 openfiles_remove(doc);
542 if (GTK_IS_WIDGET(doc->priv->tag_tree))
544 gtk_widget_destroy(doc->priv->tag_tree);
545 if (GTK_IS_TREE_VIEW(doc->priv->tag_tree))
547 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
548 g_object_unref((gpointer)doc->priv->tag_tree);
550 doc->priv->tag_tree = NULL;
555 static void on_hide_sidebar(void)
557 ui_prefs.sidebar_visible = FALSE;
558 ui_sidebar_show_hide();
562 static gboolean on_sidebar_display_symbol_list_show(GtkWidget *item)
564 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
565 interface_prefs.sidebar_symbol_visible);
566 return FALSE;
570 static gboolean on_sidebar_display_open_files_show(GtkWidget *item)
572 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
573 interface_prefs.sidebar_openfiles_visible);
574 return FALSE;
578 void sidebar_add_common_menu_items(GtkMenu *menu)
580 GtkWidget *item;
582 item = gtk_separator_menu_item_new();
583 gtk_widget_show(item);
584 gtk_container_add(GTK_CONTAINER(menu), item);
586 item = gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
587 gtk_container_add(GTK_CONTAINER(menu), item);
588 g_signal_connect(item, "expose-event",
589 G_CALLBACK(on_sidebar_display_symbol_list_show), NULL);
590 gtk_widget_show(item);
591 g_signal_connect(item, "activate",
592 G_CALLBACK(on_list_symbol_activate), NULL);
594 item = gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
595 gtk_container_add(GTK_CONTAINER(menu), item);
596 g_signal_connect(item, "expose-event",
597 G_CALLBACK(on_sidebar_display_open_files_show), NULL);
598 gtk_widget_show(item);
599 g_signal_connect(item, "activate",
600 G_CALLBACK(on_list_document_activate), NULL);
602 item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
603 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
604 gtk_image_new_from_stock("gtk-close", GTK_ICON_SIZE_MENU));
605 gtk_widget_show(item);
606 gtk_container_add(GTK_CONTAINER(menu), item);
607 g_signal_connect(item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
611 static void on_openfiles_show_paths_activate(GtkCheckMenuItem *item, gpointer user_data)
613 documents_show_paths = gtk_check_menu_item_get_active(item);
614 sidebar_openfiles_update_all();
618 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data)
620 interface_prefs.sidebar_openfiles_visible = gtk_check_menu_item_get_active(item);
621 ui_sidebar_show_hide();
622 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
626 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data)
628 interface_prefs.sidebar_symbol_visible = gtk_check_menu_item_get_active(item);
629 ui_sidebar_show_hide();
630 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
634 static void on_find_in_files(GtkMenuItem *menuitem, gpointer user_data)
636 GtkTreeSelection *treesel;
637 GtkTreeIter iter;
638 GtkTreeModel *model;
639 GeanyDocument *doc;
640 gchar *dir;
642 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
643 if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
644 return;
645 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
647 if (!doc)
649 gtk_tree_model_get(model, &iter, DOCUMENTS_FILENAME, &dir, -1);
651 else
652 dir = g_path_get_dirname(DOC_FILENAME(doc));
654 search_show_find_in_files_dialog(dir);
655 g_free(dir);
659 static void create_openfiles_popup_menu(void)
661 GtkWidget *item;
663 openfiles_popup_menu = gtk_menu_new();
665 item = gtk_image_menu_item_new_from_stock("gtk-close", NULL);
666 gtk_widget_show(item);
667 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
668 g_signal_connect(item, "activate",
669 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
670 doc_items.close = item;
672 item = gtk_separator_menu_item_new();
673 gtk_widget_show(item);
674 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
676 item = gtk_image_menu_item_new_from_stock("gtk-save", NULL);
677 gtk_widget_show(item);
678 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
679 g_signal_connect(item, "activate",
680 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_SAVE));
681 doc_items.save = item;
683 item = gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
684 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
685 gtk_image_new_from_stock("gtk-revert-to-saved", GTK_ICON_SIZE_MENU));
686 gtk_widget_show(item);
687 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
688 g_signal_connect(item, "activate",
689 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD));
690 doc_items.reload = item;
692 item = gtk_separator_menu_item_new();
693 gtk_widget_show(item);
694 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
696 item = ui_image_menu_item_new(GTK_STOCK_FIND, _("_Find in Files"));
697 gtk_widget_show(item);
698 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
699 g_signal_connect(item, "activate", G_CALLBACK(on_find_in_files), NULL);
700 doc_items.find_in_files = item;
702 item = gtk_separator_menu_item_new();
703 gtk_widget_show(item);
704 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
706 doc_items.show_paths = gtk_check_menu_item_new_with_mnemonic(_("Show _Paths"));
707 gtk_widget_show(doc_items.show_paths);
708 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.show_paths);
709 g_signal_connect(doc_items.show_paths, "activate",
710 G_CALLBACK(on_openfiles_show_paths_activate), NULL);
712 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu));
716 static void unfold_parent(GtkTreeIter *iter)
718 GtkTreeIter parent;
719 GtkTreePath *path;
721 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter))
723 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), &parent);
724 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
725 gtk_tree_path_free(path);
730 /* compares the given data with the doc pointer from the selected row of openfiles
731 * treeview, in case of a match the row is selected and TRUE is returned
732 * (called indirectly from gtk_tree_model_foreach()) */
733 static gboolean tree_model_find_node(GtkTreeModel *model, GtkTreePath *path,
734 GtkTreeIter *iter, gpointer data)
736 GeanyDocument *doc;
738 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
740 if (doc == data)
742 /* unfolding also prevents a strange bug where the selection gets stuck on the parent
743 * when it is collapsed and then switching documents */
744 unfold_parent(iter);
745 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv.tree_openfiles), path, NULL, FALSE);
746 return TRUE;
748 else return FALSE;
752 void sidebar_select_openfiles_item(GeanyDocument *doc)
754 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles), tree_model_find_node, doc);
758 /* callbacks */
760 static void document_action(GeanyDocument *doc, gint action)
762 if (! DOC_VALID(doc))
763 return;
765 switch (action)
767 case OPENFILES_ACTION_REMOVE:
769 document_close(doc);
770 break;
772 case OPENFILES_ACTION_SAVE:
774 document_save_file(doc, FALSE);
775 break;
777 case OPENFILES_ACTION_RELOAD:
779 on_toolbutton_reload_clicked(NULL, NULL);
780 break;
786 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data)
788 GtkTreeIter iter;
789 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
790 GtkTreeModel *model;
791 GeanyDocument *doc;
792 gint action = GPOINTER_TO_INT(user_data);
794 if (gtk_tree_selection_get_selected(selection, &model, &iter))
796 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
797 if (doc)
799 document_action(doc, action);
801 else
803 /* parent item selected */
804 GtkTreeIter child;
805 gint i = gtk_tree_model_iter_n_children(model, &iter) - 1;
807 while (i >= 0 && gtk_tree_model_iter_nth_child(model, &child, &iter, i))
809 gtk_tree_model_get(model, &child, DOCUMENTS_DOCUMENT, &doc, -1);
811 document_action(doc, action);
812 i--;
819 static void change_focus_to_editor(GeanyDocument *doc, GtkWidget *source_widget)
821 if (may_steal_focus)
822 document_try_focus(doc, source_widget);
823 may_steal_focus = FALSE;
827 static gboolean on_openfiles_tree_selection_changed(gpointer data)
829 GtkTreeIter iter;
830 GtkTreeModel *model;
831 GeanyDocument *doc = NULL;
833 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
834 if (gtk_tree_selection_get_selected(selection_change.selection, &model, &iter) && ! ignore_callback)
836 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
837 if (! doc)
838 return FALSE; /* parent */
840 /* switch to the doc and grab the focus */
841 gtk_notebook_set_current_page(GTK_NOTEBOOK(main_widgets.notebook),
842 gtk_notebook_page_num(GTK_NOTEBOOK(main_widgets.notebook),
843 (GtkWidget*) doc->editor->sci));
844 if (selection_change.keyval != GDK_space)
845 change_focus_to_editor(doc, tv.tree_openfiles);
847 return FALSE;
851 static gboolean on_taglist_tree_selection_changed(gpointer data)
853 GtkTreeIter iter;
854 GtkTreeModel *model;
855 gint line = 0;
857 if (gtk_tree_selection_get_selected(selection_change.selection, &model, &iter))
859 const TMTag *tag;
861 gtk_tree_model_get(model, &iter, SYMBOLS_COLUMN_TAG, &tag, -1);
862 if (! tag)
863 return FALSE;
865 line = tag->atts.entry.line;
866 if (line > 0)
868 GeanyDocument *doc = document_get_current();
870 if (doc != NULL)
872 navqueue_goto_line(doc, doc, line);
873 if (selection_change.keyval != GDK_space)
874 change_focus_to_editor(doc, NULL);
878 return FALSE;
882 static void update_selection_change(GtkTreeSelection *selection, guint keyval)
884 selection_change.selection = selection;
885 selection_change.keyval = keyval;
889 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
890 gpointer user_data)
892 may_steal_focus = FALSE;
893 if (ui_is_keyval_enter_or_return(event->keyval) || event->keyval == GDK_space)
895 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
896 may_steal_focus = TRUE;
897 /* delay the query of selection state because this callback is executed before GTK
898 * changes the selection (g_signal_connect_after would be better but it doesn't work) */
899 update_selection_change(selection, event->keyval);
901 if (widget == tv.tree_openfiles) /* tag and doc list have separate handlers */
902 g_idle_add(on_openfiles_tree_selection_changed, NULL);
903 else
904 g_idle_add(on_taglist_tree_selection_changed, NULL);
906 return FALSE;
910 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
911 G_GNUC_UNUSED gpointer user_data)
913 GtkTreeSelection *selection;
915 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
916 may_steal_focus = TRUE;
918 if (event->type == GDK_2BUTTON_PRESS)
919 { /* double click on parent node(section) expands/collapses it */
920 GtkTreeModel *model;
921 GtkTreeIter iter;
923 if (gtk_tree_selection_get_selected(selection, &model, &iter))
925 if (gtk_tree_model_iter_has_child(model, &iter))
927 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
929 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
930 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget), path);
931 else
932 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget), path, FALSE);
934 gtk_tree_path_free(path);
935 return TRUE;
939 else if (event->button == 1)
940 { /* allow reclicking of taglist treeview item */
941 /* delay the query of selection state because this callback is executed before GTK
942 * changes the selection (g_signal_connect_after would be better but it doesn't work) */
943 update_selection_change(selection, 0);
945 if (widget == tv.tree_openfiles)
946 g_idle_add(on_openfiles_tree_selection_changed, NULL);
947 else
948 g_idle_add(on_taglist_tree_selection_changed, NULL);
950 else if (event->button == 3)
952 if (widget == tv.tree_openfiles)
954 if (!openfiles_popup_menu)
955 create_openfiles_popup_menu();
957 /* update menu item sensitivity */
958 documents_menu_update(selection);
959 gtk_menu_popup(GTK_MENU(openfiles_popup_menu), NULL, NULL, NULL, NULL,
960 event->button, event->time);
962 else
964 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
965 event->button, event->time);
968 return FALSE;
972 static void documents_menu_update(GtkTreeSelection *selection)
974 GtkTreeModel *model;
975 GtkTreeIter iter;
976 gboolean sel, path;
977 gchar *shortname = NULL;
978 GeanyDocument *doc = NULL;
980 /* maybe no selection e.g. if ctrl-click deselected */
981 sel = gtk_tree_selection_get_selected(selection, &model, &iter);
982 if (sel)
984 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc,
985 DOCUMENTS_SHORTNAME, &shortname, -1);
987 path = NZV(shortname) &&
988 (g_path_is_absolute(shortname) ||
989 (app->project && g_str_has_prefix(shortname, app->project->name)));
991 /* can close all, save all (except shortname), but only reload individually ATM */
992 gtk_widget_set_sensitive(doc_items.close, sel);
993 gtk_widget_set_sensitive(doc_items.save, (doc && doc->real_path) || path);
994 gtk_widget_set_sensitive(doc_items.reload, doc && doc->real_path);
995 gtk_widget_set_sensitive(doc_items.find_in_files, sel);
996 g_free(shortname);
998 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths),
999 documents_show_paths);
1003 static StashGroup *stash_group = NULL;
1005 static void on_load_settings(void)
1007 tag_window = ui_lookup_widget(main_widgets.window, "scrolledwindow2");
1009 prepare_openfiles();
1010 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
1011 stash_group_display(stash_group, NULL);
1012 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1016 static void on_save_settings(void)
1018 stash_group_update(stash_group, NULL);
1019 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1023 void sidebar_init(void)
1025 StashGroup *group;
1027 group = stash_group_new(PACKAGE);
1028 stash_group_add_boolean(group, &documents_show_paths, "documents_show_paths", TRUE);
1029 stash_group_add_widget_property(group, &ui_prefs.sidebar_page, "sidebar_page", GINT_TO_POINTER(0),
1030 main_widgets.sidebar_notebook, "page", 0);
1031 configuration_add_pref_group(group, FALSE);
1032 stash_group = group;
1034 /* delay building documents treeview until sidebar font has been read */
1035 g_signal_connect(geany_object, "load-settings", on_load_settings, NULL);
1036 g_signal_connect(geany_object, "save-settings", on_save_settings, NULL);
1039 if (gtk_check_version(2, 10, 0) == NULL)
1041 g_signal_connect(main_widgets.sidebar_notebook, "page-added",
1042 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1044 g_signal_connect(main_widgets.sidebar_notebook, "page-removed",
1045 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1047 /* tabs may have changed when sidebar is reshown */
1048 g_signal_connect(main_widgets.sidebar_notebook, "show",
1049 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1051 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1054 #define WIDGET(w) w && GTK_IS_WIDGET(w)
1056 void sidebar_finalize(void)
1058 if (WIDGET(tv.default_tag_tree))
1060 g_object_unref(tv.default_tag_tree);
1061 /* This is not exactly clean, default_tag_tree's ref_count is 2 when it is shown,
1062 * 1 oherwise. We should probably handle the ref_count more accurate. */
1063 if (WIDGET(tv.default_tag_tree))
1064 gtk_widget_destroy(tv.default_tag_tree);
1066 if (WIDGET(tv.popup_taglist))
1067 gtk_widget_destroy(tv.popup_taglist);
1068 if (WIDGET(openfiles_popup_menu))
1069 gtk_widget_destroy(openfiles_popup_menu);
1073 void sidebar_focus_openfiles_tab(void)
1075 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_openfiles_visible)
1077 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1079 gtk_notebook_set_current_page(notebook, TREEVIEW_OPENFILES);
1080 gtk_widget_grab_focus(tv.tree_openfiles);
1085 void sidebar_focus_symbols_tab(void)
1087 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_symbol_visible)
1089 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1090 GtkWidget *symbol_list_scrollwin = gtk_notebook_get_nth_page(notebook, TREEVIEW_SYMBOL);
1092 gtk_notebook_set_current_page(notebook, TREEVIEW_SYMBOL);
1093 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin)));
1098 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
1099 guint page_num, gpointer data)
1101 if (gtk_check_version(2, 10, 0) == NULL)
1103 gint tabs = gtk_notebook_get_n_pages(notebook);
1105 if (interface_prefs.sidebar_symbol_visible == FALSE)
1106 tabs--;
1107 if (interface_prefs.sidebar_openfiles_visible == FALSE)
1108 tabs--;
1110 gtk_notebook_set_show_tabs(notebook, (tabs > 1));