Fix several tooltips to properly use plain text instead of markup
[geany-mirror.git] / src / sidebar.c
blob7a7e26a4b8f6cd1b58ca2dc946aba09690dcc2b9
1 /*
2 * sidebar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 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 along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * Sidebar related code for the Symbol list and Open files GtkTreeViews.
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
30 #include "sidebar.h"
32 #include "app.h"
33 #include "callbacks.h" /* FIXME: for ignore_callback */
34 #include "documentprivate.h"
35 #include "filetypesprivate.h"
36 #include "geanyobject.h"
37 #include "keyfile.h"
38 #include "navqueue.h"
39 #include "stash.h"
40 #include "support.h"
41 #include "symbols.h"
42 #include "ui_utils.h"
43 #include "utils.h"
44 #include "keybindings.h"
46 #include <string.h>
48 #include <gdk/gdkkeysyms.h>
51 SidebarTreeviews tv = {NULL, NULL, NULL};
52 /* while typeahead searching, editor should not get focus */
53 static gboolean may_steal_focus = FALSE;
55 static struct
57 GtkWidget *close;
58 GtkWidget *save;
59 GtkWidget *reload;
60 GtkWidget *show_paths;
61 GtkWidget *find_in_files;
62 GtkWidget *expand_all;
63 GtkWidget *collapse_all;
65 doc_items = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
67 enum
69 TREEVIEW_SYMBOL = 0,
70 TREEVIEW_OPENFILES
73 enum
75 OPENFILES_ACTION_REMOVE = 0,
76 OPENFILES_ACTION_SAVE,
77 OPENFILES_ACTION_RELOAD
80 /* documents tree model columns */
81 enum
83 DOCUMENTS_ICON,
84 DOCUMENTS_SHORTNAME, /* dirname for parents, basename for children */
85 DOCUMENTS_DOCUMENT,
86 DOCUMENTS_COLOR,
87 DOCUMENTS_FILENAME /* full filename */
90 static GtkTreeStore *store_openfiles;
91 static GtkWidget *openfiles_popup_menu;
92 static gboolean documents_show_paths;
93 static GtkWidget *tag_window; /* scrolled window that holds the symbol list GtkTreeView */
95 /* callback prototypes */
96 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data);
97 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
98 gpointer user_data);
99 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
100 gpointer user_data);
101 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data);
102 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data);
103 static void documents_menu_update(GtkTreeSelection *selection);
104 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
105 guint page_num, gpointer data);
108 /* the prepare_* functions are document-related, but I think they fit better here than in document.c */
109 static void prepare_taglist(GtkWidget *tree, GtkTreeStore *store)
111 GtkCellRenderer *text_renderer, *icon_renderer;
112 GtkTreeViewColumn *column;
113 GtkTreeSelection *selection;
115 text_renderer = gtk_cell_renderer_text_new();
116 icon_renderer = gtk_cell_renderer_pixbuf_new();
117 column = gtk_tree_view_column_new();
119 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
120 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", SYMBOLS_COLUMN_ICON, NULL);
121 g_object_set(icon_renderer, "xalign", 0.0, NULL);
123 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
124 gtk_tree_view_column_set_attributes(column, text_renderer, "text", SYMBOLS_COLUMN_NAME, NULL);
125 g_object_set(text_renderer, "yalign", 0.5, NULL);
126 gtk_tree_view_column_set_title(column, _("Symbols"));
128 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
129 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
131 ui_widget_modify_font_from_string(tree, interface_prefs.tagbar_font);
133 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
134 g_object_unref(store);
136 g_signal_connect(tree, "button-press-event",
137 G_CALLBACK(sidebar_button_press_cb), NULL);
138 g_signal_connect(tree, "key-press-event",
139 G_CALLBACK(sidebar_key_press_cb), NULL);
141 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(tree), interface_prefs.show_symbol_list_expanders);
142 if (! interface_prefs.show_symbol_list_expanders)
143 gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(tree), 10);
144 /* Tooltips */
145 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tree), SYMBOLS_COLUMN_TOOLTIP);
147 /* selection handling */
148 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
149 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
150 /* callback for changed selection not necessary, will be handled by button-press-event */
154 static gboolean
155 on_default_tag_tree_button_press_event(GtkWidget *widget, GdkEventButton *event,
156 gpointer user_data)
158 if (event->button == 3)
160 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
161 event->button, event->time);
162 return TRUE;
164 return FALSE;
168 static void create_default_tag_tree(void)
170 GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(tag_window);
171 GtkWidget *label;
173 /* default_tag_tree is a GtkViewPort with a GtkLabel inside it */
174 tv.default_tag_tree = gtk_viewport_new(
175 gtk_scrolled_window_get_hadjustment(scrolled_window),
176 gtk_scrolled_window_get_vadjustment(scrolled_window));
177 label = gtk_label_new(_("No tags 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 /* changes the tree view to the given one, trying not to do useless changes */
195 #define CHANGE_TREE(new_child) \
196 G_STMT_START { \
197 /* only change the tag tree if it's actually not the same (to avoid flickering) and if
198 * it's the one of the current document (to avoid problems when e.g. reloading
199 * configuration files */ \
200 if (child != new_child && doc == document_get_current()) \
202 if (child) \
203 gtk_container_remove(GTK_CONTAINER(tag_window), child); \
204 gtk_container_add(GTK_CONTAINER(tag_window), new_child); \
206 } G_STMT_END
208 if (tv.default_tag_tree == NULL)
209 create_default_tag_tree();
211 /* show default empty tag tree if there are no tags */
212 if (doc == NULL || doc->file_type == NULL || ! filetype_has_tags(doc->file_type))
214 CHANGE_TREE(tv.default_tag_tree);
215 return;
218 if (update)
219 { /* updating the tag list in the left tag window */
220 if (doc->priv->tag_tree == NULL)
222 doc->priv->tag_store = gtk_tree_store_new(
223 SYMBOLS_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, TM_TYPE_TAG, G_TYPE_STRING);
224 doc->priv->tag_tree = gtk_tree_view_new();
225 prepare_taglist(doc->priv->tag_tree, doc->priv->tag_store);
226 gtk_widget_show(doc->priv->tag_tree);
227 g_object_ref((gpointer)doc->priv->tag_tree); /* to hold it after removing */
230 doc->has_tags = symbols_recreate_tag_list(doc, SYMBOLS_SORT_USE_PREVIOUS);
233 if (doc->has_tags)
235 CHANGE_TREE(doc->priv->tag_tree);
237 else
239 CHANGE_TREE(tv.default_tag_tree);
242 #undef CHANGE_TREE
246 /* cleverly sorts documents by their short name */
247 static gint documents_sort_func(GtkTreeModel *model, GtkTreeIter *iter_a,
248 GtkTreeIter *iter_b, gpointer data)
250 gchar *key_a, *key_b;
251 gchar *name_a, *name_b;
252 gint cmp;
254 gtk_tree_model_get(model, iter_a, DOCUMENTS_SHORTNAME, &name_a, -1);
255 key_a = g_utf8_collate_key_for_filename(name_a, -1);
256 g_free(name_a);
257 gtk_tree_model_get(model, iter_b, DOCUMENTS_SHORTNAME, &name_b, -1);
258 key_b = g_utf8_collate_key_for_filename(name_b, -1);
259 g_free(name_b);
260 cmp = strcmp(key_a, key_b);
261 g_free(key_b);
262 g_free(key_a);
264 return cmp;
268 /* does some preparing things to the open files list widget */
269 static void prepare_openfiles(void)
271 GtkCellRenderer *icon_renderer;
272 GtkCellRenderer *text_renderer;
273 GtkTreeViewColumn *column;
274 GtkTreeSelection *selection;
275 GtkTreeSortable *sortable;
277 tv.tree_openfiles = ui_lookup_widget(main_widgets.window, "treeview6");
279 /* store the icon and the short filename to show, and the index as reference,
280 * the colour (black/red/green) and the full name for the tooltip */
281 store_openfiles = gtk_tree_store_new(5, G_TYPE_ICON, G_TYPE_STRING,
282 G_TYPE_POINTER, GDK_TYPE_COLOR, G_TYPE_STRING);
283 gtk_tree_view_set_model(GTK_TREE_VIEW(tv.tree_openfiles), GTK_TREE_MODEL(store_openfiles));
285 /* set policy settings for the scolledwindow around the treeview again, because glade
286 * doesn't keep the settings */
287 gtk_scrolled_window_set_policy(
288 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets.window, "scrolledwindow7")),
289 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
291 icon_renderer = gtk_cell_renderer_pixbuf_new();
292 g_object_set(icon_renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
293 text_renderer = gtk_cell_renderer_text_new();
294 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
295 column = gtk_tree_view_column_new();
296 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
297 gtk_tree_view_column_set_attributes(column, icon_renderer, "gicon", DOCUMENTS_ICON, NULL);
298 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
299 gtk_tree_view_column_set_attributes(column, text_renderer, "text", DOCUMENTS_SHORTNAME,
300 "foreground-gdk", DOCUMENTS_COLOR, NULL);
301 gtk_tree_view_append_column(GTK_TREE_VIEW(tv.tree_openfiles), column);
302 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv.tree_openfiles), FALSE);
304 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv.tree_openfiles),
305 DOCUMENTS_SHORTNAME);
307 /* sort opened filenames in the store_openfiles treeview */
308 sortable = GTK_TREE_SORTABLE(GTK_TREE_MODEL(store_openfiles));
309 gtk_tree_sortable_set_sort_func(sortable, DOCUMENTS_SHORTNAME, documents_sort_func, NULL, NULL);
310 gtk_tree_sortable_set_sort_column_id(sortable, DOCUMENTS_SHORTNAME, GTK_SORT_ASCENDING);
312 ui_widget_modify_font_from_string(tv.tree_openfiles, interface_prefs.tagbar_font);
314 /* tooltips */
315 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tv.tree_openfiles), DOCUMENTS_FILENAME);
317 /* selection handling */
318 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
319 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
320 g_object_unref(store_openfiles);
322 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "button-press-event",
323 G_CALLBACK(sidebar_button_press_cb), NULL);
324 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "key-press-event",
325 G_CALLBACK(sidebar_key_press_cb), NULL);
329 /* iter should be toplevel */
330 static gboolean find_tree_iter_dir(GtkTreeIter *iter, const gchar *dir)
332 GeanyDocument *doc;
333 gchar *name;
334 gboolean result;
336 if (utils_str_equal(dir, "."))
337 dir = GEANY_STRING_UNTITLED;
339 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
340 g_return_val_if_fail(!doc, FALSE);
342 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_SHORTNAME, &name, -1);
344 result = utils_filenamecmp(name, dir) == 0;
345 g_free(name);
347 return result;
351 static gboolean utils_filename_has_prefix(const gchar *str, const gchar *prefix)
353 gchar *head = g_strndup(str, strlen(prefix));
354 gboolean ret = utils_filenamecmp(head, prefix) == 0;
356 g_free(head);
357 return ret;
361 static gchar *get_doc_folder(const gchar *path)
363 gchar *tmp_dirname = g_strdup(path);
364 gchar *project_base_path;
365 gchar *dirname = NULL;
366 const gchar *home_dir = g_get_home_dir();
367 const gchar *rest;
369 /* replace the project base path with the project name */
370 project_base_path = project_get_base_path();
372 if (project_base_path != NULL)
374 gsize len = strlen(project_base_path);
376 /* remove trailing separator so we can match base path exactly */
377 if (project_base_path[len-1] == G_DIR_SEPARATOR)
378 project_base_path[--len] = '\0';
380 /* check whether the dir name matches or uses the project base path */
381 if (utils_filename_has_prefix(tmp_dirname, project_base_path))
383 rest = tmp_dirname + len;
384 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
386 dirname = g_strdup_printf("%s%s", app->project->name, rest);
389 g_free(project_base_path);
391 if (dirname == NULL)
393 dirname = tmp_dirname;
395 /* If matches home dir, replace with tilde */
396 if (!EMPTY(home_dir) && utils_filename_has_prefix(dirname, home_dir))
398 rest = dirname + strlen(home_dir);
399 if (*rest == G_DIR_SEPARATOR || *rest == '\0')
401 dirname = g_strdup_printf("~%s", rest);
402 g_free(tmp_dirname);
406 else
407 g_free(tmp_dirname);
409 return dirname;
413 static GtkTreeIter *get_doc_parent(GeanyDocument *doc)
415 gchar *path;
416 gchar *dirname = NULL;
417 static GtkTreeIter parent;
418 GtkTreeModel *model = GTK_TREE_MODEL(store_openfiles);
419 static GIcon *dir_icon = NULL;
421 if (!documents_show_paths)
422 return NULL;
424 path = g_path_get_dirname(DOC_FILENAME(doc));
425 dirname = get_doc_folder(path);
427 if (gtk_tree_model_get_iter_first(model, &parent))
431 if (find_tree_iter_dir(&parent, dirname))
433 g_free(dirname);
434 g_free(path);
435 return &parent;
438 while (gtk_tree_model_iter_next(model, &parent));
440 /* no match, add dir parent */
441 if (!dir_icon)
442 dir_icon = ui_get_mime_icon("inode/directory");
444 gtk_tree_store_append(store_openfiles, &parent, NULL);
445 gtk_tree_store_set(store_openfiles, &parent, DOCUMENTS_ICON, dir_icon,
446 DOCUMENTS_FILENAME, path,
447 DOCUMENTS_SHORTNAME, doc->file_name ? dirname : GEANY_STRING_UNTITLED, -1);
449 g_free(dirname);
450 g_free(path);
451 return &parent;
455 /* Also sets doc->priv->iter.
456 * This is called recursively in sidebar_openfiles_update_all(). */
457 void sidebar_openfiles_add(GeanyDocument *doc)
459 GtkTreeIter *iter = &doc->priv->iter;
460 GtkTreeIter *parent = get_doc_parent(doc);
461 gchar *basename;
462 const GdkColor *color = document_get_status_color(doc);
463 static GIcon *file_icon = NULL;
465 gtk_tree_store_append(store_openfiles, iter, parent);
467 /* check if new parent */
468 if (parent && gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), parent) == 1)
470 GtkTreePath *path;
472 /* expand parent */
473 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), parent);
474 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
475 gtk_tree_path_free(path);
477 if (!file_icon)
478 file_icon = ui_get_mime_icon("text/plain");
480 basename = g_path_get_basename(DOC_FILENAME(doc));
481 gtk_tree_store_set(store_openfiles, iter,
482 DOCUMENTS_ICON, (doc->file_type && doc->file_type->icon) ? doc->file_type->icon : file_icon,
483 DOCUMENTS_SHORTNAME, basename, DOCUMENTS_DOCUMENT, doc, DOCUMENTS_COLOR, color,
484 DOCUMENTS_FILENAME, DOC_FILENAME(doc), -1);
485 g_free(basename);
489 static void openfiles_remove(GeanyDocument *doc)
491 GtkTreeIter *iter = &doc->priv->iter;
492 GtkTreeIter parent;
494 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter) &&
495 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), &parent) == 1)
496 gtk_tree_store_remove(store_openfiles, &parent);
497 else
498 gtk_tree_store_remove(store_openfiles, iter);
502 void sidebar_openfiles_update(GeanyDocument *doc)
504 GtkTreeIter *iter = &doc->priv->iter;
505 gchar *fname;
507 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_FILENAME, &fname, -1);
509 if (utils_str_equal(fname, DOC_FILENAME(doc)))
511 /* just update color and the icon */
512 const GdkColor *color = document_get_status_color(doc);
513 GIcon *icon = doc->file_type->icon;
515 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_COLOR, color, -1);
516 if (icon)
517 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_ICON, icon, -1);
519 else
521 /* path has changed, so remove and re-add */
522 GtkTreeSelection *treesel;
523 gboolean sel;
525 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
526 sel = gtk_tree_selection_iter_is_selected(treesel, &doc->priv->iter);
527 openfiles_remove(doc);
529 sidebar_openfiles_add(doc);
530 if (sel)
531 gtk_tree_selection_select_iter(treesel, &doc->priv->iter);
533 g_free(fname);
537 void sidebar_openfiles_update_all(void)
539 guint i;
541 gtk_tree_store_clear(store_openfiles);
542 foreach_document (i)
544 sidebar_openfiles_add(documents[i]);
549 void sidebar_remove_document(GeanyDocument *doc)
551 openfiles_remove(doc);
553 if (GTK_IS_WIDGET(doc->priv->tag_tree))
555 gtk_widget_destroy(doc->priv->tag_tree); /* make GTK release its references, if any */
556 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
557 g_object_unref(doc->priv->tag_tree);
558 doc->priv->tag_tree = NULL;
563 static void on_hide_sidebar(void)
565 ui_prefs.sidebar_visible = FALSE;
566 ui_sidebar_show_hide();
570 static gboolean on_sidebar_display_symbol_list_show(GtkWidget *item)
572 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
573 interface_prefs.sidebar_symbol_visible);
574 return FALSE;
578 static gboolean on_sidebar_display_open_files_show(GtkWidget *item)
580 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
581 interface_prefs.sidebar_openfiles_visible);
582 return FALSE;
586 void sidebar_add_common_menu_items(GtkMenu *menu)
588 GtkWidget *item;
590 item = gtk_separator_menu_item_new();
591 gtk_widget_show(item);
592 gtk_container_add(GTK_CONTAINER(menu), item);
594 item = gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
595 gtk_container_add(GTK_CONTAINER(menu), item);
596 #if GTK_CHECK_VERSION(3, 0, 0)
597 g_signal_connect(item, "draw", G_CALLBACK(on_sidebar_display_symbol_list_show), NULL);
598 #else
599 g_signal_connect(item, "expose-event",
600 G_CALLBACK(on_sidebar_display_symbol_list_show), NULL);
601 #endif
602 gtk_widget_show(item);
603 g_signal_connect(item, "activate",
604 G_CALLBACK(on_list_symbol_activate), NULL);
606 item = gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
607 gtk_container_add(GTK_CONTAINER(menu), item);
608 #if GTK_CHECK_VERSION(3, 0, 0)
609 g_signal_connect(item, "draw", G_CALLBACK(on_sidebar_display_open_files_show), NULL);
610 #else
611 g_signal_connect(item, "expose-event",
612 G_CALLBACK(on_sidebar_display_open_files_show), NULL);
613 #endif
614 gtk_widget_show(item);
615 g_signal_connect(item, "activate",
616 G_CALLBACK(on_list_document_activate), NULL);
618 item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
619 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
620 gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
621 gtk_widget_show(item);
622 gtk_container_add(GTK_CONTAINER(menu), item);
623 g_signal_connect(item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
627 static void on_openfiles_show_paths_activate(GtkCheckMenuItem *item, gpointer user_data)
629 documents_show_paths = gtk_check_menu_item_get_active(item);
630 sidebar_openfiles_update_all();
634 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data)
636 interface_prefs.sidebar_openfiles_visible = gtk_check_menu_item_get_active(item);
637 ui_sidebar_show_hide();
638 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
642 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data)
644 interface_prefs.sidebar_symbol_visible = gtk_check_menu_item_get_active(item);
645 ui_sidebar_show_hide();
646 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
650 static void on_find_in_files(GtkMenuItem *menuitem, gpointer user_data)
652 GtkTreeSelection *treesel;
653 GtkTreeIter iter;
654 GtkTreeModel *model;
655 GeanyDocument *doc;
656 gchar *dir;
658 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
659 if (!gtk_tree_selection_get_selected(treesel, &model, &iter))
660 return;
661 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
663 if (!doc)
665 gtk_tree_model_get(model, &iter, DOCUMENTS_FILENAME, &dir, -1);
667 else
668 dir = g_path_get_dirname(DOC_FILENAME(doc));
670 search_show_find_in_files_dialog(dir);
671 g_free(dir);
675 static void on_openfiles_expand_collapse(GtkMenuItem *menuitem, gpointer user_data)
677 gboolean expand = GPOINTER_TO_INT(user_data);
679 if (expand)
680 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv.tree_openfiles));
681 else
682 gtk_tree_view_collapse_all(GTK_TREE_VIEW(tv.tree_openfiles));
686 static void create_openfiles_popup_menu(void)
688 GtkWidget *item;
690 openfiles_popup_menu = gtk_menu_new();
692 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE, NULL);
693 gtk_widget_show(item);
694 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
695 g_signal_connect(item, "activate",
696 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
697 doc_items.close = item;
699 item = gtk_separator_menu_item_new();
700 gtk_widget_show(item);
701 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
703 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, NULL);
704 gtk_widget_show(item);
705 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
706 g_signal_connect(item, "activate",
707 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_SAVE));
708 doc_items.save = item;
710 item = gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
711 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
712 gtk_image_new_from_stock(GTK_STOCK_REVERT_TO_SAVED, GTK_ICON_SIZE_MENU));
713 gtk_widget_show(item);
714 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
715 g_signal_connect(item, "activate",
716 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD));
717 doc_items.reload = item;
719 item = gtk_separator_menu_item_new();
720 gtk_widget_show(item);
721 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
723 item = ui_image_menu_item_new(GTK_STOCK_FIND, _("_Find in Files..."));
724 gtk_widget_show(item);
725 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
726 g_signal_connect(item, "activate", G_CALLBACK(on_find_in_files), NULL);
727 doc_items.find_in_files = item;
729 item = gtk_separator_menu_item_new();
730 gtk_widget_show(item);
731 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
733 doc_items.show_paths = gtk_check_menu_item_new_with_mnemonic(_("Show _Paths"));
734 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths), documents_show_paths);
735 gtk_widget_show(doc_items.show_paths);
736 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.show_paths);
737 g_signal_connect(doc_items.show_paths, "activate",
738 G_CALLBACK(on_openfiles_show_paths_activate), NULL);
740 item = gtk_separator_menu_item_new();
741 gtk_widget_show(item);
742 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
744 doc_items.expand_all = ui_image_menu_item_new(GTK_STOCK_ADD, _("_Expand All"));
745 gtk_widget_show(doc_items.expand_all);
746 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.expand_all);
747 g_signal_connect(doc_items.expand_all, "activate",
748 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(TRUE));
750 doc_items.collapse_all = ui_image_menu_item_new(GTK_STOCK_REMOVE, _("_Collapse All"));
751 gtk_widget_show(doc_items.collapse_all);
752 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.collapse_all);
753 g_signal_connect(doc_items.collapse_all, "activate",
754 G_CALLBACK(on_openfiles_expand_collapse), GINT_TO_POINTER(FALSE));
756 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu));
760 static void unfold_parent(GtkTreeIter *iter)
762 GtkTreeIter parent;
763 GtkTreePath *path;
765 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter))
767 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), &parent);
768 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
769 gtk_tree_path_free(path);
774 /* compares the given data with the doc pointer from the selected row of openfiles
775 * treeview, in case of a match the row is selected and TRUE is returned
776 * (called indirectly from gtk_tree_model_foreach()) */
777 static gboolean tree_model_find_node(GtkTreeModel *model, GtkTreePath *path,
778 GtkTreeIter *iter, gpointer data)
780 GeanyDocument *doc;
782 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
784 if (doc == data)
786 /* unfolding also prevents a strange bug where the selection gets stuck on the parent
787 * when it is collapsed and then switching documents */
788 unfold_parent(iter);
789 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv.tree_openfiles), path, NULL, FALSE);
790 return TRUE;
792 else return FALSE;
796 void sidebar_select_openfiles_item(GeanyDocument *doc)
798 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles), tree_model_find_node, doc);
802 /* callbacks */
804 static void document_action(GeanyDocument *doc, gint action)
806 if (! DOC_VALID(doc))
807 return;
809 switch (action)
811 case OPENFILES_ACTION_REMOVE:
813 document_close(doc);
814 break;
816 case OPENFILES_ACTION_SAVE:
818 document_save_file(doc, FALSE);
819 break;
821 case OPENFILES_ACTION_RELOAD:
823 document_reload_prompt(doc, NULL);
824 break;
830 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data)
832 GtkTreeIter iter;
833 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
834 GtkTreeModel *model;
835 GeanyDocument *doc;
836 gint action = GPOINTER_TO_INT(user_data);
838 if (gtk_tree_selection_get_selected(selection, &model, &iter))
840 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
841 if (doc)
843 document_action(doc, action);
845 else
847 /* parent item selected */
848 GtkTreeIter child;
849 gint i = gtk_tree_model_iter_n_children(model, &iter) - 1;
851 while (i >= 0 && gtk_tree_model_iter_nth_child(model, &child, &iter, i))
853 gtk_tree_model_get(model, &child, DOCUMENTS_DOCUMENT, &doc, -1);
855 document_action(doc, action);
856 i--;
863 static void change_focus_to_editor(GeanyDocument *doc, GtkWidget *source_widget)
865 if (may_steal_focus)
866 document_try_focus(doc, source_widget);
867 may_steal_focus = FALSE;
871 static gboolean openfiles_go_to_selection(GtkTreeSelection *selection, guint keyval)
873 GtkTreeIter iter;
874 GtkTreeModel *model;
875 GeanyDocument *doc = NULL;
877 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
878 if (gtk_tree_selection_get_selected(selection, &model, &iter) && ! ignore_callback)
880 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
881 if (! doc)
882 return FALSE; /* parent */
884 /* switch to the doc and grab the focus */
885 document_show_tab(doc);
886 if (keyval != GDK_space)
887 change_focus_to_editor(doc, tv.tree_openfiles);
889 return FALSE;
893 static gboolean taglist_go_to_selection(GtkTreeSelection *selection, guint keyval, guint state)
895 GtkTreeIter iter;
896 GtkTreeModel *model;
897 gint line = 0;
898 gboolean handled = TRUE;
900 if (gtk_tree_selection_get_selected(selection, &model, &iter))
902 TMTag *tag;
904 gtk_tree_model_get(model, &iter, SYMBOLS_COLUMN_TAG, &tag, -1);
905 if (! tag)
906 return FALSE;
908 line = tag->line;
909 if (line > 0)
911 GeanyDocument *doc = document_get_current();
913 if (doc != NULL)
915 navqueue_goto_line(doc, doc, line);
916 state = keybindings_get_modifiers(state);
917 if (keyval != GDK_space && ! (state & GEANY_PRIMARY_MOD_MASK))
918 change_focus_to_editor(doc, NULL);
919 else
920 handled = FALSE;
923 tm_tag_unref(tag);
925 return handled;
929 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
930 gpointer user_data)
932 may_steal_focus = FALSE;
933 if (ui_is_keyval_enter_or_return(event->keyval) || event->keyval == GDK_space)
935 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
936 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
937 may_steal_focus = TRUE;
939 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
940 * doing so will prevent further handlers to be run in most cases, but the only one is our
941 * own, so guess it's fine. */
942 if (widget_class->key_press_event)
943 widget_class->key_press_event(widget, event);
945 if (widget == tv.tree_openfiles) /* tag and doc list have separate handlers */
946 openfiles_go_to_selection(selection, event->keyval);
947 else
948 taglist_go_to_selection(selection, event->keyval, event->state);
950 return TRUE;
952 return FALSE;
956 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
957 G_GNUC_UNUSED gpointer user_data)
959 GtkTreeSelection *selection;
960 GtkWidgetClass *widget_class = GTK_WIDGET_GET_CLASS(widget);
961 gboolean handled = FALSE;
963 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
964 * doing so will prevent further handlers to be run in most cases, but the only one is our own,
965 * so guess it's fine. */
966 if (widget_class->button_press_event)
967 handled = widget_class->button_press_event(widget, event);
969 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
970 may_steal_focus = TRUE;
972 if (event->type == GDK_2BUTTON_PRESS)
973 { /* double click on parent node(section) expands/collapses it */
974 GtkTreeModel *model;
975 GtkTreeIter iter;
977 if (gtk_tree_selection_get_selected(selection, &model, &iter))
979 if (gtk_tree_model_iter_has_child(model, &iter))
981 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
983 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
984 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget), path);
985 else
986 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget), path, FALSE);
988 gtk_tree_path_free(path);
989 return TRUE;
993 else if (event->button == 1)
994 { /* allow reclicking of taglist treeview item */
995 if (widget == tv.tree_openfiles)
997 openfiles_go_to_selection(selection, 0);
998 handled = TRUE;
1000 else
1001 handled = taglist_go_to_selection(selection, 0, event->state);
1003 else if (event->button == 2)
1005 if (widget == tv.tree_openfiles)
1006 on_openfiles_document_action(NULL, GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
1008 else if (event->button == 3)
1010 if (widget == tv.tree_openfiles)
1012 if (!openfiles_popup_menu)
1013 create_openfiles_popup_menu();
1015 /* update menu item sensitivity */
1016 documents_menu_update(selection);
1017 gtk_menu_popup(GTK_MENU(openfiles_popup_menu), NULL, NULL, NULL, NULL,
1018 event->button, event->time);
1020 else
1022 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
1023 event->button, event->time);
1025 handled = TRUE;
1027 return handled;
1031 static void documents_menu_update(GtkTreeSelection *selection)
1033 GtkTreeModel *model;
1034 GtkTreeIter iter;
1035 gboolean sel, path;
1036 gchar *shortname = NULL;
1037 GeanyDocument *doc = NULL;
1039 /* maybe no selection e.g. if ctrl-click deselected */
1040 sel = gtk_tree_selection_get_selected(selection, &model, &iter);
1041 if (sel)
1043 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc,
1044 DOCUMENTS_SHORTNAME, &shortname, -1);
1046 path = !EMPTY(shortname) &&
1047 (g_path_is_absolute(shortname) ||
1048 (app->project && g_str_has_prefix(shortname, app->project->name)));
1050 /* can close all, save all (except shortname), but only reload individually ATM */
1051 gtk_widget_set_sensitive(doc_items.close, sel);
1052 gtk_widget_set_sensitive(doc_items.save, (doc && doc->real_path) || path);
1053 gtk_widget_set_sensitive(doc_items.reload, doc && doc->real_path);
1054 gtk_widget_set_sensitive(doc_items.find_in_files, sel);
1055 g_free(shortname);
1057 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths), documents_show_paths);
1058 gtk_widget_set_sensitive(doc_items.expand_all, documents_show_paths);
1059 gtk_widget_set_sensitive(doc_items.collapse_all, documents_show_paths);
1063 static StashGroup *stash_group = NULL;
1065 static void on_load_settings(void)
1067 tag_window = ui_lookup_widget(main_widgets.window, "scrolledwindow2");
1069 prepare_openfiles();
1070 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
1071 stash_group_display(stash_group, NULL);
1072 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1076 static void on_save_settings(void)
1078 stash_group_update(stash_group, NULL);
1079 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1083 void sidebar_init(void)
1085 StashGroup *group;
1087 group = stash_group_new(PACKAGE);
1088 stash_group_add_boolean(group, &documents_show_paths, "documents_show_paths", TRUE);
1089 stash_group_add_widget_property(group, &ui_prefs.sidebar_page, "sidebar_page", GINT_TO_POINTER(0),
1090 main_widgets.sidebar_notebook, "page", 0);
1091 configuration_add_pref_group(group, FALSE);
1092 stash_group = group;
1094 /* delay building documents treeview until sidebar font has been read */
1095 g_signal_connect(geany_object, "load-settings", on_load_settings, NULL);
1096 g_signal_connect(geany_object, "save-settings", on_save_settings, NULL);
1098 g_signal_connect(main_widgets.sidebar_notebook, "page-added",
1099 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1100 g_signal_connect(main_widgets.sidebar_notebook, "page-removed",
1101 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1102 /* tabs may have changed when sidebar is reshown */
1103 g_signal_connect(main_widgets.sidebar_notebook, "show",
1104 G_CALLBACK(sidebar_tabs_show_hide), NULL);
1106 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
1109 #define WIDGET(w) w && GTK_IS_WIDGET(w)
1111 void sidebar_finalize(void)
1113 if (WIDGET(tv.default_tag_tree))
1115 gtk_widget_destroy(tv.default_tag_tree); /* make GTK release its references, if any... */
1116 g_object_unref(tv.default_tag_tree); /* ...and release our own */
1118 if (WIDGET(tv.popup_taglist))
1119 gtk_widget_destroy(tv.popup_taglist);
1120 if (WIDGET(openfiles_popup_menu))
1121 gtk_widget_destroy(openfiles_popup_menu);
1125 void sidebar_focus_openfiles_tab(void)
1127 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_openfiles_visible)
1129 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1131 gtk_notebook_set_current_page(notebook, TREEVIEW_OPENFILES);
1132 gtk_widget_grab_focus(tv.tree_openfiles);
1137 void sidebar_focus_symbols_tab(void)
1139 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_symbol_visible)
1141 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
1142 GtkWidget *symbol_list_scrollwin = gtk_notebook_get_nth_page(notebook, TREEVIEW_SYMBOL);
1144 gtk_notebook_set_current_page(notebook, TREEVIEW_SYMBOL);
1145 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin)));
1150 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
1151 guint page_num, gpointer data)
1153 gint tabs = gtk_notebook_get_n_pages(notebook);
1155 if (interface_prefs.sidebar_symbol_visible == FALSE)
1156 tabs--;
1157 if (interface_prefs.sidebar_openfiles_visible == FALSE)
1158 tabs--;
1160 gtk_notebook_set_show_tabs(notebook, (tabs > 1));