Realign some macros.
[geany-mirror.git] / src / sidebar.c
blob3cb45a518ccc94e9eebbd0b5d375d01989d6eb32
1 /*
2 * sidebar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $Id$
25 * Sidebar related code for the Symbol list and Open files GtkTreeViews.
28 #include <string.h>
30 #include "geany.h"
31 #include "support.h"
32 #include "callbacks.h"
33 #include "sidebar.h"
34 #include "document.h"
35 #include "editor.h"
36 #include "documentprivate.h"
37 #include "filetypes.h"
38 #include "utils.h"
39 #include "ui_utils.h"
40 #include "symbols.h"
41 #include "navqueue.h"
42 #include "project.h"
43 #include "stash.h"
44 #include "keyfile.h"
45 #include "sciwrappers.h"
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;
61 doc_items = {NULL, NULL, NULL, NULL};
63 static struct
65 GtkTreeSelection *selection;
66 guint keyval;
67 } selection_change = {NULL, 0};
69 enum
71 TREEVIEW_SYMBOL = 0,
72 TREEVIEW_OPENFILES
75 enum
77 OPENFILES_ACTION_REMOVE = 0,
78 OPENFILES_ACTION_SAVE,
79 OPENFILES_ACTION_RELOAD
82 /* documents tree model columns */
83 enum
85 DOCUMENTS_ICON,
86 DOCUMENTS_SHORTNAME, /* dirname for parents, basename for children */
87 DOCUMENTS_DOCUMENT,
88 DOCUMENTS_COLOR,
89 DOCUMENTS_FILENAME /* full filename */
92 static GtkTreeStore *store_openfiles;
93 static GtkWidget *openfiles_popup_menu;
94 static gboolean documents_show_paths;
95 static GtkWidget *tag_window; /* scrolled window that holds the symbol list GtkTreeView */
97 /* callback prototypes */
98 static gboolean on_openfiles_tree_selection_changed(gpointer data);
99 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data);
100 static gboolean on_taglist_tree_selection_changed(gpointer data);
101 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
102 gpointer user_data);
103 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
104 gpointer user_data);
105 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data);
106 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data);
107 static void documents_menu_update(GtkTreeSelection *selection);
108 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
109 guint page_num, gpointer data);
112 /* the prepare_* functions are document-related, but I think they fit better here than in document.c */
113 static void prepare_taglist(GtkWidget *tree, GtkTreeStore *store)
115 GtkCellRenderer *text_renderer, *icon_renderer;
116 GtkTreeViewColumn *column;
117 GtkTreeSelection *selection;
119 text_renderer = gtk_cell_renderer_text_new();
120 icon_renderer = gtk_cell_renderer_pixbuf_new();
121 column = gtk_tree_view_column_new();
123 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
124 gtk_tree_view_column_set_attributes(column, icon_renderer, "pixbuf", SYMBOLS_COLUMN_ICON, NULL);
125 g_object_set(icon_renderer, "xalign", 0.0, NULL);
127 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
128 gtk_tree_view_column_set_attributes(column, text_renderer, "text", SYMBOLS_COLUMN_NAME, NULL);
129 g_object_set(text_renderer, "yalign", 0.5, NULL);
130 gtk_tree_view_column_set_title(column, _("Symbols"));
132 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
133 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
135 ui_widget_modify_font_from_string(tree, interface_prefs.tagbar_font);
137 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
138 g_object_unref(store);
140 g_signal_connect(tree, "button-press-event",
141 G_CALLBACK(sidebar_button_press_cb), NULL);
142 g_signal_connect(tree, "key-press-event",
143 G_CALLBACK(sidebar_key_press_cb), NULL);
145 if (gtk_check_version(2, 12, 0) == NULL)
147 g_object_set(tree, "show-expanders", interface_prefs.show_symbol_list_expanders, NULL);
148 if (! interface_prefs.show_symbol_list_expanders)
149 g_object_set(tree, "level-indentation", 10, NULL);
150 /* Tooltips */
151 g_object_set(tree,
152 "has-tooltip", TRUE,
153 "tooltip-column", SYMBOLS_COLUMN_TOOLTIP, NULL);
156 /* selection handling */
157 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
158 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
159 /* callback for changed selection not necessary, will be handled by button-press-event */
163 static gboolean
164 on_default_tag_tree_button_press_event(GtkWidget *widget, GdkEventButton *event,
165 gpointer user_data)
167 if (event->button == 3)
169 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
170 event->button, event->time);
171 return TRUE;
173 return FALSE;
177 static void create_default_tag_tree(void)
179 GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW(tag_window);
180 GtkWidget *label;
182 /* default_tag_tree is a GtkViewPort with a GtkLabel inside it */
183 tv.default_tag_tree = gtk_viewport_new(
184 gtk_scrolled_window_get_hadjustment(scrolled_window),
185 gtk_scrolled_window_get_vadjustment(scrolled_window));
186 label = gtk_label_new(_("No tags found"));
187 gtk_misc_set_alignment(GTK_MISC(label), 0.1f, 0.01f);
188 gtk_container_add(GTK_CONTAINER(tv.default_tag_tree), label);
189 gtk_widget_show_all(tv.default_tag_tree);
190 g_signal_connect(tv.default_tag_tree, "button-press-event",
191 G_CALLBACK(on_default_tag_tree_button_press_event), NULL);
192 g_object_ref((gpointer)tv.default_tag_tree); /* to hold it after removing */
196 /* update = rescan the tags for doc->filename */
197 void sidebar_update_tag_list(GeanyDocument *doc, gboolean update)
199 if (gtk_bin_get_child(GTK_BIN(tag_window)))
200 gtk_container_remove(GTK_CONTAINER(tag_window), gtk_bin_get_child(GTK_BIN(tag_window)));
202 if (tv.default_tag_tree == NULL)
203 create_default_tag_tree();
205 /* show default empty tag tree if there are no tags */
206 if (doc == NULL || doc->file_type == NULL || ! filetype_has_tags(doc->file_type))
208 gtk_container_add(GTK_CONTAINER(tag_window), tv.default_tag_tree);
209 return;
212 if (update)
213 { /* updating the tag list in the left tag window */
214 if (doc->priv->tag_tree == NULL)
216 doc->priv->tag_store = gtk_tree_store_new(
217 SYMBOLS_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING);
218 doc->priv->tag_tree = gtk_tree_view_new();
219 prepare_taglist(doc->priv->tag_tree, doc->priv->tag_store);
220 gtk_widget_show(doc->priv->tag_tree);
221 g_object_ref((gpointer)doc->priv->tag_tree); /* to hold it after removing */
224 doc->has_tags = symbols_recreate_tag_list(doc, SYMBOLS_SORT_USE_PREVIOUS);
227 if (doc->has_tags)
229 gtk_container_add(GTK_CONTAINER(tag_window), doc->priv->tag_tree);
231 else
233 gtk_container_add(GTK_CONTAINER(tag_window), tv.default_tag_tree);
238 /* does some preparing things to the open files list widget */
239 static void prepare_openfiles(void)
241 GtkCellRenderer *icon_renderer;
242 GtkCellRenderer *text_renderer;
243 GtkTreeViewColumn *column;
244 GtkTreeSelection *selection;
245 GtkTreeSortable *sortable;
247 tv.tree_openfiles = ui_lookup_widget(main_widgets.window, "treeview6");
249 /* store the icon and the short filename to show, and the index as reference,
250 * the colour (black/red/green) and the full name for the tooltip */
251 store_openfiles = gtk_tree_store_new(5, G_TYPE_STRING, G_TYPE_STRING,
252 G_TYPE_POINTER, GDK_TYPE_COLOR, G_TYPE_STRING);
253 gtk_tree_view_set_model(GTK_TREE_VIEW(tv.tree_openfiles), GTK_TREE_MODEL(store_openfiles));
255 /* set policy settings for the scolledwindow around the treeview again, because glade
256 * doesn't keep the settings */
257 gtk_scrolled_window_set_policy(
258 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets.window, "scrolledwindow7")),
259 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
261 icon_renderer = gtk_cell_renderer_pixbuf_new();
262 text_renderer = gtk_cell_renderer_text_new();
263 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL);
264 column = gtk_tree_view_column_new();
265 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
266 gtk_tree_view_column_set_attributes(column, icon_renderer, "stock-id", DOCUMENTS_ICON, NULL);
267 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
268 gtk_tree_view_column_set_attributes(column, text_renderer, "text", DOCUMENTS_SHORTNAME,
269 "foreground-gdk", DOCUMENTS_COLOR, NULL);
270 gtk_tree_view_append_column(GTK_TREE_VIEW(tv.tree_openfiles), column);
271 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv.tree_openfiles), FALSE);
273 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv.tree_openfiles),
274 DOCUMENTS_SHORTNAME);
276 /* sort opened filenames in the store_openfiles treeview */
277 sortable = GTK_TREE_SORTABLE(GTK_TREE_MODEL(store_openfiles));
278 gtk_tree_sortable_set_sort_column_id(sortable, DOCUMENTS_SHORTNAME, GTK_SORT_ASCENDING);
280 ui_widget_modify_font_from_string(tv.tree_openfiles, interface_prefs.tagbar_font);
282 /* GTK 2.12 tooltips */
283 if (gtk_check_version(2, 12, 0) == NULL)
284 g_object_set(tv.tree_openfiles, "has-tooltip", TRUE, "tooltip-column", DOCUMENTS_FILENAME, NULL);
286 /* selection handling */
287 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
288 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
289 g_object_unref(store_openfiles);
291 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "button-press-event",
292 G_CALLBACK(sidebar_button_press_cb), NULL);
293 g_signal_connect(GTK_TREE_VIEW(tv.tree_openfiles), "key-press-event",
294 G_CALLBACK(sidebar_key_press_cb), NULL);
298 /* iter should be toplevel */
299 static gboolean find_tree_iter_dir(GtkTreeIter *iter, const gchar *dir)
301 GeanyDocument *doc;
302 gchar *name;
303 gboolean result;
305 if (utils_str_equal(dir, "."))
306 dir = GEANY_STRING_UNTITLED;
308 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
309 g_return_val_if_fail(!doc, FALSE);
311 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_SHORTNAME, &name, -1);
313 result = utils_str_equal(name, dir);
314 g_free(name);
316 return result;
320 static GtkTreeIter *get_doc_parent(GeanyDocument *doc)
322 gchar *tmp_dirname;
323 gchar *project_base_path;
324 gchar *dirname = NULL;
325 static GtkTreeIter parent;
326 GtkTreeModel *model = GTK_TREE_MODEL(store_openfiles);
328 if (!documents_show_paths)
329 return NULL;
331 tmp_dirname = g_path_get_dirname(DOC_FILENAME(doc));
332 /* replace the project base path with the project name */
333 project_base_path = project_get_base_path();
334 if (project_base_path != NULL)
336 gsize len = strlen(project_base_path);
337 const gchar *rest;
339 /* check whether the dir name matches or uses the project base path */
340 if (!utils_str_equal(project_base_path, tmp_dirname))
341 setptr(project_base_path, g_strconcat(project_base_path, G_DIR_SEPARATOR_S, NULL));
342 if (g_str_has_prefix(tmp_dirname, project_base_path))
344 rest = tmp_dirname + len;
345 dirname = g_strdup_printf("%s%s%s",
346 app->project->name,
347 (*rest != G_DIR_SEPARATOR && *rest != '\0') ? G_DIR_SEPARATOR_S : "",
348 rest);
350 g_free(project_base_path);
352 if (dirname == NULL)
353 dirname = tmp_dirname;
354 else
355 g_free(tmp_dirname);
357 if (gtk_tree_model_get_iter_first(model, &parent))
361 if (find_tree_iter_dir(&parent, dirname))
363 g_free(dirname);
364 return &parent;
367 while (gtk_tree_model_iter_next(model, &parent));
369 /* no match, add dir parent */
370 gtk_tree_store_append(store_openfiles, &parent, NULL);
371 gtk_tree_store_set(store_openfiles, &parent, DOCUMENTS_ICON, GTK_STOCK_DIRECTORY,
372 DOCUMENTS_SHORTNAME, doc->file_name ? dirname : GEANY_STRING_UNTITLED, -1);
374 g_free(dirname);
375 return &parent;
379 /* Also sets doc->priv->iter.
380 * This is called recursively in sidebar_openfiles_update_all(). */
381 void sidebar_openfiles_add(GeanyDocument *doc)
383 GtkTreeIter *iter = &doc->priv->iter;
384 GtkTreeIter *parent = get_doc_parent(doc);
385 gchar *basename;
386 const GdkColor *color = document_get_status_color(doc);
388 gtk_tree_store_append(store_openfiles, iter, parent);
390 /* check if new parent */
391 if (parent && gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), parent) == 1)
393 GtkTreePath *path;
395 /* expand parent */
396 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), parent);
397 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
398 gtk_tree_path_free(path);
400 basename = g_path_get_basename(DOC_FILENAME(doc));
401 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_ICON, GTK_STOCK_FILE,
402 DOCUMENTS_SHORTNAME, basename, DOCUMENTS_DOCUMENT, doc, DOCUMENTS_COLOR, color,
403 DOCUMENTS_FILENAME, DOC_FILENAME(doc), -1);
404 g_free(basename);
408 static void openfiles_remove(GeanyDocument *doc)
410 GtkTreeIter *iter = &doc->priv->iter;
411 GtkTreeIter parent;
413 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter) &&
414 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles), &parent) == 1)
415 gtk_tree_store_remove(store_openfiles, &parent);
416 else
417 gtk_tree_store_remove(store_openfiles, iter);
421 void sidebar_openfiles_update(GeanyDocument *doc)
423 GtkTreeIter *iter = &doc->priv->iter;
424 gchar *fname;
426 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_FILENAME, &fname, -1);
428 if (utils_str_equal(fname, DOC_FILENAME(doc)))
430 /* just update color */
431 const GdkColor *color = document_get_status_color(doc);
433 gtk_tree_store_set(store_openfiles, iter, DOCUMENTS_COLOR, color, -1);
435 else
437 /* path has changed, so remove and re-add */
438 GtkTreeSelection *treesel;
439 gboolean sel;
441 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
442 sel = gtk_tree_selection_iter_is_selected(treesel, &doc->priv->iter);
443 openfiles_remove(doc);
445 sidebar_openfiles_add(doc);
446 if (sel)
447 gtk_tree_selection_select_iter(treesel, &doc->priv->iter);
449 g_free(fname);
453 void sidebar_openfiles_update_all()
455 guint i, page_count;
456 GeanyDocument *doc;
458 gtk_tree_store_clear(store_openfiles);
459 page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
460 for (i = 0; i < page_count; i++)
462 doc = document_get_from_page(i);
463 if (G_UNLIKELY(doc == NULL))
464 continue;
466 sidebar_openfiles_add(doc);
471 void sidebar_remove_document(GeanyDocument *doc)
473 openfiles_remove(doc);
475 if (GTK_IS_WIDGET(doc->priv->tag_tree))
477 gtk_widget_destroy(doc->priv->tag_tree);
478 if (GTK_IS_TREE_VIEW(doc->priv->tag_tree))
480 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
481 g_object_unref((gpointer)doc->priv->tag_tree);
483 doc->priv->tag_tree = NULL;
488 static void on_hide_sidebar(void)
490 ui_prefs.sidebar_visible = FALSE;
491 ui_sidebar_show_hide();
495 static gboolean on_sidebar_display_symbol_list_show(GtkWidget *item)
497 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
498 interface_prefs.sidebar_symbol_visible);
499 return FALSE;
503 static gboolean on_sidebar_display_open_files_show(GtkWidget *item)
505 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
506 interface_prefs.sidebar_openfiles_visible);
507 return FALSE;
511 void sidebar_add_common_menu_items(GtkMenu *menu)
513 GtkWidget *item;
515 item = gtk_separator_menu_item_new();
516 gtk_widget_show(item);
517 gtk_container_add(GTK_CONTAINER(menu), item);
519 item = gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
520 gtk_container_add(GTK_CONTAINER(menu), item);
521 g_signal_connect(item, "expose-event",
522 G_CALLBACK(on_sidebar_display_symbol_list_show), NULL);
523 gtk_widget_show(item);
524 g_signal_connect(item, "activate",
525 G_CALLBACK(on_list_symbol_activate), NULL);
527 item = gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
528 gtk_container_add(GTK_CONTAINER(menu), item);
529 g_signal_connect(item, "expose-event",
530 G_CALLBACK(on_sidebar_display_open_files_show), NULL);
531 gtk_widget_show(item);
532 g_signal_connect(item, "activate",
533 G_CALLBACK(on_list_document_activate), NULL);
535 item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
536 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
537 gtk_image_new_from_stock("gtk-close", GTK_ICON_SIZE_MENU));
538 gtk_widget_show(item);
539 gtk_container_add(GTK_CONTAINER(menu), item);
540 g_signal_connect(item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
544 static void on_openfiles_show_paths_activate(GtkCheckMenuItem *item, gpointer user_data)
546 documents_show_paths = gtk_check_menu_item_get_active(item);
547 sidebar_openfiles_update_all();
551 static void on_list_document_activate(GtkCheckMenuItem *item, gpointer user_data)
553 interface_prefs.sidebar_openfiles_visible = gtk_check_menu_item_get_active(item);
554 ui_sidebar_show_hide();
555 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
559 static void on_list_symbol_activate(GtkCheckMenuItem *item, gpointer user_data)
561 interface_prefs.sidebar_symbol_visible = gtk_check_menu_item_get_active(item);
562 ui_sidebar_show_hide();
563 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
567 static void create_openfiles_popup_menu(void)
569 GtkWidget *item;
571 openfiles_popup_menu = gtk_menu_new();
573 item = gtk_image_menu_item_new_from_stock("gtk-close", NULL);
574 gtk_widget_show(item);
575 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
576 g_signal_connect(item, "activate",
577 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_REMOVE));
578 doc_items.close = item;
580 item = gtk_separator_menu_item_new();
581 gtk_widget_show(item);
582 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
584 item = gtk_image_menu_item_new_from_stock("gtk-save", NULL);
585 gtk_widget_show(item);
586 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
587 g_signal_connect(item, "activate",
588 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_SAVE));
589 doc_items.save = item;
591 item = gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
592 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
593 gtk_image_new_from_stock("gtk-revert-to-saved", GTK_ICON_SIZE_MENU));
594 gtk_widget_show(item);
595 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
596 g_signal_connect(item, "activate",
597 G_CALLBACK(on_openfiles_document_action), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD));
598 doc_items.reload = item;
600 item = gtk_separator_menu_item_new();
601 gtk_widget_show(item);
602 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), item);
604 doc_items.show_paths = gtk_check_menu_item_new_with_mnemonic(_("Show _Paths"));
605 gtk_widget_show(doc_items.show_paths);
606 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu), doc_items.show_paths);
607 g_signal_connect(doc_items.show_paths, "activate",
608 G_CALLBACK(on_openfiles_show_paths_activate), NULL);
610 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu));
614 static void unfold_parent(GtkTreeIter *iter)
616 GtkTreeIter parent;
617 GtkTreePath *path;
619 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles), &parent, iter))
621 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles), &parent);
622 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv.tree_openfiles), path, TRUE);
623 gtk_tree_path_free(path);
628 /* compares the given data with the doc pointer from the selected row of openfiles
629 * treeview, in case of a match the row is selected and TRUE is returned
630 * (called indirectly from gtk_tree_model_foreach()) */
631 static gboolean tree_model_find_node(GtkTreeModel *model, GtkTreePath *path,
632 GtkTreeIter *iter, gpointer data)
634 GeanyDocument *doc;
636 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles), iter, DOCUMENTS_DOCUMENT, &doc, -1);
638 if (doc == data)
640 /* unfolding also prevents a strange bug where the selection gets stuck on the parent
641 * when it is collapsed and then switching documents */
642 unfold_parent(iter);
643 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv.tree_openfiles), path, NULL, FALSE);
644 return TRUE;
646 else return FALSE;
650 void sidebar_select_openfiles_item(GeanyDocument *doc)
652 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles), tree_model_find_node, doc);
656 /* callbacks */
658 static void document_action(GeanyDocument *doc, gint action)
660 if (! DOC_VALID(doc))
661 return;
663 switch (action)
665 case OPENFILES_ACTION_REMOVE:
667 document_close(doc);
668 break;
670 case OPENFILES_ACTION_SAVE:
672 document_save_file(doc, FALSE);
673 break;
675 case OPENFILES_ACTION_RELOAD:
677 on_toolbutton_reload_clicked(NULL, NULL);
678 break;
684 static void on_openfiles_document_action(GtkMenuItem *menuitem, gpointer user_data)
686 GtkTreeIter iter;
687 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv.tree_openfiles));
688 GtkTreeModel *model;
689 GeanyDocument *doc;
690 gint action = GPOINTER_TO_INT(user_data);
692 if (gtk_tree_selection_get_selected(selection, &model, &iter))
694 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
695 if (doc)
697 document_action(doc, action);
699 else
701 /* parent item selected */
702 GtkTreeIter child;
703 gint i = gtk_tree_model_iter_n_children(model, &iter) - 1;
705 while (i >= 0 && gtk_tree_model_iter_nth_child(model, &child, &iter, i))
707 gtk_tree_model_get(model, &child, DOCUMENTS_DOCUMENT, &doc, -1);
709 document_action(doc, action);
710 i--;
717 static void change_focus_to_editor(GeanyDocument *doc, GtkWidget *source_widget)
719 if (may_steal_focus)
720 document_try_focus(doc, source_widget);
721 may_steal_focus = FALSE;
725 static gboolean on_openfiles_tree_selection_changed(gpointer data)
727 GtkTreeIter iter;
728 GtkTreeModel *model;
729 GeanyDocument *doc = NULL;
731 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
732 if (gtk_tree_selection_get_selected(selection_change.selection, &model, &iter) && ! ignore_callback)
734 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc, -1);
735 if (! doc)
736 return FALSE; /* parent */
738 /* switch to the doc and grab the focus */
739 gtk_notebook_set_current_page(GTK_NOTEBOOK(main_widgets.notebook),
740 gtk_notebook_page_num(GTK_NOTEBOOK(main_widgets.notebook),
741 (GtkWidget*) doc->editor->sci));
742 if (selection_change.keyval != GDK_space)
743 change_focus_to_editor(doc, tv.tree_openfiles);
745 return FALSE;
749 static gboolean on_taglist_tree_selection_changed(gpointer data)
751 GtkTreeIter iter;
752 GtkTreeModel *model;
753 gint line = 0;
755 if (gtk_tree_selection_get_selected(selection_change.selection, &model, &iter))
757 const TMTag *tag;
759 gtk_tree_model_get(model, &iter, SYMBOLS_COLUMN_TAG, &tag, -1);
760 if (! tag)
761 return FALSE;
763 line = tag->atts.entry.line;
764 if (line > 0)
766 GeanyDocument *doc = document_get_current();
768 if (doc != NULL)
770 navqueue_goto_line(doc, doc, line);
771 if (selection_change.keyval != GDK_space)
772 change_focus_to_editor(doc, NULL);
776 return FALSE;
780 static void update_selection_change(GtkTreeSelection *selection, guint keyval)
782 selection_change.selection = selection;
783 selection_change.keyval = keyval;
787 static gboolean sidebar_key_press_cb(GtkWidget *widget, GdkEventKey *event,
788 gpointer user_data)
790 may_steal_focus = FALSE;
791 if (ui_is_keyval_enter_or_return(event->keyval) || event->keyval == GDK_space)
793 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
794 may_steal_focus = TRUE;
795 /* delay the query of selection state because this callback is executed before GTK
796 * changes the selection (g_signal_connect_after would be better but it doesn't work) */
797 update_selection_change(selection, event->keyval);
799 if (widget == tv.tree_openfiles) /* tag and doc list have separate handlers */
800 g_idle_add(on_openfiles_tree_selection_changed, NULL);
801 else
802 g_idle_add(on_taglist_tree_selection_changed, NULL);
804 return FALSE;
808 static gboolean sidebar_button_press_cb(GtkWidget *widget, GdkEventButton *event,
809 G_GNUC_UNUSED gpointer user_data)
811 GtkTreeSelection *selection;
813 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
814 may_steal_focus = TRUE;
816 if (event->type == GDK_2BUTTON_PRESS)
817 { /* double click on parent node(section) expands/collapses it */
818 GtkTreeModel *model;
819 GtkTreeIter iter;
821 if (gtk_tree_selection_get_selected(selection, &model, &iter))
823 if (gtk_tree_model_iter_has_child(model, &iter))
825 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
827 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
828 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget), path);
829 else
830 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget), path, FALSE);
832 gtk_tree_path_free(path);
833 return TRUE;
837 else if (event->button == 1)
838 { /* allow reclicking of taglist treeview item */
839 /* delay the query of selection state because this callback is executed before GTK
840 * changes the selection (g_signal_connect_after would be better but it doesn't work) */
841 update_selection_change(selection, 0);
843 if (widget == tv.tree_openfiles)
844 g_idle_add(on_openfiles_tree_selection_changed, NULL);
845 else
846 g_idle_add(on_taglist_tree_selection_changed, NULL);
848 else if (event->button == 3)
850 if (widget == tv.tree_openfiles)
852 if (!openfiles_popup_menu)
853 create_openfiles_popup_menu();
855 /* update menu item sensitivity */
856 documents_menu_update(selection);
857 gtk_menu_popup(GTK_MENU(openfiles_popup_menu), NULL, NULL, NULL, NULL,
858 event->button, event->time);
860 else
862 gtk_menu_popup(GTK_MENU(tv.popup_taglist), NULL, NULL, NULL, NULL,
863 event->button, event->time);
866 return FALSE;
870 static void documents_menu_update(GtkTreeSelection *selection)
872 GtkTreeModel *model;
873 GtkTreeIter iter;
874 gboolean sel, path;
875 gchar *shortname = NULL;
876 GeanyDocument *doc = NULL;
878 /* maybe no selection e.g. if ctrl-click deselected */
879 sel = gtk_tree_selection_get_selected(selection, &model, &iter);
880 if (sel)
882 gtk_tree_model_get(model, &iter, DOCUMENTS_DOCUMENT, &doc,
883 DOCUMENTS_SHORTNAME, &shortname, -1);
885 path = NZV(shortname) &&
886 (g_path_is_absolute(shortname) ||
887 (app->project && g_str_has_prefix(shortname, app->project->name)));
889 /* can close all, save all (except shortname), but only reload individually ATM */
890 gtk_widget_set_sensitive(doc_items.close, sel);
891 gtk_widget_set_sensitive(doc_items.save, (doc && doc->real_path) || path);
892 gtk_widget_set_sensitive(doc_items.reload, doc && doc->real_path);
893 g_free(shortname);
895 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items.show_paths),
896 documents_show_paths);
900 StashGroup *stash_group = NULL;
902 static void on_load_settings(void)
904 tag_window = ui_lookup_widget(main_widgets.window, "scrolledwindow2");
906 prepare_openfiles();
907 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
908 stash_group_display(stash_group, NULL);
909 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
913 static void on_save_settings(void)
915 stash_group_update(stash_group, NULL);
916 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
920 void sidebar_init(void)
922 StashGroup *group;
924 group = stash_group_new(PACKAGE);
925 stash_group_add_boolean(group, &documents_show_paths, "documents_show_paths", TRUE);
926 stash_group_add_widget_property(group, &ui_prefs.sidebar_page, "sidebar_page", GINT_TO_POINTER(0),
927 main_widgets.sidebar_notebook, "page", 0);
928 configuration_add_pref_group(group, FALSE);
929 stash_group = group;
931 /* delay building documents treeview until sidebar font has been read */
932 g_signal_connect(geany_object, "load-settings", on_load_settings, NULL);
933 g_signal_connect(geany_object, "save-settings", on_save_settings, NULL);
936 if (gtk_check_version(2, 10, 0) == NULL)
938 g_signal_connect(main_widgets.sidebar_notebook, "page-added",
939 G_CALLBACK(sidebar_tabs_show_hide), NULL);
941 g_signal_connect(main_widgets.sidebar_notebook, "page-removed",
942 G_CALLBACK(sidebar_tabs_show_hide), NULL);
944 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets.sidebar_notebook), NULL, 0, NULL);
947 #define WIDGET(w) w && GTK_IS_WIDGET(w)
949 void sidebar_finalize(void)
951 if (WIDGET(tv.default_tag_tree))
953 g_object_unref(tv.default_tag_tree);
954 gtk_widget_destroy(tv.default_tag_tree);
956 if (WIDGET(tv.popup_taglist))
957 gtk_widget_destroy(tv.popup_taglist);
958 if (WIDGET(openfiles_popup_menu))
959 gtk_widget_destroy(openfiles_popup_menu);
963 void sidebar_focus_openfiles_tab(void)
965 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_openfiles_visible)
967 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
969 gtk_notebook_set_current_page(notebook, TREEVIEW_OPENFILES);
970 gtk_widget_grab_focus(tv.tree_openfiles);
975 void sidebar_focus_symbols_tab(void)
977 if (ui_prefs.sidebar_visible && interface_prefs.sidebar_symbol_visible)
979 GtkNotebook *notebook = GTK_NOTEBOOK(main_widgets.sidebar_notebook);
980 GtkWidget *symbol_list_scrollwin = gtk_notebook_get_nth_page(notebook, TREEVIEW_SYMBOL);
982 gtk_notebook_set_current_page(notebook, TREEVIEW_SYMBOL);
983 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin)));
988 static void sidebar_tabs_show_hide(GtkNotebook *notebook, GtkWidget *child,
989 guint page_num, gpointer data)
991 if (gtk_check_version(2, 10, 0) == NULL)
993 gint tabs = gtk_notebook_get_n_pages(notebook);
995 if (interface_prefs.sidebar_symbol_visible == FALSE)
996 tabs--;
997 if (interface_prefs.sidebar_openfiles_visible == FALSE)
998 tabs--;
1000 gtk_notebook_set_show_tabs(notebook, (tabs > 1));