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.
33 #include "callbacks.h" /* FIXME: for ignore_callback */
34 #include "documentprivate.h"
35 #include "filetypesprivate.h"
36 #include "geanyobject.h"
44 #include "keybindings.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
;
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
};
75 OPENFILES_ACTION_REMOVE
= 0,
76 OPENFILES_ACTION_SAVE
,
77 OPENFILES_ACTION_RELOAD
80 /* documents tree model columns */
84 DOCUMENTS_SHORTNAME
, /* dirname for parents, basename for children */
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
,
99 static gboolean
sidebar_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
,
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);
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 */
155 on_default_tag_tree_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
,
158 if (event
->button
== 3)
160 gtk_menu_popup(GTK_MENU(tv
.popup_taglist
), NULL
, NULL
, NULL
, NULL
,
161 event
->button
, event
->time
);
168 static void create_default_tag_tree(void)
170 GtkScrolledWindow
*scrolled_window
= GTK_SCROLLED_WINDOW(tag_window
);
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 gtk_viewport_set_shadow_type(GTK_VIEWPORT(tv
.default_tag_tree
), GTK_SHADOW_NONE
);
178 label
= gtk_label_new(_("No tags found"));
179 gtk_misc_set_alignment(GTK_MISC(label
), 0.1f
, 0.01f
);
180 gtk_container_add(GTK_CONTAINER(tv
.default_tag_tree
), label
);
181 gtk_widget_show_all(tv
.default_tag_tree
);
182 g_signal_connect(tv
.default_tag_tree
, "button-press-event",
183 G_CALLBACK(on_default_tag_tree_button_press_event
), NULL
);
184 g_object_ref((gpointer
)tv
.default_tag_tree
); /* to hold it after removing */
188 /* update = rescan the tags for doc->filename */
189 void sidebar_update_tag_list(GeanyDocument
*doc
, gboolean update
)
191 GtkWidget
*child
= gtk_bin_get_child(GTK_BIN(tag_window
));
193 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
196 doc
->priv
->tag_tree_dirty
= TRUE
;
198 if (gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
)) != TREEVIEW_SYMBOL
)
199 return; /* don't bother updating symbol tree if we don't see it */
201 /* changes the tree view to the given one, trying not to do useless changes */
202 #define CHANGE_TREE(new_child) \
204 /* only change the tag tree if it's actually not the same (to avoid flickering) and if
205 * it's the one of the current document (to avoid problems when e.g. reloading
206 * configuration files */ \
207 if (child != new_child && doc == document_get_current()) \
210 gtk_container_remove(GTK_CONTAINER(tag_window), child); \
211 gtk_container_add(GTK_CONTAINER(tag_window), new_child); \
215 if (tv
.default_tag_tree
== NULL
)
216 create_default_tag_tree();
218 /* show default empty tag tree if there are no tags */
219 if (doc
== NULL
|| doc
->file_type
== NULL
|| ! filetype_has_tags(doc
->file_type
))
221 CHANGE_TREE(tv
.default_tag_tree
);
225 if (doc
->priv
->tag_tree_dirty
)
226 { /* updating the tag list in the left tag window */
227 if (doc
->priv
->tag_tree
== NULL
)
229 doc
->priv
->tag_store
= gtk_tree_store_new(
230 SYMBOLS_N_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_STRING
, TM_TYPE_TAG
, G_TYPE_STRING
);
231 doc
->priv
->tag_tree
= gtk_tree_view_new();
232 prepare_taglist(doc
->priv
->tag_tree
, doc
->priv
->tag_store
);
233 gtk_widget_show(doc
->priv
->tag_tree
);
234 g_object_ref((gpointer
)doc
->priv
->tag_tree
); /* to hold it after removing */
237 doc
->has_tags
= symbols_recreate_tag_list(doc
, SYMBOLS_SORT_USE_PREVIOUS
);
238 doc
->priv
->tag_tree_dirty
= FALSE
;
243 CHANGE_TREE(doc
->priv
->tag_tree
);
247 CHANGE_TREE(tv
.default_tag_tree
);
254 /* cleverly sorts documents by their short name */
255 static gint
documents_sort_func(GtkTreeModel
*model
, GtkTreeIter
*iter_a
,
256 GtkTreeIter
*iter_b
, gpointer data
)
258 gchar
*key_a
, *key_b
;
259 gchar
*name_a
, *name_b
;
262 gtk_tree_model_get(model
, iter_a
, DOCUMENTS_SHORTNAME
, &name_a
, -1);
263 key_a
= g_utf8_collate_key_for_filename(name_a
, -1);
265 gtk_tree_model_get(model
, iter_b
, DOCUMENTS_SHORTNAME
, &name_b
, -1);
266 key_b
= g_utf8_collate_key_for_filename(name_b
, -1);
268 cmp
= strcmp(key_a
, key_b
);
276 /* does some preparing things to the open files list widget */
277 static void prepare_openfiles(void)
279 GtkCellRenderer
*icon_renderer
;
280 GtkCellRenderer
*text_renderer
;
281 GtkTreeViewColumn
*column
;
282 GtkTreeSelection
*selection
;
283 GtkTreeSortable
*sortable
;
285 tv
.tree_openfiles
= ui_lookup_widget(main_widgets
.window
, "treeview6");
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_openfiles
= gtk_tree_store_new(5, G_TYPE_ICON
, G_TYPE_STRING
,
290 G_TYPE_POINTER
, GDK_TYPE_COLOR
, G_TYPE_STRING
);
291 gtk_tree_view_set_model(GTK_TREE_VIEW(tv
.tree_openfiles
), GTK_TREE_MODEL(store_openfiles
));
293 /* set policy settings for the scolledwindow around the treeview again, because glade
294 * doesn't keep the settings */
295 gtk_scrolled_window_set_policy(
296 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets
.window
, "scrolledwindow7")),
297 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
299 icon_renderer
= gtk_cell_renderer_pixbuf_new();
300 g_object_set(icon_renderer
, "stock-size", GTK_ICON_SIZE_MENU
, NULL
);
301 text_renderer
= gtk_cell_renderer_text_new();
302 g_object_set(text_renderer
, "ellipsize", PANGO_ELLIPSIZE_MIDDLE
, NULL
);
303 column
= gtk_tree_view_column_new();
304 gtk_tree_view_column_pack_start(column
, icon_renderer
, FALSE
);
305 gtk_tree_view_column_set_attributes(column
, icon_renderer
, "gicon", DOCUMENTS_ICON
, NULL
);
306 gtk_tree_view_column_pack_start(column
, text_renderer
, TRUE
);
307 gtk_tree_view_column_set_attributes(column
, text_renderer
, "text", DOCUMENTS_SHORTNAME
,
308 "foreground-gdk", DOCUMENTS_COLOR
, NULL
);
309 gtk_tree_view_append_column(GTK_TREE_VIEW(tv
.tree_openfiles
), column
);
310 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv
.tree_openfiles
), FALSE
);
312 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv
.tree_openfiles
),
313 DOCUMENTS_SHORTNAME
);
315 /* sort opened filenames in the store_openfiles treeview */
316 sortable
= GTK_TREE_SORTABLE(GTK_TREE_MODEL(store_openfiles
));
317 gtk_tree_sortable_set_sort_func(sortable
, DOCUMENTS_SHORTNAME
, documents_sort_func
, NULL
, NULL
);
318 gtk_tree_sortable_set_sort_column_id(sortable
, DOCUMENTS_SHORTNAME
, GTK_SORT_ASCENDING
);
320 ui_widget_modify_font_from_string(tv
.tree_openfiles
, interface_prefs
.tagbar_font
);
323 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tv
.tree_openfiles
), DOCUMENTS_FILENAME
);
325 /* selection handling */
326 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
327 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
328 g_object_unref(store_openfiles
);
330 g_signal_connect(GTK_TREE_VIEW(tv
.tree_openfiles
), "button-press-event",
331 G_CALLBACK(sidebar_button_press_cb
), NULL
);
332 g_signal_connect(GTK_TREE_VIEW(tv
.tree_openfiles
), "key-press-event",
333 G_CALLBACK(sidebar_key_press_cb
), NULL
);
337 /* iter should be toplevel */
338 static gboolean
find_tree_iter_dir(GtkTreeIter
*iter
, const gchar
*dir
)
344 if (utils_str_equal(dir
, "."))
345 dir
= GEANY_STRING_UNTITLED
;
347 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles
), iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
348 g_return_val_if_fail(!doc
, FALSE
);
350 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles
), iter
, DOCUMENTS_SHORTNAME
, &name
, -1);
352 result
= utils_filenamecmp(name
, dir
) == 0;
359 static gboolean
utils_filename_has_prefix(const gchar
*str
, const gchar
*prefix
)
361 gchar
*head
= g_strndup(str
, strlen(prefix
));
362 gboolean ret
= utils_filenamecmp(head
, prefix
) == 0;
369 static gchar
*get_doc_folder(const gchar
*path
)
371 gchar
*tmp_dirname
= g_strdup(path
);
372 gchar
*project_base_path
;
373 gchar
*dirname
= NULL
;
374 const gchar
*home_dir
= g_get_home_dir();
377 /* replace the project base path with the project name */
378 project_base_path
= project_get_base_path();
380 if (project_base_path
!= NULL
)
382 gsize len
= strlen(project_base_path
);
384 /* remove trailing separator so we can match base path exactly */
385 if (project_base_path
[len
-1] == G_DIR_SEPARATOR
)
386 project_base_path
[--len
] = '\0';
388 /* check whether the dir name matches or uses the project base path */
389 if (utils_filename_has_prefix(tmp_dirname
, project_base_path
))
391 rest
= tmp_dirname
+ len
;
392 if (*rest
== G_DIR_SEPARATOR
|| *rest
== '\0')
394 dirname
= g_strdup_printf("%s%s", app
->project
->name
, rest
);
397 g_free(project_base_path
);
401 dirname
= tmp_dirname
;
403 /* If matches home dir, replace with tilde */
404 if (!EMPTY(home_dir
) && utils_filename_has_prefix(dirname
, home_dir
))
406 rest
= dirname
+ strlen(home_dir
);
407 if (*rest
== G_DIR_SEPARATOR
|| *rest
== '\0')
409 dirname
= g_strdup_printf("~%s", rest
);
421 static GtkTreeIter
*get_doc_parent(GeanyDocument
*doc
)
424 gchar
*dirname
= NULL
;
425 static GtkTreeIter parent
;
426 GtkTreeModel
*model
= GTK_TREE_MODEL(store_openfiles
);
427 static GIcon
*dir_icon
= NULL
;
429 if (!documents_show_paths
)
432 path
= g_path_get_dirname(DOC_FILENAME(doc
));
433 dirname
= get_doc_folder(path
);
435 if (gtk_tree_model_get_iter_first(model
, &parent
))
439 if (find_tree_iter_dir(&parent
, dirname
))
446 while (gtk_tree_model_iter_next(model
, &parent
));
448 /* no match, add dir parent */
450 dir_icon
= ui_get_mime_icon("inode/directory");
452 gtk_tree_store_append(store_openfiles
, &parent
, NULL
);
453 gtk_tree_store_set(store_openfiles
, &parent
, DOCUMENTS_ICON
, dir_icon
,
454 DOCUMENTS_FILENAME
, path
,
455 DOCUMENTS_SHORTNAME
, doc
->file_name
? dirname
: GEANY_STRING_UNTITLED
, -1);
463 /* Also sets doc->priv->iter.
464 * This is called recursively in sidebar_openfiles_update_all(). */
465 void sidebar_openfiles_add(GeanyDocument
*doc
)
467 GtkTreeIter
*iter
= &doc
->priv
->iter
;
468 GtkTreeIter
*parent
= get_doc_parent(doc
);
470 const GdkColor
*color
= document_get_status_color(doc
);
471 static GIcon
*file_icon
= NULL
;
473 gtk_tree_store_append(store_openfiles
, iter
, parent
);
475 /* check if new parent */
476 if (parent
&& gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles
), parent
) == 1)
481 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles
), parent
);
482 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv
.tree_openfiles
), path
, TRUE
);
483 gtk_tree_path_free(path
);
486 file_icon
= ui_get_mime_icon("text/plain");
488 basename
= g_path_get_basename(DOC_FILENAME(doc
));
489 gtk_tree_store_set(store_openfiles
, iter
,
490 DOCUMENTS_ICON
, (doc
->file_type
&& doc
->file_type
->icon
) ? doc
->file_type
->icon
: file_icon
,
491 DOCUMENTS_SHORTNAME
, basename
, DOCUMENTS_DOCUMENT
, doc
, DOCUMENTS_COLOR
, color
,
492 DOCUMENTS_FILENAME
, DOC_FILENAME(doc
), -1);
497 static void openfiles_remove(GeanyDocument
*doc
)
499 GtkTreeIter
*iter
= &doc
->priv
->iter
;
502 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles
), &parent
, iter
) &&
503 gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store_openfiles
), &parent
) == 1)
504 gtk_tree_store_remove(store_openfiles
, &parent
);
506 gtk_tree_store_remove(store_openfiles
, iter
);
510 void sidebar_openfiles_update(GeanyDocument
*doc
)
512 GtkTreeIter
*iter
= &doc
->priv
->iter
;
515 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles
), iter
, DOCUMENTS_FILENAME
, &fname
, -1);
517 if (utils_str_equal(fname
, DOC_FILENAME(doc
)))
519 /* just update color and the icon */
520 const GdkColor
*color
= document_get_status_color(doc
);
521 GIcon
*icon
= doc
->file_type
->icon
;
523 gtk_tree_store_set(store_openfiles
, iter
, DOCUMENTS_COLOR
, color
, -1);
525 gtk_tree_store_set(store_openfiles
, iter
, DOCUMENTS_ICON
, icon
, -1);
529 /* path has changed, so remove and re-add */
530 GtkTreeSelection
*treesel
;
533 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
534 sel
= gtk_tree_selection_iter_is_selected(treesel
, &doc
->priv
->iter
);
535 openfiles_remove(doc
);
537 sidebar_openfiles_add(doc
);
539 gtk_tree_selection_select_iter(treesel
, &doc
->priv
->iter
);
545 void sidebar_openfiles_update_all(void)
549 gtk_tree_store_clear(store_openfiles
);
552 sidebar_openfiles_add(documents
[i
]);
557 void sidebar_remove_document(GeanyDocument
*doc
)
559 openfiles_remove(doc
);
561 if (GTK_IS_WIDGET(doc
->priv
->tag_tree
))
563 gtk_widget_destroy(doc
->priv
->tag_tree
); /* make GTK release its references, if any */
564 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
565 g_object_unref(doc
->priv
->tag_tree
);
566 doc
->priv
->tag_tree
= NULL
;
571 static void on_hide_sidebar(void)
573 ui_prefs
.sidebar_visible
= FALSE
;
574 ui_sidebar_show_hide();
578 static gboolean
on_sidebar_display_symbol_list_show(GtkWidget
*item
)
580 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
),
581 interface_prefs
.sidebar_symbol_visible
);
586 static gboolean
on_sidebar_display_open_files_show(GtkWidget
*item
)
588 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
),
589 interface_prefs
.sidebar_openfiles_visible
);
594 void sidebar_add_common_menu_items(GtkMenu
*menu
)
598 item
= gtk_separator_menu_item_new();
599 gtk_widget_show(item
);
600 gtk_container_add(GTK_CONTAINER(menu
), item
);
602 item
= gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
603 gtk_container_add(GTK_CONTAINER(menu
), item
);
604 #if GTK_CHECK_VERSION(3, 0, 0)
605 g_signal_connect(item
, "draw", G_CALLBACK(on_sidebar_display_symbol_list_show
), NULL
);
607 g_signal_connect(item
, "expose-event",
608 G_CALLBACK(on_sidebar_display_symbol_list_show
), NULL
);
610 gtk_widget_show(item
);
611 g_signal_connect(item
, "activate",
612 G_CALLBACK(on_list_symbol_activate
), NULL
);
614 item
= gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
615 gtk_container_add(GTK_CONTAINER(menu
), item
);
616 #if GTK_CHECK_VERSION(3, 0, 0)
617 g_signal_connect(item
, "draw", G_CALLBACK(on_sidebar_display_open_files_show
), NULL
);
619 g_signal_connect(item
, "expose-event",
620 G_CALLBACK(on_sidebar_display_open_files_show
), NULL
);
622 gtk_widget_show(item
);
623 g_signal_connect(item
, "activate",
624 G_CALLBACK(on_list_document_activate
), NULL
);
626 item
= gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
627 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
628 gtk_image_new_from_stock(GTK_STOCK_CLOSE
, GTK_ICON_SIZE_MENU
));
629 gtk_widget_show(item
);
630 gtk_container_add(GTK_CONTAINER(menu
), item
);
631 g_signal_connect(item
, "activate", G_CALLBACK(on_hide_sidebar
), NULL
);
635 static void on_openfiles_show_paths_activate(GtkCheckMenuItem
*item
, gpointer user_data
)
637 documents_show_paths
= gtk_check_menu_item_get_active(item
);
638 sidebar_openfiles_update_all();
642 static void on_list_document_activate(GtkCheckMenuItem
*item
, gpointer user_data
)
644 interface_prefs
.sidebar_openfiles_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_list_symbol_activate(GtkCheckMenuItem
*item
, gpointer user_data
)
652 interface_prefs
.sidebar_symbol_visible
= gtk_check_menu_item_get_active(item
);
653 ui_sidebar_show_hide();
654 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
658 static void on_find_in_files(GtkMenuItem
*menuitem
, gpointer user_data
)
660 GtkTreeSelection
*treesel
;
666 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
667 if (!gtk_tree_selection_get_selected(treesel
, &model
, &iter
))
669 gtk_tree_model_get(model
, &iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
673 gtk_tree_model_get(model
, &iter
, DOCUMENTS_FILENAME
, &dir
, -1);
676 dir
= g_path_get_dirname(DOC_FILENAME(doc
));
678 search_show_find_in_files_dialog(dir
);
683 static void on_openfiles_expand_collapse(GtkMenuItem
*menuitem
, gpointer user_data
)
685 gboolean expand
= GPOINTER_TO_INT(user_data
);
688 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv
.tree_openfiles
));
690 gtk_tree_view_collapse_all(GTK_TREE_VIEW(tv
.tree_openfiles
));
694 static void create_openfiles_popup_menu(void)
698 openfiles_popup_menu
= gtk_menu_new();
700 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE
, NULL
);
701 gtk_widget_show(item
);
702 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
703 g_signal_connect(item
, "activate",
704 G_CALLBACK(on_openfiles_document_action
), GINT_TO_POINTER(OPENFILES_ACTION_REMOVE
));
705 doc_items
.close
= item
;
707 item
= gtk_separator_menu_item_new();
708 gtk_widget_show(item
);
709 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
711 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE
, NULL
);
712 gtk_widget_show(item
);
713 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
714 g_signal_connect(item
, "activate",
715 G_CALLBACK(on_openfiles_document_action
), GINT_TO_POINTER(OPENFILES_ACTION_SAVE
));
716 doc_items
.save
= item
;
718 item
= gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
719 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
720 gtk_image_new_from_stock(GTK_STOCK_REVERT_TO_SAVED
, GTK_ICON_SIZE_MENU
));
721 gtk_widget_show(item
);
722 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
723 g_signal_connect(item
, "activate",
724 G_CALLBACK(on_openfiles_document_action
), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD
));
725 doc_items
.reload
= item
;
727 item
= gtk_separator_menu_item_new();
728 gtk_widget_show(item
);
729 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
731 item
= ui_image_menu_item_new(GTK_STOCK_FIND
, _("_Find in Files..."));
732 gtk_widget_show(item
);
733 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
734 g_signal_connect(item
, "activate", G_CALLBACK(on_find_in_files
), NULL
);
735 doc_items
.find_in_files
= item
;
737 item
= gtk_separator_menu_item_new();
738 gtk_widget_show(item
);
739 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
741 doc_items
.show_paths
= gtk_check_menu_item_new_with_mnemonic(_("Show _Paths"));
742 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items
.show_paths
), documents_show_paths
);
743 gtk_widget_show(doc_items
.show_paths
);
744 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), doc_items
.show_paths
);
745 g_signal_connect(doc_items
.show_paths
, "activate",
746 G_CALLBACK(on_openfiles_show_paths_activate
), NULL
);
748 item
= gtk_separator_menu_item_new();
749 gtk_widget_show(item
);
750 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
752 doc_items
.expand_all
= ui_image_menu_item_new(GTK_STOCK_ADD
, _("_Expand All"));
753 gtk_widget_show(doc_items
.expand_all
);
754 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), doc_items
.expand_all
);
755 g_signal_connect(doc_items
.expand_all
, "activate",
756 G_CALLBACK(on_openfiles_expand_collapse
), GINT_TO_POINTER(TRUE
));
758 doc_items
.collapse_all
= ui_image_menu_item_new(GTK_STOCK_REMOVE
, _("_Collapse All"));
759 gtk_widget_show(doc_items
.collapse_all
);
760 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), doc_items
.collapse_all
);
761 g_signal_connect(doc_items
.collapse_all
, "activate",
762 G_CALLBACK(on_openfiles_expand_collapse
), GINT_TO_POINTER(FALSE
));
764 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu
));
768 static void unfold_parent(GtkTreeIter
*iter
)
773 if (gtk_tree_model_iter_parent(GTK_TREE_MODEL(store_openfiles
), &parent
, iter
))
775 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles
), &parent
);
776 gtk_tree_view_expand_row(GTK_TREE_VIEW(tv
.tree_openfiles
), path
, TRUE
);
777 gtk_tree_path_free(path
);
782 /* compares the given data with the doc pointer from the selected row of openfiles
783 * treeview, in case of a match the row is selected and TRUE is returned
784 * (called indirectly from gtk_tree_model_foreach()) */
785 static gboolean
tree_model_find_node(GtkTreeModel
*model
, GtkTreePath
*path
,
786 GtkTreeIter
*iter
, gpointer data
)
790 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles
), iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
794 /* unfolding also prevents a strange bug where the selection gets stuck on the parent
795 * when it is collapsed and then switching documents */
797 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
.tree_openfiles
), path
, NULL
, FALSE
);
804 void sidebar_select_openfiles_item(GeanyDocument
*doc
)
806 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles
), tree_model_find_node
, doc
);
812 static void document_action(GeanyDocument
*doc
, gint action
)
814 if (! DOC_VALID(doc
))
819 case OPENFILES_ACTION_REMOVE
:
824 case OPENFILES_ACTION_SAVE
:
826 document_save_file(doc
, FALSE
);
829 case OPENFILES_ACTION_RELOAD
:
831 document_reload_prompt(doc
, NULL
);
838 static void on_openfiles_document_action(GtkMenuItem
*menuitem
, gpointer user_data
)
841 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
844 gint action
= GPOINTER_TO_INT(user_data
);
846 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
))
848 gtk_tree_model_get(model
, &iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
851 document_action(doc
, action
);
855 /* parent item selected */
857 gint i
= gtk_tree_model_iter_n_children(model
, &iter
) - 1;
859 while (i
>= 0 && gtk_tree_model_iter_nth_child(model
, &child
, &iter
, i
))
861 gtk_tree_model_get(model
, &child
, DOCUMENTS_DOCUMENT
, &doc
, -1);
863 document_action(doc
, action
);
871 static void change_focus_to_editor(GeanyDocument
*doc
, GtkWidget
*source_widget
)
874 document_try_focus(doc
, source_widget
);
875 may_steal_focus
= FALSE
;
879 static gboolean
openfiles_go_to_selection(GtkTreeSelection
*selection
, guint keyval
)
883 GeanyDocument
*doc
= NULL
;
885 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
886 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
) && ! ignore_callback
)
888 gtk_tree_model_get(model
, &iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
890 return FALSE
; /* parent */
892 /* switch to the doc and grab the focus */
893 document_show_tab(doc
);
894 if (keyval
!= GDK_space
)
895 change_focus_to_editor(doc
, tv
.tree_openfiles
);
901 static gboolean
taglist_go_to_selection(GtkTreeSelection
*selection
, guint keyval
, guint state
)
906 gboolean handled
= TRUE
;
908 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
))
912 gtk_tree_model_get(model
, &iter
, SYMBOLS_COLUMN_TAG
, &tag
, -1);
919 GeanyDocument
*doc
= document_get_current();
923 navqueue_goto_line(doc
, doc
, line
);
924 state
= keybindings_get_modifiers(state
);
925 if (keyval
!= GDK_space
&& ! (state
& GEANY_PRIMARY_MOD_MASK
))
926 change_focus_to_editor(doc
, NULL
);
937 static gboolean
sidebar_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
,
940 may_steal_focus
= FALSE
;
941 if (ui_is_keyval_enter_or_return(event
->keyval
) || event
->keyval
== GDK_space
)
943 GtkWidgetClass
*widget_class
= GTK_WIDGET_GET_CLASS(widget
);
944 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
945 may_steal_focus
= TRUE
;
947 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
948 * doing so will prevent further handlers to be run in most cases, but the only one is our
949 * own, so guess it's fine. */
950 if (widget_class
->key_press_event
)
951 widget_class
->key_press_event(widget
, event
);
953 if (widget
== tv
.tree_openfiles
) /* tag and doc list have separate handlers */
954 openfiles_go_to_selection(selection
, event
->keyval
);
956 taglist_go_to_selection(selection
, event
->keyval
, event
->state
);
964 static gboolean
sidebar_button_press_cb(GtkWidget
*widget
, GdkEventButton
*event
,
965 G_GNUC_UNUSED gpointer user_data
)
967 GtkTreeSelection
*selection
;
968 GtkWidgetClass
*widget_class
= GTK_WIDGET_GET_CLASS(widget
);
969 gboolean handled
= FALSE
;
971 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
972 * doing so will prevent further handlers to be run in most cases, but the only one is our own,
973 * so guess it's fine. */
974 if (widget_class
->button_press_event
)
975 handled
= widget_class
->button_press_event(widget
, event
);
977 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
978 may_steal_focus
= TRUE
;
980 if (event
->type
== GDK_2BUTTON_PRESS
)
981 { /* double click on parent node(section) expands/collapses it */
985 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
))
987 if (gtk_tree_model_iter_has_child(model
, &iter
))
989 GtkTreePath
*path
= gtk_tree_model_get_path(model
, &iter
);
991 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget
), path
))
992 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget
), path
);
994 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget
), path
, FALSE
);
996 gtk_tree_path_free(path
);
1001 else if (event
->button
== 1)
1002 { /* allow reclicking of taglist treeview item */
1003 if (widget
== tv
.tree_openfiles
)
1005 openfiles_go_to_selection(selection
, 0);
1009 handled
= taglist_go_to_selection(selection
, 0, event
->state
);
1011 else if (event
->button
== 2)
1013 if (widget
== tv
.tree_openfiles
)
1014 on_openfiles_document_action(NULL
, GINT_TO_POINTER(OPENFILES_ACTION_REMOVE
));
1016 else if (event
->button
== 3)
1018 if (widget
== tv
.tree_openfiles
)
1020 if (!openfiles_popup_menu
)
1021 create_openfiles_popup_menu();
1023 /* update menu item sensitivity */
1024 documents_menu_update(selection
);
1025 gtk_menu_popup(GTK_MENU(openfiles_popup_menu
), NULL
, NULL
, NULL
, NULL
,
1026 event
->button
, event
->time
);
1030 gtk_menu_popup(GTK_MENU(tv
.popup_taglist
), NULL
, NULL
, NULL
, NULL
,
1031 event
->button
, event
->time
);
1039 static void documents_menu_update(GtkTreeSelection
*selection
)
1041 GtkTreeModel
*model
;
1044 gchar
*shortname
= NULL
;
1045 GeanyDocument
*doc
= NULL
;
1047 /* maybe no selection e.g. if ctrl-click deselected */
1048 sel
= gtk_tree_selection_get_selected(selection
, &model
, &iter
);
1051 gtk_tree_model_get(model
, &iter
, DOCUMENTS_DOCUMENT
, &doc
,
1052 DOCUMENTS_SHORTNAME
, &shortname
, -1);
1054 path
= !EMPTY(shortname
) &&
1055 (g_path_is_absolute(shortname
) ||
1056 (app
->project
&& g_str_has_prefix(shortname
, app
->project
->name
)));
1058 /* can close all, save all (except shortname), but only reload individually ATM */
1059 gtk_widget_set_sensitive(doc_items
.close
, sel
);
1060 gtk_widget_set_sensitive(doc_items
.save
, (doc
&& doc
->real_path
) || path
);
1061 gtk_widget_set_sensitive(doc_items
.reload
, doc
&& doc
->real_path
);
1062 gtk_widget_set_sensitive(doc_items
.find_in_files
, sel
);
1065 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items
.show_paths
), documents_show_paths
);
1066 gtk_widget_set_sensitive(doc_items
.expand_all
, documents_show_paths
);
1067 gtk_widget_set_sensitive(doc_items
.collapse_all
, documents_show_paths
);
1071 static StashGroup
*stash_group
= NULL
;
1073 static void on_load_settings(void)
1075 tag_window
= ui_lookup_widget(main_widgets
.window
, "scrolledwindow2");
1077 prepare_openfiles();
1078 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
1079 stash_group_display(stash_group
, NULL
);
1080 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
1084 static void on_save_settings(void)
1086 stash_group_update(stash_group
, NULL
);
1087 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
1091 static void on_sidebar_switch_page(GtkNotebook
*notebook
,
1092 gpointer page
, guint page_num
, gpointer user_data
)
1094 if (page_num
== TREEVIEW_SYMBOL
)
1095 sidebar_update_tag_list(document_get_current(), FALSE
);
1099 void sidebar_init(void)
1103 group
= stash_group_new(PACKAGE
);
1104 stash_group_add_boolean(group
, &documents_show_paths
, "documents_show_paths", TRUE
);
1105 stash_group_add_widget_property(group
, &ui_prefs
.sidebar_page
, "sidebar_page", GINT_TO_POINTER(0),
1106 main_widgets
.sidebar_notebook
, "page", 0);
1107 configuration_add_pref_group(group
, FALSE
);
1108 stash_group
= group
;
1110 /* delay building documents treeview until sidebar font has been read */
1111 g_signal_connect(geany_object
, "load-settings", on_load_settings
, NULL
);
1112 g_signal_connect(geany_object
, "save-settings", on_save_settings
, NULL
);
1114 g_signal_connect(main_widgets
.sidebar_notebook
, "page-added",
1115 G_CALLBACK(sidebar_tabs_show_hide
), NULL
);
1116 g_signal_connect(main_widgets
.sidebar_notebook
, "page-removed",
1117 G_CALLBACK(sidebar_tabs_show_hide
), NULL
);
1118 /* tabs may have changed when sidebar is reshown */
1119 g_signal_connect(main_widgets
.sidebar_notebook
, "show",
1120 G_CALLBACK(sidebar_tabs_show_hide
), NULL
);
1121 g_signal_connect_after(main_widgets
.sidebar_notebook
, "switch-page",
1122 G_CALLBACK(on_sidebar_switch_page
), NULL
);
1124 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
1127 #define WIDGET(w) w && GTK_IS_WIDGET(w)
1129 void sidebar_finalize(void)
1131 if (WIDGET(tv
.default_tag_tree
))
1133 gtk_widget_destroy(tv
.default_tag_tree
); /* make GTK release its references, if any... */
1134 g_object_unref(tv
.default_tag_tree
); /* ...and release our own */
1136 if (WIDGET(tv
.popup_taglist
))
1137 gtk_widget_destroy(tv
.popup_taglist
);
1138 if (WIDGET(openfiles_popup_menu
))
1139 gtk_widget_destroy(openfiles_popup_menu
);
1143 void sidebar_focus_openfiles_tab(void)
1145 if (ui_prefs
.sidebar_visible
&& interface_prefs
.sidebar_openfiles_visible
)
1147 GtkNotebook
*notebook
= GTK_NOTEBOOK(main_widgets
.sidebar_notebook
);
1149 gtk_notebook_set_current_page(notebook
, TREEVIEW_OPENFILES
);
1150 gtk_widget_grab_focus(tv
.tree_openfiles
);
1155 void sidebar_focus_symbols_tab(void)
1157 if (ui_prefs
.sidebar_visible
&& interface_prefs
.sidebar_symbol_visible
)
1159 GtkNotebook
*notebook
= GTK_NOTEBOOK(main_widgets
.sidebar_notebook
);
1160 GtkWidget
*symbol_list_scrollwin
= gtk_notebook_get_nth_page(notebook
, TREEVIEW_SYMBOL
);
1162 gtk_notebook_set_current_page(notebook
, TREEVIEW_SYMBOL
);
1163 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin
)));
1168 static void sidebar_tabs_show_hide(GtkNotebook
*notebook
, GtkWidget
*child
,
1169 guint page_num
, gpointer data
)
1171 gint tabs
= gtk_notebook_get_n_pages(notebook
);
1173 if (interface_prefs
.sidebar_symbol_visible
== FALSE
)
1175 if (interface_prefs
.sidebar_openfiles_visible
== FALSE
)
1178 gtk_notebook_set_show_tabs(notebook
, (tabs
> 1));