Make editor menu initially hidden
[geany-mirror.git] / src / sidebar.c
blobff7053ce90e5aad91bfca7830c0eb4c188f26156
1 /*
2 * sidebar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 * Sidebar related code for the Symbol list and Open files GtkTreeViews.
26 #include <string.h>
28 #include "geany.h"
29 #include "support.h"
30 #include "callbacks.h"
31 #include "sidebar.h"
32 #include "document.h"
33 #include "editor.h"
34 #include "documentprivate.h"
35 #include "filetypes.h"
36 #include "utils.h"
37 #include "ui_utils.h"
38 #include "symbols.h"
39 #include "navqueue.h"
40 #include "project.h"
41 #include "stash.h"
42 #include "keyfile.h"
43 #include "sciwrappers.h"
44 #include "search.h"
46 #include <gdk/gdkkeysyms.h>
49 SidebarTreeviews tv = {NULL, NULL, NULL};
50 /* while typeahead searching, editor should not get focus */
51 static gboolean may_steal_focus = FALSE;
53 static struct
55 GtkWidget *close;
56 GtkWidget *save;
57 GtkWidget *reload;
58 GtkWidget *show_paths;
59 GtkWidget *find_in_files;
60 GtkWidget *expand_all;
61 GtkWidget *collapse_all;
63 doc_items = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
65 enum
67 TREEVIEW_SYMBOL = 0,
68 TREEVIEW_OPENFILES
71 enum
73 OPENFILES_ACTION_REMOVE = 0,
74 OPENFILES_ACTION_SAVE,
75 OPENFILES_ACTION_RELOAD
78 /* documents tree model columns */
79 enum
81 DOCUMENTS_ICON,
82 DOCUMENTS_SHORTNAME, /* dirname for parents, basename for children */
83 DOCUMENTS_DOCUMENT,
84 DOCUMENTS_COLOR,
85 DOCUMENTS_FILENAME /* full filename */
88 static GtkTreeStore *store_openfiles;
89 static GtkWidget *openfiles_popup_menu;
90 static gboolean documents_show_paths;
91 static GtkWidget *tag_window; /* scrolled window that holds the symbol list GtkTreeView */
93 /* callback prototypes */
94 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data);
95 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
96 gpointer user_data);
97 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
98 gpointer user_data);
99 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data);
100 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data);
101 static void documents_menu_update(GtkTreeSelection *selection);
102 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
103 guint page_num, gpointer data);
106 /* the prepare_* functions are document-related, but I think they fit better here than in document.c */
107 static void prepare_taglist(GtkWidget *tree, GtkTreeStore *store)
109 GtkCellRenderer *text_renderer, *icon_renderer;
110 GtkTreeViewColumn *column;
111 GtkTreeSelection *selection;
113 text_renderer = gtk_cell_renderer_text_new();
114 icon_renderer = gtk_cell_renderer_pixbuf_new();
115 column = gtk_tree_view_column_new();
117 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
118 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", SYMBOLS_COLUMN_ICON, NULL);
119 g_object_set(icon_renderer, "xalign", 0.0, NULL);
121 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
122 gtk_tree_view_column_set_attributes(column, text_renderer, "text", SYMBOLS_COLUMN_NAME, NULL);
123 g_object_set(text_renderer, "yalign", 0.5, NULL);
124 gtk_tree_view_column_set_title(column, _("Symbols"));
126 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
127 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
129 ui_widget_modify_font_from_string(tree, interface_prefs.tagbar_font);
131 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
132 g_object_unref(store);
134 g_signal_connect(tree, "button-press-event",
135 G_CALLBACK(sidebar_button_press_cb), NULL);
136 g_signal_connect(tree, "key-press-event",
137 G_CALLBACK(sidebar_key_press_cb), NULL);
139 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(tree), interface_prefs.show_symbol_list_expanders);
140 if (! interface_prefs.show_symbol_list_expanders)
141 gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(tree), 10);
142 /* Tooltips */
143 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(tree), SYMBOLS_COLUMN_TOOLTIP);
145 /* selection handling */
146 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
147 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
148 /* callback for changed selection not necessary, will be handled by button-press-event */
152 static gboolean
153 on_default_tag_tree_button_press_event(GtkWidget *widget, GdkEventButton *event,
154 gpointer user_data)
156 if (event->button == 3)
158 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
159 event->button, event->time);
160 return TRUE;
162 return FALSE;
166 static void create_default_tag_tree(void)
168 GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(tag_window);
169 GtkWidget *label;
171 /* default_tag_tree is a GtkViewPort with a GtkLabel inside it */
172 tv.default_tag_tree = gtk_viewport_new(
173 gtk_scrolled_window_get_hadjustment(scrolled_window),
174 gtk_scrolled_window_get_vadjustment(scrolled_window));
175 label = gtk_label_new(_("No tags found"));
176 gtk_misc_set_alignment(GTK_MISC(label), 0.1f, 0.01f);
177 gtk_container_add(GTK_CONTAINER(tv.default_tag_tree), label);
178 gtk_widget_show_all(tv.default_tag_tree);
179 g_signal_connect(tv.default_tag_tree, "button-press-event",
180 G_CALLBACK(on_default_tag_tree_button_press_event), NULL);
181 g_object_ref((gpointer)tv.default_tag_tree); /* to hold it after removing */
185 /* update = rescan the tags for doc->filename */
186 void sidebar_update_tag_list(GeanyDocument *doc, gboolean update)
188 GtkWidget *child = gtk_bin_get_child(GTK_BIN(tag_window));
190 /* changes the tree view to the given one, trying not to do useless changes */
191 #define CHANGE_TREE(new_child) \
192 G_STMT_START { \
193 /* only change the tag tree if it's actually not the same (to avoid flickering) and if
194 * it's the one of the current document (to avoid problems when e.g. reloading
195 * configuration files */ \
196 if (child != new_child && doc == document_get_current()) \
198 if (child) \
199 gtk_container_remove(GTK_CONTAINER(tag_window), child); \
200 gtk_container_add(GTK_CONTAINER(tag_window), new_child); \
202 } G_STMT_END
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 CHANGE_TREE(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, TM_TYPE_TAG, 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 CHANGE_TREE(doc->priv->tag_tree);
233 else
235 CHANGE_TREE(tv.default_tag_tree);
238 #undef CHANGE_TREE
242 /* cleverly sorts documents by their short name */
243 static gint documents_sort_func(GtkTreeModel *model, GtkTreeIter *iter_a,
244 GtkTreeIter *iter_b, gpointer data)
246 gchar *key_a, *key_b;
247 gchar *name_a, *name_b;
248 gint cmp;
250 gtk_tree_model_get(model, iter_a, DOCUMENTS_SHORTNAME, &name_a, -1);
251 key_a = g_utf8_collate_key_for_filename(name_a, -1);
252 g_free(name_a);
253 gtk_tree_model_get(model, iter_b, DOCUMENTS_SHORTNAME, &name_b, -1);
254 key_b = g_utf8_collate_key_for_filename(name_b, -1);
255 g_free(name_b);
256 cmp = strcmp(key_a, key_b);
257 g_free(key_b);
258 g_free(key_a);
260 return cmp;
264 /* does some preparing things to the open files list widget */
265 static void prepare_openfiles(void)
267 GtkCellRenderer *icon_renderer;
268 GtkCellRenderer *text_renderer;
269 GtkTreeViewColumn *column;
270 GtkTreeSelection *selection;
271 GtkTreeSortable *sortable;
273 tv.tree_openfiles = ui_lookup_widget(main_widgets.window, "treeview6");
275 /* store the icon and the short filename to show, and the index as reference,
276 * the colour (black/red/green) and the full name for the tooltip */
277 store_openfiles = gtk_tree_store_new(5, GDK_TYPE_PIXBUF, G_TYPE_STRING,
278 G_TYPE_POINTER, GDK_TYPE_COLOR, G_TYPE_STRING);
279 gtk_tree_view_set_model(GTK_TREE_VIEW(tv.tree_openfiles), GTK_TREE_MODEL(store_openfiles));
281 /* set policy settings for the scolledwindow around the treeview again, because glade
282 * doesn't keep the settings */
283 gtk_scrolled_window_set_policy(
284 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets.window, "scrolledwindow7")),
285 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
287 icon_renderer = gtk_cell_renderer_pixbuf_new();
288 text_renderer = gtk_cell_renderer_text_new();
289 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
290 column = gtk_tree_view_column_new();
291 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
292 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", DOCUMENTS_ICON, NULL);
293 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
294 gtk_tree_view_column_set_attributes(column, text_renderer, "text", DOCUMENTS_SHORTNAME,
295 "foreground-gdk", DOCUMENTS_COLOR, NULL);
296 gtk_tree_view_append_column(GTK_TREE_VIEW(tv.tree_openfiles), column);
297 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv.tree_openfiles), FALSE);
299 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv.tree_openfiles),
300 DOCUMENTS_SHORTNAME);
302 /* sort opened filenames in the store_openfiles treeview */
303 sortable = GTK_TREE_SORTABLE(GTK_TREE_MODEL(store_openfiles));
304 gtk_tree_sortable_set_sort_func(sortable, DOCUMENTS_SHORTNAME, documents_sort_func, NULL, NULL);
305 gtk_tree_sortable_set_sort_column_id(sortable, DOCUMENTS_SHORTNAME, GTK_SORT_ASCENDING);
307 ui_widget_modify_font_from_string(tv.tree_openfiles, interface_prefs.tagbar_font);
309 /* tooltips */
310 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(tv.tree_openfiles), DOCUMENTS_FILENAME);
312 /* selection handling */
313 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
314 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
315 g_object_unref(store_openfiles);
317 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "button-press-event",
318 G_CALLBACK(sidebar_button_press_cb), NULL);
319 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "key-press-event",
320 G_CALLBACK(sidebar_key_press_cb), NULL);
324 /* iter should be toplevel */
325 static gboolean find_tree_iter_dir(GtkTreeIter *iter, const gchar *dir)
327 GeanyDocument *doc;
328 gchar *name;
329 gboolean result;
331 if (utils_str_equal(dir, "."))
332 dir = GEANY_STRING_UNTITLED;
334 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
335 g_return_val_if_fail(!doc, FALSE);
337 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_SHORTNAME, &name, -1);
339 result = utils_filenamecmp(name, dir) == 0;
340 g_free(name);
342 return result;
346 static gboolean utils_filename_has_prefix(const gchar *str, const gchar *prefix)
348 gchar *head = g_strndup(str, strlen(prefix));
349 gboolean ret = utils_filenamecmp(head, prefix) == 0;
351 g_free(head);
352 return ret;
356 static gchar *get_doc_folder(const gchar *path)
358 gchar *tmp_dirname = g_strdup(path);
359 gchar *project_base_path;
360 gchar *dirname = NULL;
361 const gchar *home_dir = g_get_home_dir();
362 const gchar *rest;
364 /* replace the project base path with the project name */
365 project_base_path = project_get_base_path();
367 if (project_base_path != NULL)
369 gsize len = strlen(project_base_path);
371 if (project_base_path[len-1] == G_DIR_SEPARATOR)
372 project_base_path[len-1] = '\0';
374 /* check whether the dir name matches or uses the project base path */
375 if (utils_filename_has_prefix(tmp_dirname, project_base_path))
377 rest = tmp_dirname + len;
378 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
380 dirname = g_strdup_printf("%s%s", app->project->name, rest);
383 g_free(project_base_path);
385 if (dirname == NULL)
387 dirname = tmp_dirname;
389 /* If matches home dir, replace with tilde */
390 if (NZV(home_dir) && utils_filename_has_prefix(dirname, home_dir))
392 rest = dirname + strlen(home_dir);
393 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
395 dirname = g_strdup_printf("~%s", rest);
396 g_free(tmp_dirname);
400 else
401 g_free(tmp_dirname);
403 return dirname;
407 static GtkTreeIter *get_doc_parent(GeanyDocument *doc)
409 gchar *path;
410 gchar *dirname = NULL;
411 static GtkTreeIter parent;
412 GtkTreeModel *model = GTK_TREE_MODEL(store_openfiles);
413 static GdkPixbuf *dir_icon = NULL;
415 if (!documents_show_paths)
416 return NULL;
418 path = g_path_get_dirname(DOC_FILENAME(doc));
419 dirname = get_doc_folder(path);
421 if (gtk_tree_model_get_iter_first(model, &parent))
425 if (find_tree_iter_dir(&parent, dirname))
427 g_free(dirname);
428 g_free(path);
429 return &parent;
432 while (gtk_tree_model_iter_next(model, &parent));
434 /* no match, add dir parent */
435 if (!dir_icon)
436 dir_icon = ui_get_mime_icon("inode/directory", GTK_ICON_SIZE_MENU);
438 gtk_tree_store_append(store_openfiles, &parent, NULL);
439 gtk_tree_store_set(store_openfiles, &parent, DOCUMENTS_ICON, dir_icon,
440 DOCUMENTS_FILENAME, path,
441 DOCUMENTS_SHORTNAME, doc->file_name ? dirname : GEANY_STRING_UNTITLED, -1);
443 g_free(dirname);
444 g_free(path);
445 return &parent;
449 /* Also sets doc->priv->iter.
450 * This is called recursively in sidebar_openfiles_update_all(). */
451 void sidebar_openfiles_add(GeanyDocument *doc)
453 GtkTreeIter *iter = &doc->priv->iter;
454 GtkTreeIter *parent = get_doc_parent(doc);
455 gchar *basename;
456 const GdkColor *color = document_get_status_color(doc);
457 static GdkPixbuf *file_icon = NULL;
459 gtk_tree_store_append(store_openfiles, iter, parent);
461 /* check if new parent */
462 if (parent && gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), parent) == 1)
464 GtkTreePath *path;
466 /* expand parent */
467 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), parent);
468 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
469 gtk_tree_path_free(path);
471 if (!file_icon)
472 file_icon = ui_get_mime_icon("text/plain", GTK_ICON_SIZE_MENU);
474 basename = g_path_get_basename(DOC_FILENAME(doc));
475 gtk_tree_store_set(store_openfiles, iter,
476 DOCUMENTS_ICON, (doc->file_type && doc->file_type->icon) ? doc->file_type->icon : file_icon,
477 DOCUMENTS_SHORTNAME, basename, DOCUMENTS_DOCUMENT, doc, DOCUMENTS_COLOR, color,
478 DOCUMENTS_FILENAME, DOC_FILENAME(doc), -1);
479 g_free(basename);
483 static void openfiles_remove(GeanyDocument *doc)
485 GtkTreeIter *iter = &doc->priv->iter;
486 GtkTreeIter parent;
488 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter) &&
489 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), &parent) == 1)
490 gtk_tree_store_remove(store_openfiles, &parent);
491 else
492 gtk_tree_store_remove(store_openfiles, iter);
496 void sidebar_openfiles_update(GeanyDocument *doc)
498 GtkTreeIter *iter = &doc->priv->iter;
499 gchar *fname;
501 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_FILENAME, &fname, -1);
503 if (utils_str_equal(fname, DOC_FILENAME(doc)))
505 /* just update color and the icon */
506 const GdkColor *color = document_get_status_color(doc);
507 GdkPixbuf *icon = doc->file_type->icon;
509 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_COLOR, color, -1);
510 if (icon)
511 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_ICON, icon, -1);
513 else
515 /* path has changed, so remove and re-add */
516 GtkTreeSelection *treesel;
517 gboolean sel;
519 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
520 sel = gtk_tree_selection_iter_is_selected(treesel, &doc->priv->iter);
521 openfiles_remove(doc);
523 sidebar_openfiles_add(doc);
524 if (sel)
525 gtk_tree_selection_select_iter(treesel, &doc->priv->iter);
527 g_free(fname);
531 void sidebar_openfiles_update_all()
533 guint i;
535 gtk_tree_store_clear(store_openfiles);
536 foreach_document (i)
538 sidebar_openfiles_add(documents[i]);
543 void sidebar_remove_document(GeanyDocument *doc)
545 openfiles_remove(doc);
547 if (GTK_IS_WIDGET(doc->priv->tag_tree))
549 gtk_widget_destroy(doc->priv->tag_tree); /* make GTK release its references, if any */
550 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
551 g_object_unref(doc->priv->tag_tree);
552 doc->priv->tag_tree = NULL;
557 static void on_hide_sidebar(void)
559 ui_prefs.sidebar_visible = FALSE;
560 ui_sidebar_show_hide();
564 static gboolean on_sidebar_display_symbol_list_show(GtkWidget *item)
566 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
567 interface_prefs.sidebar_symbol_visible);
568 return FALSE;
572 static gboolean on_sidebar_display_open_files_show(GtkWidget *item)
574 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
575 interface_prefs.sidebar_openfiles_visible);
576 return FALSE;
580 void sidebar_add_common_menu_items(GtkMenu *menu)
582 GtkWidget *item;
584 item = gtk_separator_menu_item_new();
585 gtk_widget_show(item);
586 gtk_container_add(GTK_CONTAINER(menu), item);
588 item = gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
589 gtk_container_add(GTK_CONTAINER(menu), item);
590 g_signal_connect(item, "expose-event",
591 G_CALLBACK(on_sidebar_display_symbol_list_show), NULL);
592 gtk_widget_show(item);
593 g_signal_connect(item, "activate",
594 G_CALLBACK(on_list_symbol_activate), NULL);
596 item = gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
597 gtk_container_add(GTK_CONTAINER(menu), item);
598 g_signal_connect(item, "expose-event",
599 G_CALLBACK(on_sidebar_display_open_files_show), NULL);
600 gtk_widget_show(item);
601 g_signal_connect(item, "activate",
602 G_CALLBACK(on_list_document_activate), NULL);
604 item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
605 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
606 gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
607 gtk_widget_show(item);
608 gtk_container_add(GTK_CONTAINER(menu), item);
609 g_signal_connect(item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
613 static void on_openfiles_show_paths_activate(GtkCheckMenuItem *item, gpointer user_data)
615 documents_show_paths = gtk_check_menu_item_get_active(item);
616 sidebar_openfiles_update_all();
620 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data)
622 interface_prefs.sidebar_openfiles_visible = gtk_check_menu_item_get_active(item);
623 ui_sidebar_show_hide();
624 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
628 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data)
630 interface_prefs.sidebar_symbol_visible = gtk_check_menu_item_get_active(item);
631 ui_sidebar_show_hide();
632 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
636 static void on_find_in_files(GtkMenuItem *menuitem, gpointer user_data)
638 GtkTreeSelection *treesel;
639 GtkTreeIter iter;
640 GtkTreeModel *model;
641 GeanyDocument *doc;
642 gchar *dir;
644 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
645 if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
646 return;
647 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
649 if (!doc)
651 gtk_tree_model_get(model, &iter, DOCUMENTS_FILENAME, &dir, -1);
653 else
654 dir = g_path_get_dirname(DOC_FILENAME(doc));
656 search_show_find_in_files_dialog(dir);
657 g_free(dir);
661 static void on_openfiles_expand_collapse(GtkMenuItem *menuitem, gpointer user_data)
663 gboolean expand = GPOINTER_TO_INT(user_data);
665 if (expand)
666 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv.tree_openfiles));
667 else
668 gtk_tree_view_collapse_all(GTK_TREE_VIEW(tv.tree_openfiles));
672 static void create_openfiles_popup_menu(void)
674 GtkWidget *item;
676 openfiles_popup_menu = gtk_menu_new();
678 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL);
679 gtk_widget_show(item);
680 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
681 g_signal_connect(item, "activate",
682 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
683 doc_items.close = item;
685 item = gtk_separator_menu_item_new();
686 gtk_widget_show(item);
687 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
689 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, NULL);
690 gtk_widget_show(item);
691 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
692 g_signal_connect(item, "activate",
693 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_SAVE));
694 doc_items.save = item;
696 item = gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
697 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
698 gtk_image_new_from_stock(GTK_STOCK_REVERT_TO_SAVED, GTK_ICON_SIZE_MENU));
699 gtk_widget_show(item);
700 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
701 g_signal_connect(item, "activate",
702 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD));
703 doc_items.reload = item;
705 item = gtk_separator_menu_item_new();
706 gtk_widget_show(item);
707 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
709 item = ui_image_menu_item_new(GTK_STOCK_FIND, _("_Find in Files"));
710 gtk_widget_show(item);
711 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
712 g_signal_connect(item, "activate", G_CALLBACK(on_find_in_files), NULL);
713 doc_items.find_in_files = item;
715 item = gtk_separator_menu_item_new();
716 gtk_widget_show(item);
717 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
719 doc_items.show_paths = gtk_check_menu_item_new_with_mnemonic(_("Show _Paths"));
720 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths), documents_show_paths);
721 gtk_widget_show(doc_items.show_paths);
722 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.show_paths);
723 g_signal_connect(doc_items.show_paths, "activate",
724 G_CALLBACK(on_openfiles_show_paths_activate), NULL);
726 item = gtk_separator_menu_item_new();
727 gtk_widget_show(item);
728 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
730 doc_items.expand_all = ui_image_menu_item_new(GTK_STOCK_ADD, _("_Expand All"));
731 gtk_widget_show(doc_items.expand_all);
732 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.expand_all);
733 g_signal_connect(doc_items.expand_all, "activate",
734 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(TRUE));
736 doc_items.collapse_all = ui_image_menu_item_new(GTK_STOCK_REMOVE, _("_Collapse All"));
737 gtk_widget_show(doc_items.collapse_all);
738 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.collapse_all);
739 g_signal_connect(doc_items.collapse_all, "activate",
740 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(FALSE));
742 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu));
746 static void unfold_parent(GtkTreeIter *iter)
748 GtkTreeIter parent;
749 GtkTreePath *path;
751 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter))
753 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), &parent);
754 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
755 gtk_tree_path_free(path);
760 /* compares the given data with the doc pointer from the selected row of openfiles
761 * treeview, in case of a match the row is selected and TRUE is returned
762 * (called indirectly from gtk_tree_model_foreach()) */
763 static gboolean tree_model_find_node(GtkTreeModel *model, GtkTreePath *path,
764 GtkTreeIter *iter, gpointer data)
766 GeanyDocument *doc;
768 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
770 if (doc == data)
772 /* unfolding also prevents a strange bug where the selection gets stuck on the parent
773 * when it is collapsed and then switching documents */
774 unfold_parent(iter);
775 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv.tree_openfiles), path, NULL, FALSE);
776 return TRUE;
778 else return FALSE;
782 void sidebar_select_openfiles_item(GeanyDocument *doc)
784 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles), tree_model_find_node, doc);
788 /* callbacks */
790 static void document_action(GeanyDocument *doc, gint action)
792 if (! DOC_VALID(doc))
793 return;
795 switch (action)
797 case OPENFILES_ACTION_REMOVE:
799 document_close(doc);
800 break;
802 case OPENFILES_ACTION_SAVE:
804 document_save_file(doc, FALSE);
805 break;
807 case OPENFILES_ACTION_RELOAD:
809 on_toolbutton_reload_clicked(NULL, NULL);
810 break;
816 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data)
818 GtkTreeIter iter;
819 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
820 GtkTreeModel *model;
821 GeanyDocument *doc;
822 gint action = GPOINTER_TO_INT(user_data);
824 if (gtk_tree_selection_get_selected(selection, &model, &iter))
826 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
827 if (doc)
829 document_action(doc, action);
831 else
833 /* parent item selected */
834 GtkTreeIter child;
835 gint i = gtk_tree_model_iter_n_children(model, &iter) - 1;
837 while (i >= 0 && gtk_tree_model_iter_nth_child(model, &child, &iter, i))
839 gtk_tree_model_get(model, &child, DOCUMENTS_DOCUMENT, &doc, -1);
841 document_action(doc, action);
842 i--;
849 static void change_focus_to_editor(GeanyDocument *doc, GtkWidget *source_widget)
851 if (may_steal_focus)
852 document_try_focus(doc, source_widget);
853 may_steal_focus = FALSE;
857 static gboolean openfiles_go_to_selection(GtkTreeSelection *selection, guint keyval)
859 GtkTreeIter iter;
860 GtkTreeModel *model;
861 GeanyDocument *doc = NULL;
863 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
864 if (gtk_tree_selection_get_selected(selection, &model, &iter) && ! ignore_callback)
866 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
867 if (! doc)
868 return FALSE; /* parent */
870 /* switch to the doc and grab the focus */
871 document_show_tab(doc);
872 if (keyval != GDK_space)
873 change_focus_to_editor(doc, tv.tree_openfiles);
875 return FALSE;
879 static gboolean taglist_go_to_selection(GtkTreeSelection *selection, guint keyval)
881 GtkTreeIter iter;
882 GtkTreeModel *model;
883 gint line = 0;
885 if (gtk_tree_selection_get_selected(selection, &model, &iter))
887 TMTag *tag;
889 gtk_tree_model_get(model, &iter, SYMBOLS_COLUMN_TAG, &tag, -1);
890 if (! tag)
891 return FALSE;
893 line = tag->atts.entry.line;
894 if (line > 0)
896 GeanyDocument *doc = document_get_current();
898 if (doc != NULL)
900 navqueue_goto_line(doc, doc, line);
901 if (keyval != GDK_space)
902 change_focus_to_editor(doc, NULL);
905 tm_tag_unref(tag);
907 return FALSE;
911 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
912 gpointer user_data)
914 may_steal_focus = FALSE;
915 if (ui_is_keyval_enter_or_return(event->keyval) || event->keyval == GDK_space)
917 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
918 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
919 may_steal_focus = TRUE;
921 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
922 * doing so will prevent further handlers to be run in most cases, but the only one is our
923 * own, so guess it's fine. */
924 if (widget_class->key_press_event)
925 widget_class->key_press_event(widget, event);
927 if (widget == tv.tree_openfiles) /* tag and doc list have separate handlers */
928 openfiles_go_to_selection(selection, event->keyval);
929 else
930 taglist_go_to_selection(selection, event->keyval);
932 return TRUE;
934 return FALSE;
938 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
939 G_GNUC_UNUSED gpointer user_data)
941 GtkTreeSelection *selection;
942 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
943 gboolean handled = FALSE;
945 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
946 * doing so will prevent further handlers to be run in most cases, but the only one is our own,
947 * so guess it's fine. */
948 if (widget_class->button_press_event)
949 handled = widget_class->button_press_event(widget, event);
951 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
952 may_steal_focus = TRUE;
954 if (event->type == GDK_2BUTTON_PRESS)
955 { /* double click on parent node(section) expands/collapses it */
956 GtkTreeModel *model;
957 GtkTreeIter iter;
959 if (gtk_tree_selection_get_selected(selection, &model, &iter))
961 if (gtk_tree_model_iter_has_child(model, &iter))
963 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
965 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
966 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget), path);
967 else
968 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget), path, FALSE);
970 gtk_tree_path_free(path);
971 return TRUE;
975 else if (event->button == 1)
976 { /* allow reclicking of taglist treeview item */
977 if (widget == tv.tree_openfiles)
978 openfiles_go_to_selection(selection, 0);
979 else
980 taglist_go_to_selection(selection, 0);
981 handled = TRUE;
983 else if (event->button == 3)
985 if (widget == tv.tree_openfiles)
987 if (!openfiles_popup_menu)
988 create_openfiles_popup_menu();
990 /* update menu item sensitivity */
991 documents_menu_update(selection);
992 gtk_menu_popup(GTK_MENU(openfiles_popup_menu), NULL, NULL, NULL, NULL,
993 event->button, event->time);
995 else
997 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
998 event->button, event->time);
1000 handled = TRUE;
1002 return handled;
1006 static void documents_menu_update(GtkTreeSelection *selection)
1008 GtkTreeModel *model;
1009 GtkTreeIter iter;
1010 gboolean sel, path;
1011 gchar *shortname = NULL;
1012 GeanyDocument *doc = NULL;
1014 /* maybe no selection e.g. if ctrl-click deselected */
1015 sel = gtk_tree_selection_get_selected(selection, &model, &iter);
1016 if (sel)
1018 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc,
1019 DOCUMENTS_SHORTNAME, &shortname, -1);
1021 path = NZV(shortname) &&
1022 (g_path_is_absolute(shortname) ||
1023 (app->project && g_str_has_prefix(shortname, app->project->name)));
1025 /* can close all, save all (except shortname), but only reload individually ATM */
1026 gtk_widget_set_sensitive(doc_items.close, sel);
1027 gtk_widget_set_sensitive(doc_items.save, (doc && doc->real_path) || path);
1028 gtk_widget_set_sensitive(doc_items.reload, doc && doc->real_path);
1029 gtk_widget_set_sensitive(doc_items.find_in_files, sel);
1030 g_free(shortname);
1032 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths), documents_show_paths);
1033 gtk_widget_set_sensitive(doc_items.expand_all, documents_show_paths);
1034 gtk_widget_set_sensitive(doc_items.collapse_all, documents_show_paths);
1038 static StashGroup *stash_group = NULL;
1040 static void on_load_settings(void)
1042 tag_window = ui_lookup_widget(main_widgets.window, "scrolledwindow2");
1044 prepare_openfiles();
1045 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
1046 stash_group_display(stash_group, NULL);
1047 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1051 static void on_save_settings(void)
1053 stash_group_update(stash_group, NULL);
1054 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1058 void sidebar_init(void)
1060 StashGroup *group;
1062 group = stash_group_new(PACKAGE);
1063 stash_group_add_boolean(group, &documents_show_paths, "documents_show_paths", TRUE);
1064 stash_group_add_widget_property(group, &ui_prefs.sidebar_page, "sidebar_page", GINT_TO_POINTER(0),
1065 main_widgets.sidebar_notebook, "page", 0);
1066 configuration_add_pref_group(group, FALSE);
1067 stash_group = group;
1069 /* delay building documents treeview until sidebar font has been read */
1070 g_signal_connect(geany_object, "load-settings", on_load_settings, NULL);
1071 g_signal_connect(geany_object, "save-settings", on_save_settings, NULL);
1073 g_signal_connect(main_widgets.sidebar_notebook, "page-added",
1074 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1075 g_signal_connect(main_widgets.sidebar_notebook, "page-removed",
1076 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1077 /* tabs may have changed when sidebar is reshown */
1078 g_signal_connect(main_widgets.sidebar_notebook, "show",
1079 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1081 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1084 #define WIDGET(w) w && GTK_IS_WIDGET(w)
1086 void sidebar_finalize(void)
1088 if (WIDGET(tv.default_tag_tree))
1090 gtk_widget_destroy(tv.default_tag_tree); /* make GTK release its references, if any... */
1091 g_object_unref(tv.default_tag_tree); /* ...and release our own */
1093 if (WIDGET(tv.popup_taglist))
1094 gtk_widget_destroy(tv.popup_taglist);
1095 if (WIDGET(openfiles_popup_menu))
1096 gtk_widget_destroy(openfiles_popup_menu);
1100 void sidebar_focus_openfiles_tab(void)
1102 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_openfiles_visible)
1104 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1106 gtk_notebook_set_current_page(notebook, TREEVIEW_OPENFILES);
1107 gtk_widget_grab_focus(tv.tree_openfiles);
1112 void sidebar_focus_symbols_tab(void)
1114 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_symbol_visible)
1116 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1117 GtkWidget *symbol_list_scrollwin = gtk_notebook_get_nth_page(notebook, TREEVIEW_SYMBOL);
1119 gtk_notebook_set_current_page(notebook, TREEVIEW_SYMBOL);
1120 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin)));
1125 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
1126 guint page_num, gpointer data)
1128 gint tabs = gtk_notebook_get_n_pages(notebook);
1130 if (interface_prefs.sidebar_symbol_visible == FALSE)
1131 tabs--;
1132 if (interface_prefs.sidebar_openfiles_visible == FALSE)
1133 tabs--;
1135 gtk_notebook_set_show_tabs(notebook, (tabs > 1));