Fix anonymous tag renaming when interleaved by scopeless macros
[geany-mirror.git] / src / sidebar.c
blobd27f73a9d12d0b285b5c921a678eb68002d8830b
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[OPENFILES_PATHS_COUNT];
60 GtkWidget *find_in_files;
61 GtkWidget *expand_all;
62 GtkWidget *collapse_all;
64 doc_items;
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 static GtkTreeStore *store_openfiles;
80 static GtkWidget *openfiles_popup_menu;
81 static GtkWidget *tag_window; /* scrolled window that holds the symbol list GtkTreeView */
83 /* callback prototypes */
84 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data);
85 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
86 gpointer user_data);
87 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
88 gpointer user_data);
89 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data);
90 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data);
91 static void documents_menu_update(GtkTreeSelection *selection);
92 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
93 guint page_num, gpointer data);
96 /* the prepare_* functions are document-related, but I think they fit better here than in document.c */
97 static void prepare_taglist(GtkWidget *tree, GtkTreeStore *store)
99 GtkCellRenderer *text_renderer, *icon_renderer;
100 GtkTreeViewColumn *column;
101 GtkTreeSelection *selection;
103 text_renderer = gtk_cell_renderer_text_new();
104 icon_renderer = gtk_cell_renderer_pixbuf_new();
105 column = gtk_tree_view_column_new();
107 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
108 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", SYMBOLS_COLUMN_ICON, NULL);
109 g_object_set(icon_renderer, "xalign", 0.0, NULL);
111 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
112 gtk_tree_view_column_set_attributes(column, text_renderer, "text", SYMBOLS_COLUMN_NAME, NULL);
113 g_object_set(text_renderer, "yalign", 0.5, NULL);
114 gtk_tree_view_column_set_title(column, _("Symbols"));
116 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
117 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
119 ui_widget_modify_font_from_string(tree, interface_prefs.tagbar_font);
121 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
122 g_object_unref(store);
124 g_signal_connect(tree, "button-press-event",
125 G_CALLBACK(sidebar_button_press_cb), NULL);
126 g_signal_connect(tree, "key-press-event",
127 G_CALLBACK(sidebar_key_press_cb), NULL);
129 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(tree), interface_prefs.show_symbol_list_expanders);
130 if (! interface_prefs.show_symbol_list_expanders)
131 gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(tree), 10);
132 /* Tooltips */
133 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tree), SYMBOLS_COLUMN_TOOLTIP);
135 /* selection handling */
136 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
137 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
138 /* callback for changed selection not necessary, will be handled by button-press-event */
142 static gboolean
143 on_default_tag_tree_button_press_event(GtkWidget *widget, GdkEventButton *event,
144 gpointer user_data)
146 if (event->button == 3)
148 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
149 event->button, event->time);
150 return TRUE;
152 return FALSE;
156 static void create_default_tag_tree(void)
158 GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(tag_window);
159 GtkWidget *label;
161 /* default_tag_tree is a GtkViewPort with a GtkLabel inside it */
162 tv.default_tag_tree = gtk_viewport_new(
163 gtk_scrolled_window_get_hadjustment(scrolled_window),
164 gtk_scrolled_window_get_vadjustment(scrolled_window));
165 gtk_viewport_set_shadow_type(GTK_VIEWPORT(tv.default_tag_tree), GTK_SHADOW_NONE);
166 label = gtk_label_new(_("No symbols found"));
167 gtk_misc_set_alignment(GTK_MISC(label), 0.1f, 0.01f);
168 gtk_container_add(GTK_CONTAINER(tv.default_tag_tree), label);
169 gtk_widget_show_all(tv.default_tag_tree);
170 g_signal_connect(tv.default_tag_tree, "button-press-event",
171 G_CALLBACK(on_default_tag_tree_button_press_event), NULL);
172 g_object_ref((gpointer)tv.default_tag_tree); /* to hold it after removing */
176 /* update = rescan the tags for doc->filename */
177 void sidebar_update_tag_list(GeanyDocument *doc, gboolean update)
179 GtkWidget *child = gtk_bin_get_child(GTK_BIN(tag_window));
181 g_return_if_fail(doc == NULL || doc->is_valid);
183 if (update && doc != NULL)
184 doc->priv->tag_tree_dirty = TRUE;
186 if (gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets.sidebar_notebook)) != TREEVIEW_SYMBOL)
187 return; /* don't bother updating symbol tree if we don't see it */
189 /* changes the tree view to the given one, trying not to do useless changes */
190 #define CHANGE_TREE(new_child) \
191 G_STMT_START { \
192 /* only change the tag tree if it's actually not the same (to avoid flickering) and if \
193 * it's the one of the current document (to avoid problems when e.g. reloading \
194 * configuration files */ \
195 if (child != new_child && doc == document_get_current()) \
197 if (child) \
198 gtk_container_remove(GTK_CONTAINER(tag_window), child); \
199 gtk_container_add(GTK_CONTAINER(tag_window), new_child); \
201 } G_STMT_END
203 if (tv.default_tag_tree == NULL)
204 create_default_tag_tree();
206 /* show default empty tag tree if there are no tags */
207 if (doc == NULL || doc->file_type == NULL || ! filetype_has_tags(doc->file_type))
209 CHANGE_TREE(tv.default_tag_tree);
210 return;
213 if (doc->priv->tag_tree_dirty)
214 { /* updating the tag list in the left tag window */
215 if (doc->priv->tag_tree == NULL)
217 doc->priv->tag_store = gtk_tree_store_new(
218 SYMBOLS_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, TM_TYPE_TAG, G_TYPE_STRING);
219 doc->priv->tag_tree = gtk_tree_view_new();
220 prepare_taglist(doc->priv->tag_tree, doc->priv->tag_store);
221 gtk_widget_show(doc->priv->tag_tree);
222 g_object_ref((gpointer)doc->priv->tag_tree); /* to hold it after removing */
225 doc->has_tags = symbols_recreate_tag_list(doc, SYMBOLS_SORT_USE_PREVIOUS);
226 doc->priv->tag_tree_dirty = FALSE;
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 *name_a, *name_b;
247 GeanyDocument *doc_a, *doc_b;
248 gint cmp;
250 gtk_tree_model_get(model, iter_a, DOCUMENTS_SHORTNAME, &name_a, DOCUMENTS_DOCUMENT, &doc_a, -1);
251 gtk_tree_model_get(model, iter_b, DOCUMENTS_SHORTNAME, &name_b, DOCUMENTS_DOCUMENT, &doc_b, -1);
253 /* sort dirs after files (within a directory node) */
254 if (!doc_a && doc_b)
255 cmp = 1;
256 else if (doc_a && !doc_b)
257 cmp = -1;
258 else
260 gchar *key_a, *key_b;
261 /* g_utf8_collate_key_for_filename() seems to ignore ~. As a result, ~/dev <=> /data
262 * compares as /dev > /data while ~/dev <=> /etc compares as /dev < /etc. Thus, for sorting
263 * purposes, treat leading ~ as . which is documented to be special-cased to sort before
264 * anything else. The side effect, that documents under ~ are always first is actually
265 * welcome.
267 name_a[0] = name_a[0] == '~' ? '.' : name_a[0];
268 name_b[0] = name_b[0] == '~' ? '.' : name_b[0];
269 key_a = g_utf8_collate_key_for_filename(name_a, -1);
270 key_b = g_utf8_collate_key_for_filename(name_b, -1);
271 cmp = strcmp(key_a, key_b);
272 g_free(key_b);
273 g_free(key_a);
276 g_free(name_b);
277 g_free(name_a);
279 return cmp;
282 GEANY_EXPORT_SYMBOL
283 GtkTreeStore *sidebar_create_store_openfiles(void)
285 GtkTreeSortable *sortable;
286 GtkTreeStore *store;
287 /* store the icon and the short filename to show, and the index as reference,
288 * the colour (black/red/green) and the full name for the tooltip */
289 store = gtk_tree_store_new(6, G_TYPE_ICON, G_TYPE_STRING,
290 G_TYPE_POINTER, GDK_TYPE_COLOR, G_TYPE_STRING, G_TYPE_BOOLEAN);
292 /* sort opened filenames in the store_openfiles treeview */
293 sortable = GTK_TREE_SORTABLE(GTK_TREE_MODEL(store));
294 gtk_tree_sortable_set_sort_func(sortable, DOCUMENTS_SHORTNAME, documents_sort_func, NULL, NULL);
295 gtk_tree_sortable_set_sort_column_id(sortable, DOCUMENTS_SHORTNAME, GTK_SORT_ASCENDING);
297 store_openfiles = store;
298 return store;
302 static void store_fold_recurse(GtkTreeView *view,
303 GtkTreeIter *iter,
304 GtkTreeModel *model)
306 GeanyDocument *doc;
307 gboolean fold, valid;
308 GtkTreePath *path;
309 GtkTreeIter child_iter;
311 gtk_tree_model_get(model, iter, DOCUMENTS_DOCUMENT, &doc, -1);
312 if (doc) /* document rows are not foldable */
313 return;
315 path = gtk_tree_model_get_path(model, iter);
316 fold = !gtk_tree_view_row_expanded(view, path);
317 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DOCUMENTS_FOLD, fold, -1);
318 gtk_tree_path_free(path);
320 /* After storing the fold state for *this* row, recursively do the same for its children.
321 * We need do do this only for expanded children because all children of folded rows are
322 * folded as well.
324 if (fold)
325 return;
327 valid = gtk_tree_model_iter_children(model, &child_iter, iter);
328 while (valid)
330 store_fold_recurse(view, &child_iter, model);
331 valid = gtk_tree_model_iter_next(model, &child_iter);
336 static gboolean on_row_expand(GtkTreeView *view,
337 GtkTreeIter *iter,
338 GtkTreePath *path,
339 gpointer user_data)
341 GtkTreeModel *model;
343 model = gtk_tree_view_get_model(view);
344 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DOCUMENTS_FOLD, FALSE, -1);
345 return FALSE;
349 static gboolean on_row_collapse(GtkTreeView *view,
350 GtkTreeIter *iter,
351 GtkTreePath *path,
352 gpointer user_data)
354 GtkTreeModel *model;
355 GtkTreeIter child_iter;
356 gboolean valid;
358 model = gtk_tree_view_get_model(view);
359 gtk_tree_store_set(GTK_TREE_STORE(model), iter, DOCUMENTS_FOLD, TRUE, -1);
361 valid = gtk_tree_model_iter_children(model, &child_iter, iter);
362 while (valid)
364 store_fold_recurse(view, &child_iter, model);
365 valid = gtk_tree_model_iter_next(model, &child_iter);
368 return FALSE;
372 static void on_row_expanded(GtkTreeView *view,
373 GtkTreeIter *iter,
374 GtkTreePath *path_,
375 gpointer user_data)
377 GtkTreeIter child_iter;
378 GtkTreeModel *model;
379 GtkTreePath *path;
380 gboolean valid;
381 GeanyDocument *doc;
383 model = gtk_tree_view_get_model(view);
385 valid = gtk_tree_model_iter_children(model, &child_iter, iter);
386 while (valid)
388 gboolean fold;
390 gtk_tree_model_get(model, &child_iter, DOCUMENTS_DOCUMENT, &doc, DOCUMENTS_FOLD, &fold, -1);
391 path = gtk_tree_model_get_path(model, &child_iter);
393 if (!doc && !fold)
394 gtk_tree_view_expand_row(view, path, FALSE);
396 valid = gtk_tree_model_iter_next(model, &child_iter);
397 gtk_tree_path_free(path);
402 /* does some preparing things to the open files list widget */
403 static void prepare_openfiles(void)
405 GtkCellRenderer *icon_renderer;
406 GtkCellRenderer *text_renderer;
407 GtkTreeViewColumn *column;
408 GtkTreeSelection *selection;
410 tv.tree_openfiles = ui_lookup_widget(main_widgets.window, "treeview6");
412 sidebar_create_store_openfiles();
414 gtk_tree_view_set_model(GTK_TREE_VIEW(tv.tree_openfiles), GTK_TREE_MODEL(store_openfiles));
416 /* These two implement "remember fold state of rows when their parents are folded". Normally
417 * GTK does not remember the fold state and can only expand all or no children when
418 * expanding a row. Maybe this can be useful for other tree views as well?
420 g_signal_connect_after(GTK_TREE_VIEW(tv.tree_openfiles), "test-expand-row", G_CALLBACK(on_row_expand), NULL);
421 g_signal_connect_after(GTK_TREE_VIEW(tv.tree_openfiles), "test-collapse-row", G_CALLBACK(on_row_collapse), NULL);
422 g_signal_connect_after(GTK_TREE_VIEW(tv.tree_openfiles), "row-expanded", G_CALLBACK(on_row_expanded), NULL);
424 /* set policy settings for the scolledwindow around the treeview again, because glade
425 * doesn't keep the settings */
426 gtk_scrolled_window_set_policy(
427 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets.window, "scrolledwindow7")),
428 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
430 icon_renderer = gtk_cell_renderer_pixbuf_new();
431 g_object_set(icon_renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
432 text_renderer = gtk_cell_renderer_text_new();
433 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
434 column = gtk_tree_view_column_new();
435 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
436 gtk_tree_view_column_set_attributes(column, icon_renderer, "gicon", DOCUMENTS_ICON, NULL);
437 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
438 gtk_tree_view_column_set_attributes(column, text_renderer, "text", DOCUMENTS_SHORTNAME,
439 "foreground-gdk", DOCUMENTS_COLOR, NULL);
440 gtk_tree_view_append_column(GTK_TREE_VIEW(tv.tree_openfiles), column);
441 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv.tree_openfiles), FALSE);
443 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv.tree_openfiles),
444 DOCUMENTS_SHORTNAME);
446 ui_widget_modify_font_from_string(tv.tree_openfiles, interface_prefs.tagbar_font);
448 /* tooltips */
449 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tv.tree_openfiles), DOCUMENTS_FILENAME);
451 /* selection handling */
452 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
453 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
454 g_object_unref(store_openfiles);
456 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "button-press-event",
457 G_CALLBACK(sidebar_button_press_cb), NULL);
458 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "key-press-event",
459 G_CALLBACK(sidebar_key_press_cb), NULL);
463 static gboolean utils_filename_has_prefix(const gchar *str, const gchar *prefix)
465 gchar *head = g_strndup(str, strlen(prefix));
466 gboolean ret = utils_filenamecmp(head, prefix) == 0;
468 g_free(head);
469 return ret;
473 static gchar *get_project_folder(const gchar *path)
475 gchar *project_base_path;
476 gchar *dirname = NULL;
477 const gchar *rest;
479 /* replace the project base path with the project name */
480 project_base_path = project_get_base_path();
482 if (project_base_path != NULL)
484 gsize len = strlen(project_base_path);
486 /* remove trailing separator so we can match base path exactly */
487 if (project_base_path[len-1] == G_DIR_SEPARATOR)
488 project_base_path[--len] = '\0';
490 /* check whether the dir name matches or uses the project base path */
491 if (utils_filename_has_prefix(path, project_base_path))
493 rest = path + len;
494 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
496 dirname = g_strdup_printf("%s%s", app->project->name, rest);
499 g_free(project_base_path);
502 return dirname;
506 static gchar *get_doc_folder(const gchar *path)
508 gchar *dirname = get_project_folder(path);
509 const gchar *rest;
511 if (dirname == NULL)
513 const gchar *home_dir = g_get_home_dir();
514 gchar *tmp_dirname = g_strdup(path);
516 dirname = tmp_dirname;
517 /* If matches home dir, replace with tilde */
518 if (!EMPTY(home_dir) && utils_filename_has_prefix(dirname, home_dir))
520 rest = dirname + strlen(home_dir);
521 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
523 dirname = g_strdup_printf("~%s", rest);
524 g_free(tmp_dirname);
529 return dirname;
533 static gchar *parent_dir_name(GtkTreeStore *tree, GtkTreeIter *parent, const gchar *path)
535 gsize parent_len = 0;
536 gchar *dirname;
537 gchar *pathname = NULL;
539 if (parent)
541 gchar *parent_dir;
542 GtkTreeModel *model = GTK_TREE_MODEL(tree);
544 gtk_tree_model_get(model, parent, DOCUMENTS_FILENAME, &parent_dir, -1);
545 if (parent_dir)
547 pathname = get_doc_folder(parent_dir);
548 parent_len = strlen(pathname) + 1;
549 g_free(parent_dir);
553 dirname = get_doc_folder(path);
554 if (parent_len)
556 gsize len;
557 dirname = get_doc_folder(path);
558 len = strlen(dirname);
559 /* Maybe parent is /home but dirname is ~ (after substitution from /home/user) */
560 if (pathname[0] == dirname[0])
561 memmove(dirname, dirname + parent_len, len - parent_len + 1);
564 g_free(pathname);
566 return dirname;
570 static void tree_copy_node(GtkTreeStore *tree, GtkTreeIter *new_node, GtkTreeIter *node, GtkTreeIter *parent_new)
572 GIcon *icon;
573 gchar *filename;
574 gchar *shortname;
575 GdkColor *color;
576 GeanyDocument *doc;
577 GtkTreeModel *model = GTK_TREE_MODEL(tree);
578 gboolean fold;
580 gtk_tree_store_append(tree, new_node, parent_new);
581 gtk_tree_model_get(model, node,
582 DOCUMENTS_ICON, &icon,
583 DOCUMENTS_SHORTNAME, &shortname,
584 DOCUMENTS_DOCUMENT, &doc,
585 DOCUMENTS_COLOR, &color,
586 DOCUMENTS_FILENAME, &filename,
587 DOCUMENTS_FOLD, &fold,
588 -1);
590 if (doc)
591 doc->priv->iter = *new_node;
592 else
593 SETPTR(shortname, parent_dir_name(tree, parent_new, filename));
595 gtk_tree_store_set(tree, new_node,
596 DOCUMENTS_ICON, icon,
597 DOCUMENTS_SHORTNAME, shortname,
598 DOCUMENTS_DOCUMENT, doc,
599 DOCUMENTS_COLOR, color,
600 DOCUMENTS_FILENAME, filename,
601 DOCUMENTS_FOLD, fold,
602 -1);
603 g_free(filename);
604 g_free(shortname);
605 if (color)
606 gdk_color_free(color);
610 /* Helper that implements the recursive part of tree_reparent() */
611 static void tree_reparent_recurse(GtkTreeStore *tree, GtkTreeIter *node, GtkTreeIter *parent_new, GtkTreeIter *new_node)
613 GtkTreeModel *model = GTK_TREE_MODEL(tree);
614 GtkTreeIter child;
616 /* Start by copying the node itself. It becomes parent_new for the children to be copied. */
617 tree_copy_node(tree, new_node, node, parent_new);
618 if (gtk_tree_model_iter_nth_child(model, &child, node, 0))
620 do {
621 GtkTreeIter new_child;
622 tree_reparent_recurse(tree, &child, new_node, &new_child);
624 while (gtk_tree_model_iter_next(model, &child));
630 * Copy node and all of its children to a new parent, and then remove the old node.
632 * It is done by reparenting the node itself to the new parent, creating a copy of it,
633 * and then recursively reparenting all children to the copy of the node.
635 * Finally, the new location will be written back to node so it's readily available,
636 * e.g. to unfold it.
637 * */
638 static void tree_reparent(GtkTreeStore *tree, GtkTreeIter *node, GtkTreeIter *parent_new)
640 GtkTreeIter new_node;
641 tree_reparent_recurse(tree, node, parent_new, &new_node);
642 gtk_tree_store_remove(tree, node);
643 *node = new_node;
647 static void tree_add_new_dir(GtkTreeStore *tree, GtkTreeIter *child, GtkTreeIter *parent, const gchar *file)
649 static GIcon *dir_icon = NULL;
650 gchar *dirname = parent_dir_name(tree, parent, file);
652 if (!dir_icon)
653 dir_icon = ui_get_mime_icon("inode/directory");
655 gtk_tree_store_append(tree, child, parent);
656 gtk_tree_store_set(tree, child,
657 DOCUMENTS_ICON, dir_icon,
658 DOCUMENTS_FILENAME, file,
659 DOCUMENTS_SHORTNAME, dirname,
660 DOCUMENTS_FOLD, TRUE, /* GTK inserts folded by default, caller may expand */
661 -1);
663 g_free(dirname);
668 * Returns the position of dir delimiter where paths don't match
669 * */
670 static guint pathcmp(const gchar *s1, const gchar *s2)
672 guint i = 0;
673 gchar *a;
674 gchar *b;
676 g_return_val_if_fail(s1 != NULL, 0);
677 g_return_val_if_fail(s2 != NULL, 0);
679 #ifdef G_OS_WIN32
680 a = utils_utf8_strdown(s1);
681 if (NULL == a)
682 return 0;
683 b = utils_utf8_strdown(s2);
684 if (NULL == b)
686 g_free(a);
687 return 0;
689 #else
690 a = (gchar*)s1;
691 b = (gchar*)s2;
692 #endif
694 while (a[i] && b[i] && a[i] == b[i])
695 i++;
696 if (a[i] == '\0' && b[i] == '\0')
697 return i; /* strings are equal: a/b/c == a/b/c */
698 if ((a[i] == '\0' && b[i] == G_DIR_SEPARATOR) ||
699 (b[i] == '\0' && a[i] == G_DIR_SEPARATOR))
700 return i; /* subdir case: a/b/c == a/b */
701 while (i > 0 && (a[i] != G_DIR_SEPARATOR || b[i] != G_DIR_SEPARATOR))
702 i--; /* last slash: a/b/boo == a/b/bar */
704 #ifdef G_OS_WIN32
705 g_free(a);
706 g_free(b);
707 #endif
709 return i;
713 typedef struct TreeForeachData {
714 gchar *needle;
715 gsize best_len;
716 gsize needle_len;
717 GtkTreeIter best_iter;
718 enum {
719 TREE_CASE_NONE,
720 TREE_CASE_EQUALS,
721 TREE_CASE_CHILD_OF,
722 TREE_CASE_PARENT_OF,
723 TREE_CASE_HAVE_SAME_PARENT
724 } best_case;
725 } TreeForeachData;
728 static gboolean tree_foreach_callback(GtkTreeModel *model,
729 GtkTreePath *path,
730 GtkTreeIter *iter,
731 gpointer user_data)
733 gchar *name;
734 gchar *dirname;
735 guint diff;
736 gsize name_len;
737 GeanyDocument *doc;
738 TreeForeachData *data = (TreeForeachData*) user_data;
740 gtk_tree_model_get(model, iter, DOCUMENTS_FILENAME, &name, DOCUMENTS_DOCUMENT, &doc, -1);
742 if (doc) /* skip documents */
743 goto finally;
745 dirname = get_doc_folder(name);
746 if (dirname)
747 SETPTR(name, dirname);
749 diff = pathcmp(name, data->needle);
750 name_len = strlen(name);
752 if (diff == 0)
753 goto finally;
755 if (data->best_len < diff)
757 gint best_case;
758 gboolean tree = interface_prefs.openfiles_path_mode == OPENFILES_PATHS_TREE;
760 /* there are four cases */
761 /* first case: exact match. File is from already opened dir */
762 if (name_len == diff && data->needle_len == name_len)
763 best_case = TREE_CASE_EQUALS;
764 /* second case: split current dir. File is from deeper level */
765 else if (name_len == diff && tree)
766 best_case = TREE_CASE_CHILD_OF;
767 /* third case: split parent dir. File is from one of existing level */
768 else if (data->needle_len == diff && tree)
769 best_case = TREE_CASE_PARENT_OF;
770 /* fourth case: both dirs have same parent */
771 else if (tree)
772 best_case = TREE_CASE_HAVE_SAME_PARENT;
773 else
774 goto finally;
775 data->best_len = diff;
776 data->best_case = best_case;
777 data->best_iter = *iter;
779 finally:
780 g_free(name);
781 return FALSE;
785 /* Returns TRUE if parent points to a newly added row,
786 * caller might want to expand the relevant rows in the tree view */
787 static gboolean get_parent_for_file(GtkTreeStore *tree, const gchar *file, GtkTreeIter *parent)
789 gchar *path;
790 GtkTreeIter iter;
791 gint name_diff = 0;
792 gboolean has_parent;
793 GtkTreeModel *model = GTK_TREE_MODEL(tree);
794 TreeForeachData data = {NULL, 0, 0, {0}, TREE_CASE_NONE};
795 gboolean new_row;
797 path = g_path_get_dirname(file);
799 /* find best opened dir */
800 data.needle = get_doc_folder(path);
801 data.needle_len = strlen(data.needle);
802 name_diff = strlen(path) - data.needle_len;
803 gtk_tree_model_foreach(model, tree_foreach_callback, (gpointer)&data);
805 switch (data.best_case)
807 case TREE_CASE_EQUALS:
809 *parent = data.best_iter;
810 /* dir already open */
811 new_row = FALSE;
812 break;
814 case TREE_CASE_CHILD_OF:
816 /* This dir is longer than existing so just add child */
817 tree_add_new_dir(tree, parent, &data.best_iter, path);
818 new_row = TRUE;
819 break;
821 case TREE_CASE_PARENT_OF:
823 /* More complicated logic. This dir should be a parent
824 * of existing, so reparent existing dir.
826 has_parent = gtk_tree_model_iter_parent(model, &iter, &data.best_iter);
827 tree_add_new_dir(tree, parent, has_parent ? &iter : NULL, path);
828 tree_reparent(tree, &data.best_iter, parent);
829 new_row = TRUE;
830 break;
832 case TREE_CASE_HAVE_SAME_PARENT:
834 /* Even more complicated logic. Both dirs have same
835 * parent, so create new parent and reparent them
837 GtkTreeIter new_parent;
838 gchar *newpath = g_strndup(path, data.best_len + name_diff);
840 has_parent = gtk_tree_model_iter_parent(model, &iter, &data.best_iter);
841 tree_add_new_dir(tree, &new_parent, has_parent ? &iter : NULL, newpath);
842 tree_reparent(tree, &data.best_iter, &new_parent);
843 tree_add_new_dir(tree, parent, &new_parent, path);
845 g_free(newpath);
846 new_row = TRUE;
847 break;
849 default:
851 tree_add_new_dir(tree, parent, NULL, path);
852 new_row = TRUE;
853 break;
857 g_free(data.needle);
858 g_free(path);
860 return new_row;
864 /* Returns true when parent points to a newly added row. */
865 static gboolean sidebar_openfiles_add_iter(GtkTreeStore *tree, const gchar *file,
866 GtkTreeIter *iter, GtkTreeIter *parent)
868 gboolean new_row;
869 /* get_parent_for_file() might add rows for parent directories */
870 new_row = get_parent_for_file(tree, file, parent);
871 /* insert row for this file */
872 gtk_tree_store_append(tree, iter, parent);
874 return new_row;
878 static void expand_iter(GtkTreeIter *iter)
880 GtkTreePath *path;
882 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), iter);
883 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tv.tree_openfiles), path);
884 gtk_tree_path_free(path);
888 /* Also sets doc->priv->iter.
889 * This is called recursively in sidebar_openfiles_update_all(). */
890 GEANY_EXPORT_SYMBOL
891 void sidebar_openfiles_add(GeanyDocument *doc)
893 GtkTreeIter *iter = &doc->priv->iter;
894 GtkTreeIter parent;
895 const gchar *filename = DOC_FILENAME(doc);
896 gchar *basename;
897 const GdkColor *color = document_get_status_color(doc);
898 static GIcon *file_icon = NULL;
899 gboolean expand = FALSE;
901 if (interface_prefs.openfiles_path_mode != OPENFILES_PATHS_NONE)
902 expand = sidebar_openfiles_add_iter(store_openfiles, filename, iter, &parent);
903 else
904 gtk_tree_store_append(store_openfiles, iter, NULL);
906 if (!file_icon)
907 file_icon = ui_get_mime_icon("text/plain");
909 basename = g_path_get_basename(filename);
910 gtk_tree_store_set(store_openfiles, iter,
911 DOCUMENTS_ICON, (doc->file_type && doc->file_type->icon) ? doc->file_type->icon : file_icon,
912 DOCUMENTS_SHORTNAME, basename, DOCUMENTS_DOCUMENT, doc, DOCUMENTS_COLOR, color,
913 DOCUMENTS_FILENAME, DOC_FILENAME(doc),
914 DOCUMENTS_FOLD, FALSE,
915 -1);
916 g_free(basename);
918 /* Expand new parent if necessary. Beware: this is executed by unit tests
919 * which don't create the tree view. */
920 if (expand && G_LIKELY(tv.tree_openfiles))
921 expand_iter(&parent);
925 /* Returns true if new_node points to a reparented directory, as a result of merging empty
926 * directories.
928 void sidebar_openfiles_remove_iter(GtkTreeStore *tree, GtkTreeIter *iter_)
930 GtkTreeIter iter = *iter_;
931 GtkTreeIter parent;
932 GtkTreeModel *model = GTK_TREE_MODEL(store_openfiles);
934 /* walk on top and close all orphaned parents */
935 while (gtk_tree_model_iter_parent(model, &parent, &iter)
936 && gtk_tree_model_iter_n_children(model, &parent) == 1)
938 iter = parent;
940 gtk_tree_store_remove(store_openfiles, &iter);
942 /* If, after removing, there is a single silbling left and it represents
943 * a directory, it can be merged with the parent directory row,
944 * essentially to reverse the effect of TREE_CASE_PARENT_OF and TREE_CASE_HAVE_SAME_PARENT
945 * in get_doc_parent(). Inherit fold state from the merged child as well.
947 if (gtk_tree_store_iter_is_valid(store_openfiles, &parent)
948 && gtk_tree_model_iter_n_children(model, &parent) == 1)
950 GeanyDocument *other_doc;
951 GtkTreeIter child, pparent;
952 gboolean fold, has_parent;
954 gtk_tree_model_iter_nth_child(model, &child, &parent, 0);
955 gtk_tree_model_get(model, &child, DOCUMENTS_DOCUMENT, &other_doc, -1);
956 if (!other_doc)
958 has_parent = gtk_tree_model_iter_parent(model, &pparent, &parent);
959 tree_reparent(store_openfiles, &child, has_parent ? &pparent : NULL);
960 gtk_tree_store_remove(store_openfiles, &parent);
961 /* Expand if the child node was expanded before the merge. */
962 gtk_tree_model_get(model, &child, DOCUMENTS_FOLD, &fold, -1);
963 if (!fold)
964 expand_iter(&child);
970 static void openfiles_remove(GeanyDocument *doc)
972 if (interface_prefs.openfiles_path_mode != OPENFILES_PATHS_NONE)
973 sidebar_openfiles_remove_iter(store_openfiles, &doc->priv->iter);
974 else
975 gtk_tree_store_remove(store_openfiles, &doc->priv->iter);
979 void sidebar_openfiles_update(GeanyDocument *doc)
981 GtkTreeIter *iter = &doc->priv->iter;
982 gchar *fname;
984 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_FILENAME, &fname, -1);
986 if (utils_str_equal(fname, DOC_FILENAME(doc)))
988 /* just update color and the icon */
989 const GdkColor *color = document_get_status_color(doc);
990 GIcon *icon = doc->file_type->icon;
992 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_COLOR, color, -1);
993 if (icon)
994 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_ICON, icon, -1);
996 else
998 /* path has changed, so remove and re-add */
999 GtkTreeSelection *treesel;
1000 gboolean sel;
1002 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
1003 sel = gtk_tree_selection_iter_is_selected(treesel, &doc->priv->iter);
1004 openfiles_remove(doc);
1006 sidebar_openfiles_add(doc);
1007 if (sel)
1008 gtk_tree_selection_select_iter(treesel, &doc->priv->iter);
1010 g_free(fname);
1014 void sidebar_openfiles_update_all(void)
1016 guint i;
1018 gtk_tree_store_clear(store_openfiles);
1019 foreach_document (i)
1021 sidebar_openfiles_add(documents[i]);
1026 void sidebar_remove_document(GeanyDocument *doc)
1028 openfiles_remove(doc);
1030 if (GTK_IS_WIDGET(doc->priv->tag_tree))
1032 gtk_widget_destroy(doc->priv->tag_tree); /* make GTK release its references, if any */
1033 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
1034 g_object_unref(doc->priv->tag_tree);
1035 doc->priv->tag_tree = NULL;
1040 static void on_hide_sidebar(void)
1042 ui_prefs.sidebar_visible = FALSE;
1043 ui_sidebar_show_hide();
1047 static gboolean on_sidebar_display_symbol_list_show(GtkWidget *item)
1049 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
1050 interface_prefs.sidebar_symbol_visible);
1051 return FALSE;
1055 static gboolean on_sidebar_display_open_files_show(GtkWidget *item)
1057 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
1058 interface_prefs.sidebar_openfiles_visible);
1059 return FALSE;
1063 void sidebar_add_common_menu_items(GtkMenu *menu)
1065 GtkWidget *item;
1067 item = gtk_separator_menu_item_new();
1068 gtk_widget_show(item);
1069 gtk_container_add(GTK_CONTAINER(menu), item);
1071 item = gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
1072 gtk_container_add(GTK_CONTAINER(menu), item);
1073 g_signal_connect(item, "draw", G_CALLBACK(on_sidebar_display_symbol_list_show), NULL);
1074 gtk_widget_show(item);
1075 g_signal_connect(item, "activate",
1076 G_CALLBACK(on_list_symbol_activate), NULL);
1078 item = gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
1079 gtk_container_add(GTK_CONTAINER(menu), item);
1080 g_signal_connect(item, "draw", G_CALLBACK(on_sidebar_display_open_files_show), NULL);
1081 gtk_widget_show(item);
1082 g_signal_connect(item, "activate",
1083 G_CALLBACK(on_list_document_activate), NULL);
1085 item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
1086 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
1087 gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
1088 gtk_widget_show(item);
1089 gtk_container_add(GTK_CONTAINER(menu), item);
1090 g_signal_connect(item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
1094 static void on_openfiles_show_paths_activate(GtkCheckMenuItem *item, gpointer user_data)
1096 interface_prefs.openfiles_path_mode = GPOINTER_TO_INT(user_data);
1097 sidebar_openfiles_update_all();
1098 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv.tree_openfiles));
1099 sidebar_select_openfiles_item(document_get_current());
1103 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data)
1105 interface_prefs.sidebar_openfiles_visible = gtk_check_menu_item_get_active(item);
1106 ui_sidebar_show_hide();
1107 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1111 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data)
1113 interface_prefs.sidebar_symbol_visible = gtk_check_menu_item_get_active(item);
1114 ui_sidebar_show_hide();
1115 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1119 static void on_find_in_files(GtkMenuItem *menuitem, gpointer user_data)
1121 GtkTreeSelection *treesel;
1122 GtkTreeIter iter;
1123 GtkTreeModel *model;
1124 GeanyDocument *doc;
1125 gchar *dir;
1127 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
1128 if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
1129 return;
1130 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
1132 if (!doc)
1134 gtk_tree_model_get(model, &iter, DOCUMENTS_FILENAME, &dir, -1);
1136 else
1137 dir = g_path_get_dirname(DOC_FILENAME(doc));
1139 search_show_find_in_files_dialog(dir);
1140 g_free(dir);
1144 static void on_openfiles_expand_collapse(GtkMenuItem *menuitem, gpointer user_data)
1146 gboolean expand = GPOINTER_TO_INT(user_data);
1148 if (expand)
1149 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv.tree_openfiles));
1150 else
1151 gtk_tree_view_collapse_all(GTK_TREE_VIEW(tv.tree_openfiles));
1155 static void create_show_paths_popup_menu(void)
1157 GSList *group = NULL;
1158 const gchar *items[OPENFILES_PATHS_COUNT] = {
1159 [OPENFILES_PATHS_NONE] = _("D_ocuments Only"),
1160 [OPENFILES_PATHS_LIST] = _("Show _Paths"),
1161 [OPENFILES_PATHS_TREE] = _("Show _Tree")
1164 for (guint i = 0; i < G_N_ELEMENTS(items); i++)
1166 GtkWidget *w = gtk_radio_menu_item_new_with_mnemonic(group, items[i]);
1167 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(w));
1168 gtk_widget_show(w);
1169 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), w);
1170 g_signal_connect(w, "activate",
1171 G_CALLBACK(on_openfiles_show_paths_activate), GINT_TO_POINTER(i));
1172 doc_items.show_paths[i] = w;
1177 static void create_openfiles_popup_menu(void)
1179 GtkWidget *item;
1181 openfiles_popup_menu = gtk_menu_new();
1183 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL);
1184 gtk_widget_show(item);
1185 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1186 g_signal_connect(item, "activate",
1187 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
1188 doc_items.close = item;
1190 item = gtk_separator_menu_item_new();
1191 gtk_widget_show(item);
1192 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1194 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, NULL);
1195 gtk_widget_show(item);
1196 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1197 g_signal_connect(item, "activate",
1198 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_SAVE));
1199 doc_items.save = item;
1201 item = gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
1202 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
1203 gtk_image_new_from_stock(GTK_STOCK_REVERT_TO_SAVED, GTK_ICON_SIZE_MENU));
1204 gtk_widget_show(item);
1205 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1206 g_signal_connect(item, "activate",
1207 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD));
1208 doc_items.reload = item;
1210 item = gtk_separator_menu_item_new();
1211 gtk_widget_show(item);
1212 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1214 item = ui_image_menu_item_new(GTK_STOCK_FIND, _("_Find in Files..."));
1215 gtk_widget_show(item);
1216 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1217 g_signal_connect(item, "activate", G_CALLBACK(on_find_in_files), NULL);
1218 doc_items.find_in_files = item;
1220 item = gtk_separator_menu_item_new();
1221 gtk_widget_show(item);
1222 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1224 create_show_paths_popup_menu();
1226 item = gtk_separator_menu_item_new();
1227 gtk_widget_show(item);
1228 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
1230 doc_items.expand_all = ui_image_menu_item_new(GTK_STOCK_ADD, _("_Expand All"));
1231 gtk_widget_show(doc_items.expand_all);
1232 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.expand_all);
1233 g_signal_connect(doc_items.expand_all, "activate",
1234 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(TRUE));
1236 doc_items.collapse_all = ui_image_menu_item_new(GTK_STOCK_REMOVE, _("_Collapse All"));
1237 gtk_widget_show(doc_items.collapse_all);
1238 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.collapse_all);
1239 g_signal_connect(doc_items.collapse_all, "activate",
1240 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(FALSE));
1242 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu));
1246 /* compares the given data with the doc pointer from the selected row of openfiles
1247 * treeview, in case of a match the row is selected and TRUE is returned
1248 * (called indirectly from gtk_tree_model_foreach()) */
1249 static gboolean tree_model_find_node(GtkTreeModel *model,
1250 GtkTreePath *path,
1251 GtkTreeIter *iter,
1252 gpointer data)
1254 GeanyDocument *doc;
1256 gtk_tree_model_get(model, iter, DOCUMENTS_DOCUMENT, &doc, -1);
1257 if (doc == data)
1259 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tv.tree_openfiles), path);
1260 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv.tree_openfiles), path, NULL, FALSE);
1261 return TRUE;
1264 return FALSE;
1268 void sidebar_select_openfiles_item(GeanyDocument *doc)
1270 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles), tree_model_find_node, doc);
1274 /* callbacks */
1276 static void document_action(GeanyDocument *doc, gint action)
1278 if (! DOC_VALID(doc))
1279 return;
1281 switch (action)
1283 case OPENFILES_ACTION_REMOVE:
1285 document_close(doc);
1286 break;
1288 case OPENFILES_ACTION_SAVE:
1290 document_save_file(doc, FALSE);
1291 break;
1293 case OPENFILES_ACTION_RELOAD:
1295 document_reload_prompt(doc, NULL);
1296 break;
1302 static void on_openfiles_document_action_apply(GtkTreeModel *model, GtkTreeIter iter, gint action)
1304 GeanyDocument *doc;
1305 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
1306 if (doc)
1308 document_action(doc, action);
1310 else
1312 /* parent item selected */
1313 GtkTreeIter child;
1314 gint i = gtk_tree_model_iter_n_children(model, &iter) - 1;
1316 while (i >= 0 && gtk_tree_model_iter_nth_child(model, &child, &iter, i))
1318 on_openfiles_document_action_apply(model, child, action);
1319 i--;
1325 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data)
1327 GtkTreeIter iter;
1328 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
1329 GtkTreeModel *model;
1330 gint action = GPOINTER_TO_INT(user_data);
1332 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1333 on_openfiles_document_action_apply(model, iter, action);
1337 static void change_focus_to_editor(GeanyDocument *doc, GtkWidget *source_widget)
1339 if (may_steal_focus)
1340 document_try_focus(doc, source_widget);
1341 may_steal_focus = FALSE;
1345 static gboolean openfiles_go_to_selection(GtkTreeSelection *selection, guint keyval)
1347 GtkTreeIter iter;
1348 GtkTreeModel *model;
1349 GeanyDocument *doc = NULL;
1351 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
1352 if (gtk_tree_selection_get_selected(selection, &model, &iter) && ! ignore_callback)
1354 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
1355 if (! doc)
1356 return FALSE; /* parent */
1358 /* switch to the doc and grab the focus */
1359 document_show_tab(doc);
1360 if (keyval != GDK_KEY_space)
1361 change_focus_to_editor(doc, tv.tree_openfiles);
1363 return FALSE;
1367 static gboolean taglist_go_to_selection(GtkTreeSelection *selection, guint keyval, guint state)
1369 GtkTreeIter iter;
1370 GtkTreeModel *model;
1371 gint line = 0;
1372 gboolean handled = TRUE;
1374 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1376 TMTag *tag;
1378 gtk_tree_model_get(model, &iter, SYMBOLS_COLUMN_TAG, &tag, -1);
1379 if (! tag)
1380 return FALSE;
1382 line = tag->line;
1383 if (line > 0)
1385 GeanyDocument *doc = document_get_current();
1387 if (doc != NULL)
1389 navqueue_goto_line(doc, doc, line);
1390 state = keybindings_get_modifiers(state);
1391 if (keyval != GDK_KEY_space && ! (state & GEANY_PRIMARY_MOD_MASK))
1392 change_focus_to_editor(doc, NULL);
1393 else
1394 handled = FALSE;
1397 tm_tag_unref(tag);
1399 return handled;
1403 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
1404 gpointer user_data)
1406 may_steal_focus = FALSE;
1407 if (ui_is_keyval_enter_or_return(event->keyval) || event->keyval == GDK_KEY_space)
1409 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
1410 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1411 may_steal_focus = TRUE;
1413 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
1414 * doing so will prevent further handlers to be run in most cases, but the only one is our
1415 * own, so guess it's fine. */
1416 if (widget_class->key_press_event)
1417 widget_class->key_press_event(widget, event);
1419 if (widget == tv.tree_openfiles) /* tag and doc list have separate handlers */
1420 openfiles_go_to_selection(selection, event->keyval);
1421 else
1422 taglist_go_to_selection(selection, event->keyval, event->state);
1424 return TRUE;
1426 return FALSE;
1430 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
1431 G_GNUC_UNUSED gpointer user_data)
1433 GtkTreeSelection *selection;
1434 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
1435 gboolean handled = FALSE;
1437 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
1438 * doing so will prevent further handlers to be run in most cases, but the only one is our own,
1439 * so guess it's fine. */
1440 if (widget_class->button_press_event)
1441 handled = widget_class->button_press_event(widget, event);
1443 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
1444 may_steal_focus = TRUE;
1446 if (event->type == GDK_2BUTTON_PRESS)
1447 { /* double click on parent node(section) expands/collapses it */
1448 GtkTreeModel *model;
1449 GtkTreeIter iter;
1451 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1453 if (gtk_tree_model_iter_has_child(model, &iter))
1455 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1457 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
1458 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget), path);
1459 else
1460 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget), path, FALSE);
1462 gtk_tree_path_free(path);
1463 return TRUE;
1467 else if (event->button == 1)
1468 { /* allow reclicking of taglist treeview item */
1469 if (widget == tv.tree_openfiles)
1471 openfiles_go_to_selection(selection, 0);
1472 handled = TRUE;
1474 else
1475 handled = taglist_go_to_selection(selection, 0, event->state);
1477 else if (event->button == 2)
1479 if (widget == tv.tree_openfiles)
1480 on_openfiles_document_action(NULL, GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
1482 else if (event->button == 3)
1484 if (widget == tv.tree_openfiles)
1486 if (!openfiles_popup_menu)
1487 create_openfiles_popup_menu();
1489 /* update menu item sensitivity */
1490 documents_menu_update(selection);
1491 gtk_menu_popup(GTK_MENU(openfiles_popup_menu), NULL, NULL, NULL, NULL,
1492 event->button, event->time);
1494 else
1496 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
1497 event->button, event->time);
1499 handled = TRUE;
1501 return handled;
1505 static void documents_menu_update(GtkTreeSelection *selection)
1507 GtkTreeModel *model;
1508 GtkTreeIter iter;
1509 gboolean sel, path;
1510 gchar *shortname = NULL;
1511 GeanyDocument *doc = NULL;
1513 /* maybe no selection e.g. if ctrl-click deselected */
1514 sel = gtk_tree_selection_get_selected(selection, &model, &iter);
1515 if (sel)
1517 gtk_tree_model_get(model, &iter,
1518 DOCUMENTS_DOCUMENT, &doc,
1519 DOCUMENTS_SHORTNAME, &shortname,
1520 -1);
1522 path = !EMPTY(shortname) &&
1523 (g_path_is_absolute(shortname) ||
1524 (app->project && g_str_has_prefix(shortname, app->project->name)));
1526 /* can close all, save all (except shortname), but only reload individually ATM */
1527 gtk_widget_set_sensitive(doc_items.close, sel);
1528 gtk_widget_set_sensitive(doc_items.save, (doc && doc->real_path) || path);
1529 gtk_widget_set_sensitive(doc_items.reload, doc && doc->real_path);
1530 gtk_widget_set_sensitive(doc_items.find_in_files, sel);
1531 g_free(shortname);
1533 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths[interface_prefs.openfiles_path_mode]), TRUE);
1534 gtk_widget_set_sensitive(doc_items.expand_all, interface_prefs.openfiles_path_mode);
1535 gtk_widget_set_sensitive(doc_items.collapse_all, interface_prefs.openfiles_path_mode);
1539 static StashGroup *stash_group = NULL;
1541 static void on_load_settings(void)
1543 if (interface_prefs.openfiles_path_mode < 0
1544 || interface_prefs.openfiles_path_mode >= OPENFILES_PATHS_COUNT)
1545 interface_prefs.openfiles_path_mode = OPENFILES_PATHS_TREE;
1547 tag_window = ui_lookup_widget(main_widgets.window, "scrolledwindow2");
1549 prepare_openfiles();
1550 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
1551 stash_group_display(stash_group, NULL);
1552 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1556 static void on_save_settings(void)
1558 stash_group_update(stash_group, NULL);
1559 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1563 static void on_sidebar_switch_page(GtkNotebook *notebook,
1564 gpointer page, guint page_num, gpointer user_data)
1566 if (page_num == TREEVIEW_SYMBOL)
1567 sidebar_update_tag_list(document_get_current(), FALSE);
1571 void sidebar_init(void)
1573 StashGroup *group;
1575 group = stash_group_new(PACKAGE);
1576 stash_group_add_widget_property(group, &ui_prefs.sidebar_page, "sidebar_page", GINT_TO_POINTER(0),
1577 main_widgets.sidebar_notebook, "page", 0);
1578 configuration_add_session_group(group, FALSE);
1579 stash_group = group;
1581 /* Delay building documents treeview until sidebar font has been read and prefs are sanitized */
1582 g_signal_connect(geany_object, "load-settings", on_load_settings, NULL);
1583 g_signal_connect(geany_object, "save-settings", on_save_settings, NULL);
1585 g_signal_connect(main_widgets.sidebar_notebook, "page-added",
1586 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1587 g_signal_connect(main_widgets.sidebar_notebook, "page-removed",
1588 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1589 /* tabs may have changed when sidebar is reshown */
1590 g_signal_connect(main_widgets.sidebar_notebook, "show",
1591 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1592 g_signal_connect_after(main_widgets.sidebar_notebook, "switch-page",
1593 G_CALLBACK(on_sidebar_switch_page), NULL);
1596 #define WIDGET(w) w && GTK_IS_WIDGET(w)
1598 void sidebar_finalize(void)
1600 if (WIDGET(tv.default_tag_tree))
1602 gtk_widget_destroy(tv.default_tag_tree); /* make GTK release its references, if any... */
1603 g_object_unref(tv.default_tag_tree); /* ...and release our own */
1605 if (WIDGET(tv.popup_taglist))
1606 gtk_widget_destroy(tv.popup_taglist);
1607 if (WIDGET(openfiles_popup_menu))
1608 gtk_widget_destroy(openfiles_popup_menu);
1612 void sidebar_focus_openfiles_tab(void)
1614 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_openfiles_visible)
1616 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1618 gtk_notebook_set_current_page(notebook, TREEVIEW_OPENFILES);
1619 gtk_widget_grab_focus(tv.tree_openfiles);
1624 void sidebar_focus_symbols_tab(void)
1626 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_symbol_visible)
1628 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1629 GtkWidget *symbol_list_scrollwin = gtk_notebook_get_nth_page(notebook, TREEVIEW_SYMBOL);
1631 gtk_notebook_set_current_page(notebook, TREEVIEW_SYMBOL);
1632 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin)));
1637 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
1638 guint page_num, gpointer data)
1640 gint tabs = gtk_notebook_get_n_pages(notebook);
1642 if (interface_prefs.sidebar_symbol_visible == FALSE)
1643 tabs--;
1644 if (interface_prefs.sidebar_openfiles_visible == FALSE)
1645 tabs--;
1647 gtk_notebook_set_show_tabs(notebook, (tabs > 1));