Bump API version for the Scintilla 5.1.5 change
[geany-mirror.git] / src / sidebar.c
blob7f1b27d114dbc392141d0049020b2b8e0822f289
1 /*
2 * sidebar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
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.
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.
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.
22 * Sidebar related code for the Symbol list and Open files GtkTreeViews.
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include "sidebar.h"
31 #include "app.h"
32 #include "callbacks.h" /* FIXME: for ignore_callback */
33 #include "documentprivate.h"
34 #include "filetypesprivate.h"
35 #include "geanyobject.h"
36 #include "keyfile.h"
37 #include "navqueue.h"
38 #include "stash.h"
39 #include "support.h"
40 #include "symbols.h"
41 #include "ui_utils.h"
42 #include "utils.h"
43 #include "keybindings.h"
45 #include <string.h>
47 #include <gdk/gdkkeysyms.h>
50 SidebarTreeviews tv = {NULL, NULL, NULL};
51 /* while typeahead searching, editor should not get focus */
52 static gboolean may_steal_focus = FALSE;
54 static struct
56 GtkWidget *close;
57 GtkWidget *save;
58 GtkWidget *reload;
59 GtkWidget *show_paths;
60 GtkWidget *find_in_files;
61 GtkWidget *expand_all;
62 GtkWidget *collapse_all;
64 doc_items = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
66 enum
68 TREEVIEW_SYMBOL = 0,
69 TREEVIEW_OPENFILES
72 enum
74 OPENFILES_ACTION_REMOVE = 0,
75 OPENFILES_ACTION_SAVE,
76 OPENFILES_ACTION_RELOAD
79 /* documents tree model columns */
80 enum
82 DOCUMENTS_ICON,
83 DOCUMENTS_SHORTNAME, /* dirname for parents, basename for children */
84 DOCUMENTS_DOCUMENT,
85 DOCUMENTS_COLOR,
86 DOCUMENTS_FILENAME /* full filename */
89 static GtkTreeStore *store_openfiles;
90 static GtkWidget *openfiles_popup_menu;
91 static gboolean documents_show_paths;
92 static GtkWidget *tag_window; /* scrolled window that holds the symbol list GtkTreeView */
94 /* callback prototypes */
95 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data);
96 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
97 gpointer user_data);
98 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
99 gpointer user_data);
100 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data);
101 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data);
102 static void documents_menu_update(GtkTreeSelection *selection);
103 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
104 guint page_num, gpointer data);
107 /* the prepare_* functions are document-related, but I think they fit better here than in document.c */
108 static void prepare_taglist(GtkWidget *tree, GtkTreeStore *store)
110 GtkCellRenderer *text_renderer, *icon_renderer;
111 GtkTreeViewColumn *column;
112 GtkTreeSelection *selection;
114 text_renderer = gtk_cell_renderer_text_new();
115 icon_renderer = gtk_cell_renderer_pixbuf_new();
116 column = gtk_tree_view_column_new();
118 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
119 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", SYMBOLS_COLUMN_ICON, NULL);
120 g_object_set(icon_renderer, "xalign", 0.0, NULL);
122 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
123 gtk_tree_view_column_set_attributes(column, text_renderer, "text", SYMBOLS_COLUMN_NAME, NULL);
124 g_object_set(text_renderer, "yalign", 0.5, NULL);
125 gtk_tree_view_column_set_title(column, _("Symbols"));
127 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
128 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
130 ui_widget_modify_font_from_string(tree, interface_prefs.tagbar_font);
132 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
133 g_object_unref(store);
135 g_signal_connect(tree, "button-press-event",
136 G_CALLBACK(sidebar_button_press_cb), NULL);
137 g_signal_connect(tree, "key-press-event",
138 G_CALLBACK(sidebar_key_press_cb), NULL);
140 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(tree), interface_prefs.show_symbol_list_expanders);
141 if (! interface_prefs.show_symbol_list_expanders)
142 gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(tree), 10);
143 /* Tooltips */
144 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tree), SYMBOLS_COLUMN_TOOLTIP);
146 /* selection handling */
147 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
148 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
149 /* callback for changed selection not necessary, will be handled by button-press-event */
153 static gboolean
154 on_default_tag_tree_button_press_event(GtkWidget *widget, GdkEventButton *event,
155 gpointer user_data)
157 if (event->button == 3)
159 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
160 event->button, event->time);
161 return TRUE;
163 return FALSE;
167 static void create_default_tag_tree(void)
169 GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(tag_window);
170 GtkWidget *label;
172 /* default_tag_tree is a GtkViewPort with a GtkLabel inside it */
173 tv.default_tag_tree = gtk_viewport_new(
174 gtk_scrolled_window_get_hadjustment(scrolled_window),
175 gtk_scrolled_window_get_vadjustment(scrolled_window));
176 gtk_viewport_set_shadow_type(GTK_VIEWPORT(tv.default_tag_tree), GTK_SHADOW_NONE);
177 label = gtk_label_new(_("No symbols found"));
178 gtk_misc_set_alignment(GTK_MISC(label), 0.1f, 0.01f);
179 gtk_container_add(GTK_CONTAINER(tv.default_tag_tree), label);
180 gtk_widget_show_all(tv.default_tag_tree);
181 g_signal_connect(tv.default_tag_tree, "button-press-event",
182 G_CALLBACK(on_default_tag_tree_button_press_event), NULL);
183 g_object_ref((gpointer)tv.default_tag_tree); /* to hold it after removing */
187 /* update = rescan the tags for doc->filename */
188 void sidebar_update_tag_list(GeanyDocument *doc, gboolean update)
190 GtkWidget *child = gtk_bin_get_child(GTK_BIN(tag_window));
192 g_return_if_fail(doc == NULL || doc->is_valid);
194 if (update && doc != NULL)
195 doc->priv->tag_tree_dirty = TRUE;
197 if (gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets.sidebar_notebook)) != TREEVIEW_SYMBOL)
198 return; /* don't bother updating symbol tree if we don't see it */
200 /* changes the tree view to the given one, trying not to do useless changes */
201 #define CHANGE_TREE(new_child) \
202 G_STMT_START { \
203 /* only change the tag tree if it's actually not the same (to avoid flickering) and if
204 * it's the one of the current document (to avoid problems when e.g. reloading
205 * configuration files */ \
206 if (child != new_child && doc == document_get_current()) \
208 if (child) \
209 gtk_container_remove(GTK_CONTAINER(tag_window), child); \
210 gtk_container_add(GTK_CONTAINER(tag_window), new_child); \
212 } G_STMT_END
214 if (tv.default_tag_tree == NULL)
215 create_default_tag_tree();
217 /* show default empty tag tree if there are no tags */
218 if (doc == NULL || doc->file_type == NULL || ! filetype_has_tags(doc->file_type))
220 CHANGE_TREE(tv.default_tag_tree);
221 return;
224 if (doc->priv->tag_tree_dirty)
225 { /* updating the tag list in the left tag window */
226 if (doc->priv->tag_tree == NULL)
228 doc->priv->tag_store = gtk_tree_store_new(
229 SYMBOLS_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, TM_TYPE_TAG, G_TYPE_STRING);
230 doc->priv->tag_tree = gtk_tree_view_new();
231 prepare_taglist(doc->priv->tag_tree, doc->priv->tag_store);
232 gtk_widget_show(doc->priv->tag_tree);
233 g_object_ref((gpointer)doc->priv->tag_tree); /* to hold it after removing */
236 doc->has_tags = symbols_recreate_tag_list(doc, SYMBOLS_SORT_USE_PREVIOUS);
237 doc->priv->tag_tree_dirty = FALSE;
240 if (doc->has_tags)
242 CHANGE_TREE(doc->priv->tag_tree);
244 else
246 CHANGE_TREE(tv.default_tag_tree);
249 #undef CHANGE_TREE
253 /* cleverly sorts documents by their short name */
254 static gint documents_sort_func(GtkTreeModel *model, GtkTreeIter *iter_a,
255 GtkTreeIter *iter_b, gpointer data)
257 gchar *key_a, *key_b;
258 gchar *name_a, *name_b;
259 gint cmp;
261 gtk_tree_model_get(model, iter_a, DOCUMENTS_SHORTNAME, &name_a, -1);
262 key_a = g_utf8_collate_key_for_filename(name_a, -1);
263 g_free(name_a);
264 gtk_tree_model_get(model, iter_b, DOCUMENTS_SHORTNAME, &name_b, -1);
265 key_b = g_utf8_collate_key_for_filename(name_b, -1);
266 g_free(name_b);
267 cmp = strcmp(key_a, key_b);
268 g_free(key_b);
269 g_free(key_a);
271 return cmp;
275 /* does some preparing things to the open files list widget */
276 static void prepare_openfiles(void)
278 GtkCellRenderer *icon_renderer;
279 GtkCellRenderer *text_renderer;
280 GtkTreeViewColumn *column;
281 GtkTreeSelection *selection;
282 GtkTreeSortable *sortable;
284 tv.tree_openfiles = ui_lookup_widget(main_widgets.window, "treeview6");
286 /* store the icon and the short filename to show, and the index as reference,
287 * the colour (black/red/green) and the full name for the tooltip */
288 store_openfiles = gtk_tree_store_new(5, G_TYPE_ICON, G_TYPE_STRING,
289 G_TYPE_POINTER, GDK_TYPE_COLOR, G_TYPE_STRING);
290 gtk_tree_view_set_model(GTK_TREE_VIEW(tv.tree_openfiles), GTK_TREE_MODEL(store_openfiles));
292 /* set policy settings for the scolledwindow around the treeview again, because glade
293 * doesn't keep the settings */
294 gtk_scrolled_window_set_policy(
295 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets.window, "scrolledwindow7")),
296 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
298 icon_renderer = gtk_cell_renderer_pixbuf_new();
299 g_object_set(icon_renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
300 text_renderer = gtk_cell_renderer_text_new();
301 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
302 column = gtk_tree_view_column_new();
303 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
304 gtk_tree_view_column_set_attributes(column, icon_renderer, "gicon", DOCUMENTS_ICON, NULL);
305 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
306 gtk_tree_view_column_set_attributes(column, text_renderer, "text", DOCUMENTS_SHORTNAME,
307 "foreground-gdk", DOCUMENTS_COLOR, NULL);
308 gtk_tree_view_append_column(GTK_TREE_VIEW(tv.tree_openfiles), column);
309 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv.tree_openfiles), FALSE);
311 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv.tree_openfiles),
312 DOCUMENTS_SHORTNAME);
314 /* sort opened filenames in the store_openfiles treeview */
315 sortable = GTK_TREE_SORTABLE(GTK_TREE_MODEL(store_openfiles));
316 gtk_tree_sortable_set_sort_func(sortable, DOCUMENTS_SHORTNAME, documents_sort_func, NULL, NULL);
317 gtk_tree_sortable_set_sort_column_id(sortable, DOCUMENTS_SHORTNAME, GTK_SORT_ASCENDING);
319 ui_widget_modify_font_from_string(tv.tree_openfiles, interface_prefs.tagbar_font);
321 /* tooltips */
322 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tv.tree_openfiles), DOCUMENTS_FILENAME);
324 /* selection handling */
325 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
326 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
327 g_object_unref(store_openfiles);
329 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "button-press-event",
330 G_CALLBACK(sidebar_button_press_cb), NULL);
331 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "key-press-event",
332 G_CALLBACK(sidebar_key_press_cb), NULL);
336 /* iter should be toplevel */
337 static gboolean find_tree_iter_dir(GtkTreeIter *iter, const gchar *dir)
339 GeanyDocument *doc;
340 gchar *name;
341 gboolean result;
343 if (utils_str_equal(dir, "."))
344 dir = GEANY_STRING_UNTITLED;
346 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
347 g_return_val_if_fail(!doc, FALSE);
349 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_SHORTNAME, &name, -1);
351 result = utils_filenamecmp(name, dir) == 0;
352 g_free(name);
354 return result;
358 static gboolean utils_filename_has_prefix(const gchar *str, const gchar *prefix)
360 gchar *head = g_strndup(str, strlen(prefix));
361 gboolean ret = utils_filenamecmp(head, prefix) == 0;
363 g_free(head);
364 return ret;
368 static gchar *get_doc_folder(const gchar *path)
370 gchar *tmp_dirname = g_strdup(path);
371 gchar *project_base_path;
372 gchar *dirname = NULL;
373 const gchar *home_dir = g_get_home_dir();
374 const gchar *rest;
376 /* replace the project base path with the project name */
377 project_base_path = project_get_base_path();
379 if (project_base_path != NULL)
381 gsize len = strlen(project_base_path);
383 /* remove trailing separator so we can match base path exactly */
384 if (project_base_path[len-1] == G_DIR_SEPARATOR)
385 project_base_path[--len] = '\0';
387 /* check whether the dir name matches or uses the project base path */
388 if (utils_filename_has_prefix(tmp_dirname, project_base_path))
390 rest = tmp_dirname + len;
391 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
393 dirname = g_strdup_printf("%s%s", app->project->name, rest);
396 g_free(project_base_path);
398 if (dirname == NULL)
400 dirname = tmp_dirname;
402 /* If matches home dir, replace with tilde */
403 if (!EMPTY(home_dir) && utils_filename_has_prefix(dirname, home_dir))
405 rest = dirname + strlen(home_dir);
406 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
408 dirname = g_strdup_printf("~%s", rest);
409 g_free(tmp_dirname);
413 else
414 g_free(tmp_dirname);
416 return dirname;
420 static GtkTreeIter *get_doc_parent(GeanyDocument *doc)
422 gchar *path;
423 gchar *dirname = NULL;
424 static GtkTreeIter parent;
425 GtkTreeModel *model = GTK_TREE_MODEL(store_openfiles);
426 static GIcon *dir_icon = NULL;
428 if (!documents_show_paths)
429 return NULL;
431 path = g_path_get_dirname(DOC_FILENAME(doc));
432 dirname = get_doc_folder(path);
434 if (gtk_tree_model_get_iter_first(model, &parent))
438 if (find_tree_iter_dir(&parent, dirname))
440 g_free(dirname);
441 g_free(path);
442 return &parent;
445 while (gtk_tree_model_iter_next(model, &parent));
447 /* no match, add dir parent */
448 if (!dir_icon)
449 dir_icon = ui_get_mime_icon("inode/directory");
451 gtk_tree_store_append(store_openfiles, &parent, NULL);
452 gtk_tree_store_set(store_openfiles, &parent, DOCUMENTS_ICON, dir_icon,
453 DOCUMENTS_FILENAME, path,
454 DOCUMENTS_SHORTNAME, doc->file_name ? dirname : GEANY_STRING_UNTITLED, -1);
456 g_free(dirname);
457 g_free(path);
458 return &parent;
462 /* Also sets doc->priv->iter.
463 * This is called recursively in sidebar_openfiles_update_all(). */
464 void sidebar_openfiles_add(GeanyDocument *doc)
466 GtkTreeIter *iter = &doc->priv->iter;
467 GtkTreeIter *parent = get_doc_parent(doc);
468 gchar *basename;
469 const GdkColor *color = document_get_status_color(doc);
470 static GIcon *file_icon = NULL;
472 gtk_tree_store_append(store_openfiles, iter, parent);
474 /* check if new parent */
475 if (parent && gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), parent) == 1)
477 GtkTreePath *path;
479 /* expand parent */
480 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), parent);
481 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
482 gtk_tree_path_free(path);
484 if (!file_icon)
485 file_icon = ui_get_mime_icon("text/plain");
487 basename = g_path_get_basename(DOC_FILENAME(doc));
488 gtk_tree_store_set(store_openfiles, iter,
489 DOCUMENTS_ICON, (doc->file_type && doc->file_type->icon) ? doc->file_type->icon : file_icon,
490 DOCUMENTS_SHORTNAME, basename, DOCUMENTS_DOCUMENT, doc, DOCUMENTS_COLOR, color,
491 DOCUMENTS_FILENAME, DOC_FILENAME(doc), -1);
492 g_free(basename);
496 static void openfiles_remove(GeanyDocument *doc)
498 GtkTreeIter *iter = &doc->priv->iter;
499 GtkTreeIter parent;
501 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter) &&
502 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), &parent) == 1)
503 gtk_tree_store_remove(store_openfiles, &parent);
504 else
505 gtk_tree_store_remove(store_openfiles, iter);
509 void sidebar_openfiles_update(GeanyDocument *doc)
511 GtkTreeIter *iter = &doc->priv->iter;
512 gchar *fname;
514 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_FILENAME, &fname, -1);
516 if (utils_str_equal(fname, DOC_FILENAME(doc)))
518 /* just update color and the icon */
519 const GdkColor *color = document_get_status_color(doc);
520 GIcon *icon = doc->file_type->icon;
522 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_COLOR, color, -1);
523 if (icon)
524 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_ICON, icon, -1);
526 else
528 /* path has changed, so remove and re-add */
529 GtkTreeSelection *treesel;
530 gboolean sel;
532 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
533 sel = gtk_tree_selection_iter_is_selected(treesel, &doc->priv->iter);
534 openfiles_remove(doc);
536 sidebar_openfiles_add(doc);
537 if (sel)
538 gtk_tree_selection_select_iter(treesel, &doc->priv->iter);
540 g_free(fname);
544 void sidebar_openfiles_update_all(void)
546 guint i;
548 gtk_tree_store_clear(store_openfiles);
549 foreach_document (i)
551 sidebar_openfiles_add(documents[i]);
556 void sidebar_remove_document(GeanyDocument *doc)
558 openfiles_remove(doc);
560 if (GTK_IS_WIDGET(doc->priv->tag_tree))
562 gtk_widget_destroy(doc->priv->tag_tree); /* make GTK release its references, if any */
563 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
564 g_object_unref(doc->priv->tag_tree);
565 doc->priv->tag_tree = NULL;
570 static void on_hide_sidebar(void)
572 ui_prefs.sidebar_visible = FALSE;
573 ui_sidebar_show_hide();
577 static gboolean on_sidebar_display_symbol_list_show(GtkWidget *item)
579 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
580 interface_prefs.sidebar_symbol_visible);
581 return FALSE;
585 static gboolean on_sidebar_display_open_files_show(GtkWidget *item)
587 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
588 interface_prefs.sidebar_openfiles_visible);
589 return FALSE;
593 void sidebar_add_common_menu_items(GtkMenu *menu)
595 GtkWidget *item;
597 item = gtk_separator_menu_item_new();
598 gtk_widget_show(item);
599 gtk_container_add(GTK_CONTAINER(menu), item);
601 item = gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
602 gtk_container_add(GTK_CONTAINER(menu), item);
603 g_signal_connect(item, "draw", G_CALLBACK(on_sidebar_display_symbol_list_show), NULL);
604 gtk_widget_show(item);
605 g_signal_connect(item, "activate",
606 G_CALLBACK(on_list_symbol_activate), NULL);
608 item = gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
609 gtk_container_add(GTK_CONTAINER(menu), item);
610 g_signal_connect(item, "draw", G_CALLBACK(on_sidebar_display_open_files_show), NULL);
611 gtk_widget_show(item);
612 g_signal_connect(item, "activate",
613 G_CALLBACK(on_list_document_activate), NULL);
615 item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
616 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
617 gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
618 gtk_widget_show(item);
619 gtk_container_add(GTK_CONTAINER(menu), item);
620 g_signal_connect(item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
624 static void on_openfiles_show_paths_activate(GtkCheckMenuItem *item, gpointer user_data)
626 documents_show_paths = gtk_check_menu_item_get_active(item);
627 sidebar_openfiles_update_all();
631 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data)
633 interface_prefs.sidebar_openfiles_visible = gtk_check_menu_item_get_active(item);
634 ui_sidebar_show_hide();
635 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
639 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data)
641 interface_prefs.sidebar_symbol_visible = gtk_check_menu_item_get_active(item);
642 ui_sidebar_show_hide();
643 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
647 static void on_find_in_files(GtkMenuItem *menuitem, gpointer user_data)
649 GtkTreeSelection *treesel;
650 GtkTreeIter iter;
651 GtkTreeModel *model;
652 GeanyDocument *doc;
653 gchar *dir;
655 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
656 if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
657 return;
658 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
660 if (!doc)
662 gtk_tree_model_get(model, &iter, DOCUMENTS_FILENAME, &dir, -1);
664 else
665 dir = g_path_get_dirname(DOC_FILENAME(doc));
667 search_show_find_in_files_dialog(dir);
668 g_free(dir);
672 static void on_openfiles_expand_collapse(GtkMenuItem *menuitem, gpointer user_data)
674 gboolean expand = GPOINTER_TO_INT(user_data);
676 if (expand)
677 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv.tree_openfiles));
678 else
679 gtk_tree_view_collapse_all(GTK_TREE_VIEW(tv.tree_openfiles));
683 static void create_openfiles_popup_menu(void)
685 GtkWidget *item;
687 openfiles_popup_menu = gtk_menu_new();
689 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, 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_REMOVE));
694 doc_items.close = item;
696 item = gtk_separator_menu_item_new();
697 gtk_widget_show(item);
698 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
700 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, NULL);
701 gtk_widget_show(item);
702 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
703 g_signal_connect(item, "activate",
704 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_SAVE));
705 doc_items.save = item;
707 item = gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
708 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
709 gtk_image_new_from_stock(GTK_STOCK_REVERT_TO_SAVED, GTK_ICON_SIZE_MENU));
710 gtk_widget_show(item);
711 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
712 g_signal_connect(item, "activate",
713 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD));
714 doc_items.reload = item;
716 item = gtk_separator_menu_item_new();
717 gtk_widget_show(item);
718 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
720 item = ui_image_menu_item_new(GTK_STOCK_FIND, _("_Find in Files..."));
721 gtk_widget_show(item);
722 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
723 g_signal_connect(item, "activate", G_CALLBACK(on_find_in_files), NULL);
724 doc_items.find_in_files = item;
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.show_paths = gtk_check_menu_item_new_with_mnemonic(_("Show _Paths"));
731 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths), documents_show_paths);
732 gtk_widget_show(doc_items.show_paths);
733 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.show_paths);
734 g_signal_connect(doc_items.show_paths, "activate",
735 G_CALLBACK(on_openfiles_show_paths_activate), NULL);
737 item = gtk_separator_menu_item_new();
738 gtk_widget_show(item);
739 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
741 doc_items.expand_all = ui_image_menu_item_new(GTK_STOCK_ADD, _("_Expand All"));
742 gtk_widget_show(doc_items.expand_all);
743 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.expand_all);
744 g_signal_connect(doc_items.expand_all, "activate",
745 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(TRUE));
747 doc_items.collapse_all = ui_image_menu_item_new(GTK_STOCK_REMOVE, _("_Collapse All"));
748 gtk_widget_show(doc_items.collapse_all);
749 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.collapse_all);
750 g_signal_connect(doc_items.collapse_all, "activate",
751 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(FALSE));
753 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu));
757 static void unfold_parent(GtkTreeIter *iter)
759 GtkTreeIter parent;
761 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter))
763 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), &parent);
764 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
765 gtk_tree_path_free(path);
770 /* compares the given data with the doc pointer from the selected row of openfiles
771 * treeview, in case of a match the row is selected and TRUE is returned
772 * (called indirectly from gtk_tree_model_foreach()) */
773 static gboolean tree_model_find_node(GtkTreeModel *model, GtkTreePath *path,
774 GtkTreeIter *iter, gpointer data)
776 GeanyDocument *doc;
778 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
780 if (doc == data)
782 /* unfolding also prevents a strange bug where the selection gets stuck on the parent
783 * when it is collapsed and then switching documents */
784 unfold_parent(iter);
785 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv.tree_openfiles), path, NULL, FALSE);
786 return TRUE;
788 else return FALSE;
792 void sidebar_select_openfiles_item(GeanyDocument *doc)
794 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles), tree_model_find_node, doc);
798 /* callbacks */
800 static void document_action(GeanyDocument *doc, gint action)
802 if (! DOC_VALID(doc))
803 return;
805 switch (action)
807 case OPENFILES_ACTION_REMOVE:
809 document_close(doc);
810 break;
812 case OPENFILES_ACTION_SAVE:
814 document_save_file(doc, FALSE);
815 break;
817 case OPENFILES_ACTION_RELOAD:
819 document_reload_prompt(doc, NULL);
820 break;
826 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data)
828 GtkTreeIter iter;
829 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
830 GtkTreeModel *model;
831 GeanyDocument *doc;
832 gint action = GPOINTER_TO_INT(user_data);
834 if (gtk_tree_selection_get_selected(selection, &model, &iter))
836 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
837 if (doc)
839 document_action(doc, action);
841 else
843 /* parent item selected */
844 GtkTreeIter child;
845 gint i = gtk_tree_model_iter_n_children(model, &iter) - 1;
847 while (i >= 0 && gtk_tree_model_iter_nth_child(model, &child, &iter, i))
849 gtk_tree_model_get(model, &child, DOCUMENTS_DOCUMENT, &doc, -1);
851 document_action(doc, action);
852 i--;
859 static void change_focus_to_editor(GeanyDocument *doc, GtkWidget *source_widget)
861 if (may_steal_focus)
862 document_try_focus(doc, source_widget);
863 may_steal_focus = FALSE;
867 static gboolean openfiles_go_to_selection(GtkTreeSelection *selection, guint keyval)
869 GtkTreeIter iter;
870 GtkTreeModel *model;
871 GeanyDocument *doc = NULL;
873 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
874 if (gtk_tree_selection_get_selected(selection, &model, &iter) && ! ignore_callback)
876 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
877 if (! doc)
878 return FALSE; /* parent */
880 /* switch to the doc and grab the focus */
881 document_show_tab(doc);
882 if (keyval != GDK_KEY_space)
883 change_focus_to_editor(doc, tv.tree_openfiles);
885 return FALSE;
889 static gboolean taglist_go_to_selection(GtkTreeSelection *selection, guint keyval, guint state)
891 GtkTreeIter iter;
892 GtkTreeModel *model;
893 gint line = 0;
894 gboolean handled = TRUE;
896 if (gtk_tree_selection_get_selected(selection, &model, &iter))
898 TMTag *tag;
900 gtk_tree_model_get(model, &iter, SYMBOLS_COLUMN_TAG, &tag, -1);
901 if (! tag)
902 return FALSE;
904 line = tag->line;
905 if (line > 0)
907 GeanyDocument *doc = document_get_current();
909 if (doc != NULL)
911 navqueue_goto_line(doc, doc, line);
912 state = keybindings_get_modifiers(state);
913 if (keyval != GDK_KEY_space && ! (state & GEANY_PRIMARY_MOD_MASK))
914 change_focus_to_editor(doc, NULL);
915 else
916 handled = FALSE;
919 tm_tag_unref(tag);
921 return handled;
925 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
926 gpointer user_data)
928 may_steal_focus = FALSE;
929 if (ui_is_keyval_enter_or_return(event->keyval) || event->keyval == GDK_KEY_space)
931 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
932 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
933 may_steal_focus = TRUE;
935 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
936 * doing so will prevent further handlers to be run in most cases, but the only one is our
937 * own, so guess it's fine. */
938 if (widget_class->key_press_event)
939 widget_class->key_press_event(widget, event);
941 if (widget == tv.tree_openfiles) /* tag and doc list have separate handlers */
942 openfiles_go_to_selection(selection, event->keyval);
943 else
944 taglist_go_to_selection(selection, event->keyval, event->state);
946 return TRUE;
948 return FALSE;
952 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
953 G_GNUC_UNUSED gpointer user_data)
955 GtkTreeSelection *selection;
956 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
957 gboolean handled = FALSE;
959 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
960 * doing so will prevent further handlers to be run in most cases, but the only one is our own,
961 * so guess it's fine. */
962 if (widget_class->button_press_event)
963 handled = widget_class->button_press_event(widget, event);
965 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
966 may_steal_focus = TRUE;
968 if (event->type == GDK_2BUTTON_PRESS)
969 { /* double click on parent node(section) expands/collapses it */
970 GtkTreeModel *model;
971 GtkTreeIter iter;
973 if (gtk_tree_selection_get_selected(selection, &model, &iter))
975 if (gtk_tree_model_iter_has_child(model, &iter))
977 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
979 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
980 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget), path);
981 else
982 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget), path, FALSE);
984 gtk_tree_path_free(path);
985 return TRUE;
989 else if (event->button == 1)
990 { /* allow reclicking of taglist treeview item */
991 if (widget == tv.tree_openfiles)
993 openfiles_go_to_selection(selection, 0);
994 handled = TRUE;
996 else
997 handled = taglist_go_to_selection(selection, 0, event->state);
999 else if (event->button == 2)
1001 if (widget == tv.tree_openfiles)
1002 on_openfiles_document_action(NULL, GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
1004 else if (event->button == 3)
1006 if (widget == tv.tree_openfiles)
1008 if (!openfiles_popup_menu)
1009 create_openfiles_popup_menu();
1011 /* update menu item sensitivity */
1012 documents_menu_update(selection);
1013 gtk_menu_popup(GTK_MENU(openfiles_popup_menu), NULL, NULL, NULL, NULL,
1014 event->button, event->time);
1016 else
1018 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
1019 event->button, event->time);
1021 handled = TRUE;
1023 return handled;
1027 static void documents_menu_update(GtkTreeSelection *selection)
1029 GtkTreeModel *model;
1030 GtkTreeIter iter;
1031 gboolean sel, path;
1032 gchar *shortname = NULL;
1033 GeanyDocument *doc = NULL;
1035 /* maybe no selection e.g. if ctrl-click deselected */
1036 sel = gtk_tree_selection_get_selected(selection, &model, &iter);
1037 if (sel)
1039 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc,
1040 DOCUMENTS_SHORTNAME, &shortname, -1);
1042 path = !EMPTY(shortname) &&
1043 (g_path_is_absolute(shortname) ||
1044 (app->project && g_str_has_prefix(shortname, app->project->name)));
1046 /* can close all, save all (except shortname), but only reload individually ATM */
1047 gtk_widget_set_sensitive(doc_items.close, sel);
1048 gtk_widget_set_sensitive(doc_items.save, (doc && doc->real_path) || path);
1049 gtk_widget_set_sensitive(doc_items.reload, doc && doc->real_path);
1050 gtk_widget_set_sensitive(doc_items.find_in_files, sel);
1051 g_free(shortname);
1053 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths), documents_show_paths);
1054 gtk_widget_set_sensitive(doc_items.expand_all, documents_show_paths);
1055 gtk_widget_set_sensitive(doc_items.collapse_all, documents_show_paths);
1059 static StashGroup *stash_group = NULL;
1061 static void on_load_settings(void)
1063 tag_window = ui_lookup_widget(main_widgets.window, "scrolledwindow2");
1065 prepare_openfiles();
1066 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
1067 stash_group_display(stash_group, NULL);
1068 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1072 static void on_save_settings(void)
1074 stash_group_update(stash_group, NULL);
1075 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1079 static void on_sidebar_switch_page(GtkNotebook *notebook,
1080 gpointer page, guint page_num, gpointer user_data)
1082 if (page_num == TREEVIEW_SYMBOL)
1083 sidebar_update_tag_list(document_get_current(), FALSE);
1087 void sidebar_init(void)
1089 StashGroup *group;
1091 group = stash_group_new(PACKAGE);
1092 stash_group_add_boolean(group, &documents_show_paths, "documents_show_paths", TRUE);
1093 stash_group_add_widget_property(group, &ui_prefs.sidebar_page, "sidebar_page", GINT_TO_POINTER(0),
1094 main_widgets.sidebar_notebook, "page", 0);
1095 configuration_add_pref_group(group, FALSE);
1096 stash_group = group;
1098 /* delay building documents treeview until sidebar font has been read */
1099 g_signal_connect(geany_object, "load-settings", on_load_settings, NULL);
1100 g_signal_connect(geany_object, "save-settings", on_save_settings, NULL);
1102 g_signal_connect(main_widgets.sidebar_notebook, "page-added",
1103 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1104 g_signal_connect(main_widgets.sidebar_notebook, "page-removed",
1105 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1106 /* tabs may have changed when sidebar is reshown */
1107 g_signal_connect(main_widgets.sidebar_notebook, "show",
1108 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1109 g_signal_connect_after(main_widgets.sidebar_notebook, "switch-page",
1110 G_CALLBACK(on_sidebar_switch_page), NULL);
1112 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1115 #define WIDGET(w) w && GTK_IS_WIDGET(w)
1117 void sidebar_finalize(void)
1119 if (WIDGET(tv.default_tag_tree))
1121 gtk_widget_destroy(tv.default_tag_tree); /* make GTK release its references, if any... */
1122 g_object_unref(tv.default_tag_tree); /* ...and release our own */
1124 if (WIDGET(tv.popup_taglist))
1125 gtk_widget_destroy(tv.popup_taglist);
1126 if (WIDGET(openfiles_popup_menu))
1127 gtk_widget_destroy(openfiles_popup_menu);
1131 void sidebar_focus_openfiles_tab(void)
1133 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_openfiles_visible)
1135 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1137 gtk_notebook_set_current_page(notebook, TREEVIEW_OPENFILES);
1138 gtk_widget_grab_focus(tv.tree_openfiles);
1143 void sidebar_focus_symbols_tab(void)
1145 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_symbol_visible)
1147 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1148 GtkWidget *symbol_list_scrollwin = gtk_notebook_get_nth_page(notebook, TREEVIEW_SYMBOL);
1150 gtk_notebook_set_current_page(notebook, TREEVIEW_SYMBOL);
1151 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin)));
1156 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
1157 guint page_num, gpointer data)
1159 gint tabs = gtk_notebook_get_n_pages(notebook);
1161 if (interface_prefs.sidebar_symbol_visible == FALSE)
1162 tabs--;
1163 if (interface_prefs.sidebar_openfiles_visible == FALSE)
1164 tabs--;
1166 gtk_notebook_set_show_tabs(notebook, (tabs > 1));