2 * sidebar.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * Sidebar related code for the Symbol list and Open files GtkTreeViews.
32 #include "callbacks.h" /* FIXME: for ignore_callback */
33 #include "documentprivate.h"
34 #include "filetypesprivate.h"
35 #include "geanyobject.h"
43 #include "keybindings.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
;
59 GtkWidget
*show_paths
[OPENFILES_PATHS_COUNT
];
60 GtkWidget
*find_in_files
;
61 GtkWidget
*expand_all
;
62 GtkWidget
*collapse_all
;
74 OPENFILES_ACTION_REMOVE
= 0,
75 OPENFILES_ACTION_SAVE
,
76 OPENFILES_ACTION_RELOAD
79 static GtkTreeStore
*store_openfiles
;
80 static GtkWidget
*openfiles_popup_menu
;
81 static GtkWidget
*tag_window
; /* scrolled window that holds the symbol list GtkTreeView */
83 /* callback prototypes */
84 static void on_openfiles_document_action(GtkMenuItem
*menuitem
, gpointer user_data
);
85 static gboolean
sidebar_button_press_cb(GtkWidget
*widget
, GdkEventButton
*event
,
87 static gboolean
sidebar_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
,
89 static void on_list_document_activate(GtkCheckMenuItem
*item
, gpointer user_data
);
90 static void on_list_symbol_activate(GtkCheckMenuItem
*item
, gpointer user_data
);
91 static void documents_menu_update(GtkTreeSelection
*selection
);
92 static void sidebar_tabs_show_hide(GtkNotebook
*notebook
, GtkWidget
*child
,
93 guint page_num
, gpointer data
);
96 /* the prepare_* functions are document-related, but I think they fit better here than in document.c */
97 static void prepare_taglist(GtkWidget
*tree
, GtkTreeStore
*store
)
99 GtkCellRenderer
*text_renderer
, *icon_renderer
;
100 GtkTreeViewColumn
*column
;
101 GtkTreeSelection
*selection
;
103 text_renderer
= gtk_cell_renderer_text_new();
104 icon_renderer
= gtk_cell_renderer_pixbuf_new();
105 column
= gtk_tree_view_column_new();
107 gtk_tree_view_column_pack_start(column
, icon_renderer
, FALSE
);
108 gtk_tree_view_column_set_attributes(column
, icon_renderer
, "pixbuf", SYMBOLS_COLUMN_ICON
, NULL
);
109 g_object_set(icon_renderer
, "xalign", 0.0, NULL
);
111 gtk_tree_view_column_pack_start(column
, text_renderer
, TRUE
);
112 gtk_tree_view_column_set_attributes(column
, text_renderer
, "text", SYMBOLS_COLUMN_NAME
, NULL
);
113 g_object_set(text_renderer
, "yalign", 0.5, NULL
);
114 gtk_tree_view_column_set_title(column
, _("Symbols"));
116 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
117 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree
), FALSE
);
119 ui_widget_modify_font_from_string(tree
, interface_prefs
.tagbar_font
);
121 gtk_tree_view_set_model(GTK_TREE_VIEW(tree
), GTK_TREE_MODEL(store
));
122 g_object_unref(store
);
124 g_signal_connect(tree
, "button-press-event",
125 G_CALLBACK(sidebar_button_press_cb
), NULL
);
126 g_signal_connect(tree
, "key-press-event",
127 G_CALLBACK(sidebar_key_press_cb
), NULL
);
129 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(tree
), interface_prefs
.show_symbol_list_expanders
);
130 if (! interface_prefs
.show_symbol_list_expanders
)
131 gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(tree
), 10);
133 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tree
), SYMBOLS_COLUMN_TOOLTIP
);
135 /* selection handling */
136 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tree
));
137 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
138 /* callback for changed selection not necessary, will be handled by button-press-event */
143 on_default_tag_tree_button_press_event(GtkWidget
*widget
, GdkEventButton
*event
,
146 if (event
->button
== 3)
148 gtk_menu_popup_at_pointer(GTK_MENU(tv
.popup_taglist
), (GdkEvent
*) event
);
155 static void create_default_tag_tree(void)
157 GtkScrolledWindow
*scrolled_window
= GTK_SCROLLED_WINDOW(tag_window
);
160 /* default_tag_tree is a GtkViewPort with a GtkLabel inside it */
161 tv
.default_tag_tree
= gtk_viewport_new(
162 gtk_scrolled_window_get_hadjustment(scrolled_window
),
163 gtk_scrolled_window_get_vadjustment(scrolled_window
));
164 gtk_viewport_set_shadow_type(GTK_VIEWPORT(tv
.default_tag_tree
), GTK_SHADOW_NONE
);
165 label
= gtk_label_new(_("No symbols found"));
166 gtk_misc_set_alignment(GTK_MISC(label
), 0.1f
, 0.01f
);
167 gtk_container_add(GTK_CONTAINER(tv
.default_tag_tree
), label
);
168 gtk_widget_show_all(tv
.default_tag_tree
);
169 g_signal_connect(tv
.default_tag_tree
, "button-press-event",
170 G_CALLBACK(on_default_tag_tree_button_press_event
), NULL
);
171 g_object_ref((gpointer
)tv
.default_tag_tree
); /* to hold it after removing */
175 /* update = rescan the tags for doc->filename */
176 void sidebar_update_tag_list(GeanyDocument
*doc
, gboolean update
)
178 GtkWidget
*child
= gtk_bin_get_child(GTK_BIN(tag_window
));
180 g_return_if_fail(doc
== NULL
|| doc
->is_valid
);
182 if (update
&& doc
!= NULL
)
183 doc
->priv
->tag_tree_dirty
= TRUE
;
185 if (gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
)) != TREEVIEW_SYMBOL
)
186 return; /* don't bother updating symbol tree if we don't see it */
188 /* changes the tree view to the given one, trying not to do useless changes */
189 #define CHANGE_TREE(new_child) \
191 /* only change the tag tree if it's actually not the same (to avoid flickering) and if \
192 * it's the one of the current document (to avoid problems when e.g. reloading \
193 * configuration files */ \
194 if (child != new_child && doc == document_get_current()) \
197 gtk_container_remove(GTK_CONTAINER(tag_window), child); \
198 gtk_container_add(GTK_CONTAINER(tag_window), new_child); \
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 CHANGE_TREE(tv
.default_tag_tree
);
212 if (doc
->priv
->tag_tree_dirty
)
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
, TM_TYPE_TAG
, 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
);
225 doc
->priv
->tag_tree_dirty
= FALSE
;
230 CHANGE_TREE(doc
->priv
->tag_tree
);
234 CHANGE_TREE(tv
.default_tag_tree
);
241 /* cleverly sorts documents by their short name */
242 static gint
documents_sort_func(GtkTreeModel
*model
, GtkTreeIter
*iter_a
,
243 GtkTreeIter
*iter_b
, gpointer data
)
245 gchar
*name_a
, *name_b
;
246 GeanyDocument
*doc_a
, *doc_b
;
249 gtk_tree_model_get(model
, iter_a
, DOCUMENTS_SHORTNAME
, &name_a
, DOCUMENTS_DOCUMENT
, &doc_a
, -1);
250 gtk_tree_model_get(model
, iter_b
, DOCUMENTS_SHORTNAME
, &name_b
, DOCUMENTS_DOCUMENT
, &doc_b
, -1);
252 /* sort dirs after files (within a directory node) */
255 else if (doc_a
&& !doc_b
)
259 gchar
*key_a
, *key_b
;
260 /* g_utf8_collate_key_for_filename() seems to ignore ~. As a result, ~/dev <=> /data
261 * compares as /dev > /data while ~/dev <=> /etc compares as /dev < /etc. Thus, for sorting
262 * purposes, treat leading ~ as . which is documented to be special-cased to sort before
263 * anything else. The side effect, that documents under ~ are always first is actually
266 name_a
[0] = name_a
[0] == '~' ? '.' : name_a
[0];
267 name_b
[0] = name_b
[0] == '~' ? '.' : name_b
[0];
268 key_a
= g_utf8_collate_key_for_filename(name_a
, -1);
269 key_b
= g_utf8_collate_key_for_filename(name_b
, -1);
270 cmp
= strcmp(key_a
, key_b
);
282 GtkTreeStore
*sidebar_create_store_openfiles(void)
284 GtkTreeSortable
*sortable
;
286 /* store the icon and the short filename to show, and the index as reference,
287 * the colour (black/red/green) and the full name for the tooltip */
288 store
= gtk_tree_store_new(6, G_TYPE_ICON
, G_TYPE_STRING
,
289 G_TYPE_POINTER
, GDK_TYPE_COLOR
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
291 /* sort opened filenames in the store_openfiles treeview */
292 sortable
= GTK_TREE_SORTABLE(GTK_TREE_MODEL(store
));
293 gtk_tree_sortable_set_sort_func(sortable
, DOCUMENTS_SHORTNAME
, documents_sort_func
, NULL
, NULL
);
294 gtk_tree_sortable_set_sort_column_id(sortable
, DOCUMENTS_SHORTNAME
, GTK_SORT_ASCENDING
);
296 store_openfiles
= store
;
301 static void store_fold_recurse(GtkTreeView
*view
,
306 gboolean fold
, valid
;
308 GtkTreeIter child_iter
;
310 gtk_tree_model_get(model
, iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
311 if (doc
) /* document rows are not foldable */
314 path
= gtk_tree_model_get_path(model
, iter
);
315 fold
= !gtk_tree_view_row_expanded(view
, path
);
316 gtk_tree_store_set(GTK_TREE_STORE(model
), iter
, DOCUMENTS_FOLD
, fold
, -1);
317 gtk_tree_path_free(path
);
319 /* After storing the fold state for *this* row, recursively do the same for its children.
320 * We need do do this only for expanded children because all children of folded rows are
326 valid
= gtk_tree_model_iter_children(model
, &child_iter
, iter
);
329 store_fold_recurse(view
, &child_iter
, model
);
330 valid
= gtk_tree_model_iter_next(model
, &child_iter
);
335 static gboolean
on_row_expand(GtkTreeView
*view
,
342 model
= gtk_tree_view_get_model(view
);
343 gtk_tree_store_set(GTK_TREE_STORE(model
), iter
, DOCUMENTS_FOLD
, FALSE
, -1);
348 static gboolean
on_row_collapse(GtkTreeView
*view
,
354 GtkTreeIter child_iter
;
357 model
= gtk_tree_view_get_model(view
);
358 gtk_tree_store_set(GTK_TREE_STORE(model
), iter
, DOCUMENTS_FOLD
, TRUE
, -1);
360 valid
= gtk_tree_model_iter_children(model
, &child_iter
, iter
);
363 store_fold_recurse(view
, &child_iter
, model
);
364 valid
= gtk_tree_model_iter_next(model
, &child_iter
);
371 static void on_row_expanded(GtkTreeView
*view
,
376 GtkTreeIter child_iter
;
382 model
= gtk_tree_view_get_model(view
);
384 valid
= gtk_tree_model_iter_children(model
, &child_iter
, iter
);
389 gtk_tree_model_get(model
, &child_iter
, DOCUMENTS_DOCUMENT
, &doc
, DOCUMENTS_FOLD
, &fold
, -1);
390 path
= gtk_tree_model_get_path(model
, &child_iter
);
393 gtk_tree_view_expand_row(view
, path
, FALSE
);
395 valid
= gtk_tree_model_iter_next(model
, &child_iter
);
396 gtk_tree_path_free(path
);
401 /* does some preparing things to the open files list widget */
402 static void prepare_openfiles(void)
404 GtkCellRenderer
*icon_renderer
;
405 GtkCellRenderer
*text_renderer
;
406 GtkTreeViewColumn
*column
;
407 GtkTreeSelection
*selection
;
409 tv
.tree_openfiles
= ui_lookup_widget(main_widgets
.window
, "treeview6");
411 sidebar_create_store_openfiles();
413 gtk_tree_view_set_model(GTK_TREE_VIEW(tv
.tree_openfiles
), GTK_TREE_MODEL(store_openfiles
));
415 /* These two implement "remember fold state of rows when their parents are folded". Normally
416 * GTK does not remember the fold state and can only expand all or no children when
417 * expanding a row. Maybe this can be useful for other tree views as well?
419 g_signal_connect_after(GTK_TREE_VIEW(tv
.tree_openfiles
), "test-expand-row", G_CALLBACK(on_row_expand
), NULL
);
420 g_signal_connect_after(GTK_TREE_VIEW(tv
.tree_openfiles
), "test-collapse-row", G_CALLBACK(on_row_collapse
), NULL
);
421 g_signal_connect_after(GTK_TREE_VIEW(tv
.tree_openfiles
), "row-expanded", G_CALLBACK(on_row_expanded
), NULL
);
423 /* set policy settings for the scolledwindow around the treeview again, because glade
424 * doesn't keep the settings */
425 gtk_scrolled_window_set_policy(
426 GTK_SCROLLED_WINDOW(ui_lookup_widget(main_widgets
.window
, "scrolledwindow7")),
427 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
429 icon_renderer
= gtk_cell_renderer_pixbuf_new();
430 g_object_set(icon_renderer
, "stock-size", GTK_ICON_SIZE_MENU
, NULL
);
431 text_renderer
= gtk_cell_renderer_text_new();
432 g_object_set(text_renderer
, "ellipsize", PANGO_ELLIPSIZE_MIDDLE
, NULL
);
433 column
= gtk_tree_view_column_new();
434 gtk_tree_view_column_pack_start(column
, icon_renderer
, FALSE
);
435 gtk_tree_view_column_set_attributes(column
, icon_renderer
, "gicon", DOCUMENTS_ICON
, NULL
);
436 gtk_tree_view_column_pack_start(column
, text_renderer
, TRUE
);
437 gtk_tree_view_column_set_attributes(column
, text_renderer
, "text", DOCUMENTS_SHORTNAME
,
438 "foreground-gdk", DOCUMENTS_COLOR
, NULL
);
439 gtk_tree_view_append_column(GTK_TREE_VIEW(tv
.tree_openfiles
), column
);
440 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv
.tree_openfiles
), FALSE
);
442 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv
.tree_openfiles
),
443 DOCUMENTS_SHORTNAME
);
445 ui_widget_modify_font_from_string(tv
.tree_openfiles
, interface_prefs
.tagbar_font
);
448 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(tv
.tree_openfiles
), DOCUMENTS_FILENAME
);
450 /* selection handling */
451 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
452 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_SINGLE
);
453 g_object_unref(store_openfiles
);
455 g_signal_connect(GTK_TREE_VIEW(tv
.tree_openfiles
), "button-press-event",
456 G_CALLBACK(sidebar_button_press_cb
), NULL
);
457 g_signal_connect(GTK_TREE_VIEW(tv
.tree_openfiles
), "key-press-event",
458 G_CALLBACK(sidebar_key_press_cb
), NULL
);
462 static gboolean
utils_filename_has_prefix(const gchar
*str
, const gchar
*prefix
)
464 gchar
*head
= g_strndup(str
, strlen(prefix
));
465 gboolean ret
= utils_filenamecmp(head
, prefix
) == 0;
472 static gchar
*get_project_folder(const gchar
*path
)
474 gchar
*project_base_path
;
475 gchar
*dirname
= NULL
;
478 /* replace the project base path with the project name */
479 project_base_path
= project_get_base_path();
481 if (project_base_path
!= NULL
)
483 gsize len
= strlen(project_base_path
);
485 /* remove trailing separator so we can match base path exactly */
486 if (project_base_path
[len
-1] == G_DIR_SEPARATOR
)
487 project_base_path
[--len
] = '\0';
489 /* check whether the dir name matches or uses the project base path */
490 if (utils_filename_has_prefix(path
, project_base_path
))
493 if (*rest
== G_DIR_SEPARATOR
|| *rest
== '\0')
495 dirname
= g_strdup_printf("%s%s", app
->project
->name
, rest
);
498 g_free(project_base_path
);
505 static gchar
*get_doc_folder(const gchar
*path
)
507 gchar
*dirname
= get_project_folder(path
);
512 const gchar
*home_dir
= g_get_home_dir();
513 gchar
*tmp_dirname
= g_strdup(path
);
515 dirname
= tmp_dirname
;
516 /* If matches home dir, replace with tilde */
517 if (!EMPTY(home_dir
) && utils_filename_has_prefix(dirname
, home_dir
))
519 rest
= dirname
+ strlen(home_dir
);
520 if (*rest
== G_DIR_SEPARATOR
|| *rest
== '\0')
522 dirname
= g_strdup_printf("~%s", rest
);
532 static gchar
*parent_dir_name(GtkTreeStore
*tree
, GtkTreeIter
*parent
, const gchar
*path
)
534 gsize parent_len
= 0;
536 gchar
*pathname
= NULL
;
541 GtkTreeModel
*model
= GTK_TREE_MODEL(tree
);
543 gtk_tree_model_get(model
, parent
, DOCUMENTS_FILENAME
, &parent_dir
, -1);
546 pathname
= get_doc_folder(parent_dir
);
547 parent_len
= strlen(pathname
) + 1;
552 dirname
= get_doc_folder(path
);
556 dirname
= get_doc_folder(path
);
557 len
= strlen(dirname
);
558 /* Maybe parent is /home but dirname is ~ (after substitution from /home/user) */
559 if (pathname
[0] == dirname
[0])
560 memmove(dirname
, dirname
+ parent_len
, len
- parent_len
+ 1);
569 static void tree_copy_node(GtkTreeStore
*tree
, GtkTreeIter
*new_node
, GtkTreeIter
*node
, GtkTreeIter
*parent_new
)
576 GtkTreeModel
*model
= GTK_TREE_MODEL(tree
);
579 gtk_tree_store_append(tree
, new_node
, parent_new
);
580 gtk_tree_model_get(model
, node
,
581 DOCUMENTS_ICON
, &icon
,
582 DOCUMENTS_SHORTNAME
, &shortname
,
583 DOCUMENTS_DOCUMENT
, &doc
,
584 DOCUMENTS_COLOR
, &color
,
585 DOCUMENTS_FILENAME
, &filename
,
586 DOCUMENTS_FOLD
, &fold
,
590 doc
->priv
->iter
= *new_node
;
592 SETPTR(shortname
, parent_dir_name(tree
, parent_new
, filename
));
594 gtk_tree_store_set(tree
, new_node
,
595 DOCUMENTS_ICON
, icon
,
596 DOCUMENTS_SHORTNAME
, shortname
,
597 DOCUMENTS_DOCUMENT
, doc
,
598 DOCUMENTS_COLOR
, color
,
599 DOCUMENTS_FILENAME
, filename
,
600 DOCUMENTS_FOLD
, fold
,
605 gdk_color_free(color
);
609 /* Helper that implements the recursive part of tree_reparent() */
610 static void tree_reparent_recurse(GtkTreeStore
*tree
, GtkTreeIter
*node
, GtkTreeIter
*parent_new
, GtkTreeIter
*new_node
)
612 GtkTreeModel
*model
= GTK_TREE_MODEL(tree
);
615 /* Start by copying the node itself. It becomes parent_new for the children to be copied. */
616 tree_copy_node(tree
, new_node
, node
, parent_new
);
617 if (gtk_tree_model_iter_nth_child(model
, &child
, node
, 0))
620 GtkTreeIter new_child
;
621 tree_reparent_recurse(tree
, &child
, new_node
, &new_child
);
623 while (gtk_tree_model_iter_next(model
, &child
));
629 * Copy node and all of its children to a new parent, and then remove the old node.
631 * It is done by reparenting the node itself to the new parent, creating a copy of it,
632 * and then recursively reparenting all children to the copy of the node.
634 * Finally, the new location will be written back to node so it's readily available,
637 static void tree_reparent(GtkTreeStore
*tree
, GtkTreeIter
*node
, GtkTreeIter
*parent_new
)
639 GtkTreeIter new_node
;
640 tree_reparent_recurse(tree
, node
, parent_new
, &new_node
);
641 gtk_tree_store_remove(tree
, node
);
646 static void tree_add_new_dir(GtkTreeStore
*tree
, GtkTreeIter
*child
, GtkTreeIter
*parent
, const gchar
*file
)
648 static GIcon
*dir_icon
= NULL
;
649 gchar
*dirname
= parent_dir_name(tree
, parent
, file
);
652 dir_icon
= ui_get_mime_icon("inode/directory");
654 gtk_tree_store_append(tree
, child
, parent
);
655 gtk_tree_store_set(tree
, child
,
656 DOCUMENTS_ICON
, dir_icon
,
657 DOCUMENTS_FILENAME
, file
,
658 DOCUMENTS_SHORTNAME
, dirname
,
659 DOCUMENTS_FOLD
, TRUE
, /* GTK inserts folded by default, caller may expand */
667 * Returns the position of dir delimiter where paths don't match
669 static guint
pathcmp(const gchar
*s1
, const gchar
*s2
)
675 g_return_val_if_fail(s1
!= NULL
, 0);
676 g_return_val_if_fail(s2
!= NULL
, 0);
679 a
= utils_utf8_strdown(s1
);
682 b
= utils_utf8_strdown(s2
);
693 while (a
[i
] && b
[i
] && a
[i
] == b
[i
])
695 if (a
[i
] == '\0' && b
[i
] == '\0')
696 return i
; /* strings are equal: a/b/c == a/b/c */
697 if ((a
[i
] == '\0' && b
[i
] == G_DIR_SEPARATOR
) ||
698 (b
[i
] == '\0' && a
[i
] == G_DIR_SEPARATOR
))
699 return i
; /* subdir case: a/b/c == a/b */
700 while (i
> 0 && (a
[i
] != G_DIR_SEPARATOR
|| b
[i
] != G_DIR_SEPARATOR
))
701 i
--; /* last slash: a/b/boo == a/b/bar */
712 typedef struct TreeForeachData
{
716 GtkTreeIter best_iter
;
722 TREE_CASE_HAVE_SAME_PARENT
727 static gboolean
tree_foreach_callback(GtkTreeModel
*model
,
737 TreeForeachData
*data
= (TreeForeachData
*) user_data
;
739 gtk_tree_model_get(model
, iter
, DOCUMENTS_FILENAME
, &name
, DOCUMENTS_DOCUMENT
, &doc
, -1);
741 if (doc
) /* skip documents */
744 dirname
= get_doc_folder(name
);
746 SETPTR(name
, dirname
);
748 diff
= pathcmp(name
, data
->needle
);
749 name_len
= strlen(name
);
754 if (data
->best_len
< diff
)
757 gboolean tree
= interface_prefs
.openfiles_path_mode
== OPENFILES_PATHS_TREE
;
759 /* there are four cases */
760 /* first case: exact match. File is from already opened dir */
761 if (name_len
== diff
&& data
->needle_len
== name_len
)
762 best_case
= TREE_CASE_EQUALS
;
763 /* second case: split current dir. File is from deeper level */
764 else if (name_len
== diff
&& tree
)
765 best_case
= TREE_CASE_CHILD_OF
;
766 /* third case: split parent dir. File is from one of existing level */
767 else if (data
->needle_len
== diff
&& tree
)
768 best_case
= TREE_CASE_PARENT_OF
;
769 /* fourth case: both dirs have same parent */
771 best_case
= TREE_CASE_HAVE_SAME_PARENT
;
774 data
->best_len
= diff
;
775 data
->best_case
= best_case
;
776 data
->best_iter
= *iter
;
784 /* Returns TRUE if parent points to a newly added row,
785 * caller might want to expand the relevant rows in the tree view */
786 static gboolean
get_parent_for_file(GtkTreeStore
*tree
, const gchar
*file
, GtkTreeIter
*parent
)
792 GtkTreeModel
*model
= GTK_TREE_MODEL(tree
);
793 TreeForeachData data
= {NULL
, 0, 0, {0}, TREE_CASE_NONE
};
796 path
= g_path_get_dirname(file
);
798 /* find best opened dir */
799 data
.needle
= get_doc_folder(path
);
800 data
.needle_len
= strlen(data
.needle
);
801 name_diff
= strlen(path
) - data
.needle_len
;
802 gtk_tree_model_foreach(model
, tree_foreach_callback
, (gpointer
)&data
);
804 switch (data
.best_case
)
806 case TREE_CASE_EQUALS
:
808 *parent
= data
.best_iter
;
809 /* dir already open */
813 case TREE_CASE_CHILD_OF
:
815 /* This dir is longer than existing so just add child */
816 tree_add_new_dir(tree
, parent
, &data
.best_iter
, path
);
820 case TREE_CASE_PARENT_OF
:
822 /* More complicated logic. This dir should be a parent
823 * of existing, so reparent existing dir.
825 has_parent
= gtk_tree_model_iter_parent(model
, &iter
, &data
.best_iter
);
826 tree_add_new_dir(tree
, parent
, has_parent
? &iter
: NULL
, path
);
827 tree_reparent(tree
, &data
.best_iter
, parent
);
831 case TREE_CASE_HAVE_SAME_PARENT
:
833 /* Even more complicated logic. Both dirs have same
834 * parent, so create new parent and reparent them
836 GtkTreeIter new_parent
;
837 gchar
*newpath
= g_strndup(path
, data
.best_len
+ name_diff
);
839 has_parent
= gtk_tree_model_iter_parent(model
, &iter
, &data
.best_iter
);
840 tree_add_new_dir(tree
, &new_parent
, has_parent
? &iter
: NULL
, newpath
);
841 tree_reparent(tree
, &data
.best_iter
, &new_parent
);
842 tree_add_new_dir(tree
, parent
, &new_parent
, path
);
850 tree_add_new_dir(tree
, parent
, NULL
, path
);
863 /* Returns true when parent points to a newly added row. */
864 static gboolean
sidebar_openfiles_add_iter(GtkTreeStore
*tree
, const gchar
*file
,
865 GtkTreeIter
*iter
, GtkTreeIter
*parent
)
868 /* get_parent_for_file() might add rows for parent directories */
869 new_row
= get_parent_for_file(tree
, file
, parent
);
870 /* insert row for this file */
871 gtk_tree_store_append(tree
, iter
, parent
);
877 static void expand_iter(GtkTreeIter
*iter
)
881 path
= gtk_tree_model_get_path(GTK_TREE_MODEL(store_openfiles
), iter
);
882 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tv
.tree_openfiles
), path
);
883 gtk_tree_path_free(path
);
887 /* Also sets doc->priv->iter.
888 * This is called recursively in sidebar_openfiles_update_all(). */
890 void sidebar_openfiles_add(GeanyDocument
*doc
)
892 GtkTreeIter
*iter
= &doc
->priv
->iter
;
894 const gchar
*filename
= DOC_FILENAME(doc
);
896 const GdkColor
*color
= document_get_status_color(doc
);
897 static GIcon
*file_icon
= NULL
;
898 gboolean expand
= FALSE
;
900 if (interface_prefs
.openfiles_path_mode
!= OPENFILES_PATHS_NONE
)
901 expand
= sidebar_openfiles_add_iter(store_openfiles
, filename
, iter
, &parent
);
903 gtk_tree_store_append(store_openfiles
, iter
, NULL
);
906 file_icon
= ui_get_mime_icon("text/plain");
908 basename
= g_path_get_basename(filename
);
909 gtk_tree_store_set(store_openfiles
, iter
,
910 DOCUMENTS_ICON
, (doc
->file_type
&& doc
->file_type
->icon
) ? doc
->file_type
->icon
: file_icon
,
911 DOCUMENTS_SHORTNAME
, basename
, DOCUMENTS_DOCUMENT
, doc
, DOCUMENTS_COLOR
, color
,
912 DOCUMENTS_FILENAME
, DOC_FILENAME(doc
),
913 DOCUMENTS_FOLD
, FALSE
,
917 /* Expand new parent if necessary. Beware: this is executed by unit tests
918 * which don't create the tree view. */
919 if (expand
&& G_LIKELY(tv
.tree_openfiles
))
920 expand_iter(&parent
);
924 /* Returns true if new_node points to a reparented directory, as a result of merging empty
927 static void sidebar_openfiles_remove_iter(GtkTreeStore
*tree
, GtkTreeIter
*iter_
)
929 GtkTreeIter iter
= *iter_
;
931 GtkTreeModel
*model
= GTK_TREE_MODEL(store_openfiles
);
933 /* walk on top and close all orphaned parents */
934 while (gtk_tree_model_iter_parent(model
, &parent
, &iter
)
935 && gtk_tree_model_iter_n_children(model
, &parent
) == 1)
939 gtk_tree_store_remove(store_openfiles
, &iter
);
941 /* If, after removing, there is a single silbling left and it represents
942 * a directory, it can be merged with the parent directory row,
943 * essentially to reverse the effect of TREE_CASE_PARENT_OF and TREE_CASE_HAVE_SAME_PARENT
944 * in get_doc_parent(). Inherit fold state from the merged child as well.
946 if (gtk_tree_store_iter_is_valid(store_openfiles
, &parent
)
947 && gtk_tree_model_iter_n_children(model
, &parent
) == 1)
949 GeanyDocument
*other_doc
;
950 GtkTreeIter child
, pparent
;
951 gboolean fold
, has_parent
;
953 gtk_tree_model_iter_nth_child(model
, &child
, &parent
, 0);
954 gtk_tree_model_get(model
, &child
, DOCUMENTS_DOCUMENT
, &other_doc
, -1);
957 has_parent
= gtk_tree_model_iter_parent(model
, &pparent
, &parent
);
958 tree_reparent(store_openfiles
, &child
, has_parent
? &pparent
: NULL
);
959 gtk_tree_store_remove(store_openfiles
, &parent
);
960 /* Expand if the child node was expanded before the merge. */
961 gtk_tree_model_get(model
, &child
, DOCUMENTS_FOLD
, &fold
, -1);
969 static void openfiles_remove(GeanyDocument
*doc
)
971 if (interface_prefs
.openfiles_path_mode
!= OPENFILES_PATHS_NONE
)
972 sidebar_openfiles_remove_iter(store_openfiles
, &doc
->priv
->iter
);
974 gtk_tree_store_remove(store_openfiles
, &doc
->priv
->iter
);
978 void sidebar_openfiles_update(GeanyDocument
*doc
)
980 GtkTreeIter
*iter
= &doc
->priv
->iter
;
983 gtk_tree_model_get(GTK_TREE_MODEL(store_openfiles
), iter
, DOCUMENTS_FILENAME
, &fname
, -1);
985 if (utils_str_equal(fname
, DOC_FILENAME(doc
)))
987 /* just update color and the icon */
988 const GdkColor
*color
= document_get_status_color(doc
);
989 GIcon
*icon
= doc
->file_type
->icon
;
991 gtk_tree_store_set(store_openfiles
, iter
, DOCUMENTS_COLOR
, color
, -1);
993 gtk_tree_store_set(store_openfiles
, iter
, DOCUMENTS_ICON
, icon
, -1);
997 /* path has changed, so remove and re-add */
998 GtkTreeSelection
*treesel
;
1001 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
1002 sel
= gtk_tree_selection_iter_is_selected(treesel
, &doc
->priv
->iter
);
1003 openfiles_remove(doc
);
1005 sidebar_openfiles_add(doc
);
1007 gtk_tree_selection_select_iter(treesel
, &doc
->priv
->iter
);
1013 void sidebar_openfiles_update_all(void)
1017 gtk_tree_store_clear(store_openfiles
);
1018 foreach_document (i
)
1020 sidebar_openfiles_add(documents
[i
]);
1025 void sidebar_remove_document(GeanyDocument
*doc
)
1027 openfiles_remove(doc
);
1029 if (GTK_IS_WIDGET(doc
->priv
->tag_tree
))
1031 gtk_widget_destroy(doc
->priv
->tag_tree
); /* make GTK release its references, if any */
1032 /* Because it was ref'd in sidebar_update_tag_list, it needs unref'ing */
1033 g_object_unref(doc
->priv
->tag_tree
);
1034 doc
->priv
->tag_tree
= NULL
;
1039 static void on_hide_sidebar(void)
1041 ui_prefs
.sidebar_visible
= FALSE
;
1042 ui_sidebar_show_hide();
1046 static gboolean
on_sidebar_display_symbol_list_show(GtkWidget
*item
)
1048 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
),
1049 interface_prefs
.sidebar_symbol_visible
);
1054 static gboolean
on_sidebar_display_open_files_show(GtkWidget
*item
)
1056 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item
),
1057 interface_prefs
.sidebar_openfiles_visible
);
1062 void sidebar_add_common_menu_items(GtkMenu
*menu
)
1066 item
= gtk_separator_menu_item_new();
1067 gtk_widget_show(item
);
1068 gtk_container_add(GTK_CONTAINER(menu
), item
);
1070 item
= gtk_check_menu_item_new_with_mnemonic(_("Show S_ymbol List"));
1071 gtk_container_add(GTK_CONTAINER(menu
), item
);
1072 g_signal_connect(item
, "draw", G_CALLBACK(on_sidebar_display_symbol_list_show
), NULL
);
1073 gtk_widget_show(item
);
1074 g_signal_connect(item
, "activate",
1075 G_CALLBACK(on_list_symbol_activate
), NULL
);
1077 item
= gtk_check_menu_item_new_with_mnemonic(_("Show _Document List"));
1078 gtk_container_add(GTK_CONTAINER(menu
), item
);
1079 g_signal_connect(item
, "draw", G_CALLBACK(on_sidebar_display_open_files_show
), NULL
);
1080 gtk_widget_show(item
);
1081 g_signal_connect(item
, "activate",
1082 G_CALLBACK(on_list_document_activate
), NULL
);
1084 item
= gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar"));
1085 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
1086 gtk_image_new_from_stock(GTK_STOCK_CLOSE
, GTK_ICON_SIZE_MENU
));
1087 gtk_widget_show(item
);
1088 gtk_container_add(GTK_CONTAINER(menu
), item
);
1089 g_signal_connect(item
, "activate", G_CALLBACK(on_hide_sidebar
), NULL
);
1093 static void on_openfiles_show_paths_activate(GtkCheckMenuItem
*item
, gpointer user_data
)
1095 gint new_mode
= GPOINTER_TO_INT(user_data
);
1096 /* This is also called for menu items that became inactive (in response to activating
1097 * another one in the same group).
1099 if (!gtk_check_menu_item_get_active(item
))
1102 /* Only if the mode changes...otherwise sidebar_openfiles_update_all() recreates the
1103 * list which messes up the current selection and more.
1105 * This can happen (for example) right after startup, when no menu item was active yet.
1107 if (interface_prefs
.openfiles_path_mode
== new_mode
)
1110 interface_prefs
.openfiles_path_mode
= new_mode
;
1111 sidebar_openfiles_update_all();
1112 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv
.tree_openfiles
));
1113 sidebar_select_openfiles_item(document_get_current());
1117 static void on_list_document_activate(GtkCheckMenuItem
*item
, gpointer user_data
)
1119 interface_prefs
.sidebar_openfiles_visible
= gtk_check_menu_item_get_active(item
);
1120 ui_sidebar_show_hide();
1121 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
1125 static void on_list_symbol_activate(GtkCheckMenuItem
*item
, gpointer user_data
)
1127 interface_prefs
.sidebar_symbol_visible
= gtk_check_menu_item_get_active(item
);
1128 ui_sidebar_show_hide();
1129 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
1133 static void on_find_in_files(GtkMenuItem
*menuitem
, gpointer user_data
)
1135 GtkTreeSelection
*treesel
;
1137 GtkTreeModel
*model
;
1141 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
1142 if (!gtk_tree_selection_get_selected(treesel
, &model
, &iter
))
1144 gtk_tree_model_get(model
, &iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
1148 gtk_tree_model_get(model
, &iter
, DOCUMENTS_FILENAME
, &dir
, -1);
1151 dir
= g_path_get_dirname(DOC_FILENAME(doc
));
1153 search_show_find_in_files_dialog(dir
);
1158 static void on_openfiles_expand_collapse(GtkMenuItem
*menuitem
, gpointer user_data
)
1160 gboolean expand
= GPOINTER_TO_INT(user_data
);
1163 gtk_tree_view_expand_all(GTK_TREE_VIEW(tv
.tree_openfiles
));
1165 gtk_tree_view_collapse_all(GTK_TREE_VIEW(tv
.tree_openfiles
));
1169 static void create_show_paths_popup_menu(void)
1171 GSList
*group
= NULL
;
1172 const gchar
*items
[OPENFILES_PATHS_COUNT
] = {
1173 [OPENFILES_PATHS_NONE
] = _("D_ocuments Only"),
1174 [OPENFILES_PATHS_LIST
] = _("Show _Paths"),
1175 [OPENFILES_PATHS_TREE
] = _("Show _Tree")
1178 for (guint i
= 0; i
< G_N_ELEMENTS(items
); i
++)
1180 GtkWidget
*w
= gtk_radio_menu_item_new_with_mnemonic(group
, items
[i
]);
1181 group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(w
));
1183 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), w
);
1184 g_signal_connect(w
, "activate",
1185 G_CALLBACK(on_openfiles_show_paths_activate
), GINT_TO_POINTER(i
));
1186 doc_items
.show_paths
[i
] = w
;
1191 static void create_openfiles_popup_menu(void)
1195 openfiles_popup_menu
= gtk_menu_new();
1197 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_CLOSE
, NULL
);
1198 gtk_widget_show(item
);
1199 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1200 g_signal_connect(item
, "activate",
1201 G_CALLBACK(on_openfiles_document_action
), GINT_TO_POINTER(OPENFILES_ACTION_REMOVE
));
1202 doc_items
.close
= item
;
1204 item
= gtk_separator_menu_item_new();
1205 gtk_widget_show(item
);
1206 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1208 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE
, NULL
);
1209 gtk_widget_show(item
);
1210 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1211 g_signal_connect(item
, "activate",
1212 G_CALLBACK(on_openfiles_document_action
), GINT_TO_POINTER(OPENFILES_ACTION_SAVE
));
1213 doc_items
.save
= item
;
1215 item
= gtk_image_menu_item_new_with_mnemonic(_("_Reload"));
1216 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
),
1217 gtk_image_new_from_stock(GTK_STOCK_REVERT_TO_SAVED
, GTK_ICON_SIZE_MENU
));
1218 gtk_widget_show(item
);
1219 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1220 g_signal_connect(item
, "activate",
1221 G_CALLBACK(on_openfiles_document_action
), GINT_TO_POINTER(OPENFILES_ACTION_RELOAD
));
1222 doc_items
.reload
= item
;
1224 item
= gtk_separator_menu_item_new();
1225 gtk_widget_show(item
);
1226 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1228 item
= ui_image_menu_item_new(GTK_STOCK_FIND
, _("_Find in Files..."));
1229 gtk_widget_show(item
);
1230 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1231 g_signal_connect(item
, "activate", G_CALLBACK(on_find_in_files
), NULL
);
1232 doc_items
.find_in_files
= item
;
1234 item
= gtk_separator_menu_item_new();
1235 gtk_widget_show(item
);
1236 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1238 create_show_paths_popup_menu();
1240 item
= gtk_separator_menu_item_new();
1241 gtk_widget_show(item
);
1242 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), item
);
1244 doc_items
.expand_all
= ui_image_menu_item_new(GTK_STOCK_ADD
, _("_Expand All"));
1245 gtk_widget_show(doc_items
.expand_all
);
1246 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), doc_items
.expand_all
);
1247 g_signal_connect(doc_items
.expand_all
, "activate",
1248 G_CALLBACK(on_openfiles_expand_collapse
), GINT_TO_POINTER(TRUE
));
1250 doc_items
.collapse_all
= ui_image_menu_item_new(GTK_STOCK_REMOVE
, _("_Collapse All"));
1251 gtk_widget_show(doc_items
.collapse_all
);
1252 gtk_container_add(GTK_CONTAINER(openfiles_popup_menu
), doc_items
.collapse_all
);
1253 g_signal_connect(doc_items
.collapse_all
, "activate",
1254 G_CALLBACK(on_openfiles_expand_collapse
), GINT_TO_POINTER(FALSE
));
1256 sidebar_add_common_menu_items(GTK_MENU(openfiles_popup_menu
));
1260 /* compares the given data with the doc pointer from the selected row of openfiles
1261 * treeview, in case of a match the row is selected and TRUE is returned
1262 * (called indirectly from gtk_tree_model_foreach()) */
1263 static gboolean
tree_model_find_node(GtkTreeModel
*model
,
1270 gtk_tree_model_get(model
, iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
1273 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(tv
.tree_openfiles
), path
);
1274 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tv
.tree_openfiles
), path
, NULL
, FALSE
);
1282 void sidebar_select_openfiles_item(GeanyDocument
*doc
)
1284 gtk_tree_model_foreach(GTK_TREE_MODEL(store_openfiles
), tree_model_find_node
, doc
);
1290 static void document_action(GeanyDocument
*doc
, gint action
)
1292 if (! DOC_VALID(doc
))
1297 case OPENFILES_ACTION_REMOVE
:
1299 document_close(doc
);
1302 case OPENFILES_ACTION_SAVE
:
1304 document_save_file(doc
, FALSE
);
1307 case OPENFILES_ACTION_RELOAD
:
1309 document_reload_prompt(doc
, NULL
);
1316 /* Collects all documents under a given iter, filling @doc_array */
1317 static void on_openfiles_document_action_collect(GtkTreeModel
*model
, GtkTreeIter
*iter
,
1318 GPtrArray
*doc_array
)
1321 gtk_tree_model_get(model
, iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
1324 g_ptr_array_add(doc_array
, doc
);
1328 /* parent item selected */
1331 if (gtk_tree_model_iter_children(model
, &child
, iter
))
1335 on_openfiles_document_action_collect(model
, &child
, doc_array
);
1337 while (gtk_tree_model_iter_next(model
, &child
));
1343 static void on_openfiles_document_action(GtkMenuItem
*menuitem
, gpointer user_data
)
1346 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tv
.tree_openfiles
));
1347 GtkTreeModel
*model
;
1348 gint action
= GPOINTER_TO_INT(user_data
);
1350 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
))
1352 GPtrArray
*doc_array
= g_ptr_array_new();
1354 on_openfiles_document_action_collect(model
, &iter
, doc_array
);
1355 for (guint i
= 0; i
< doc_array
->len
; i
++)
1356 document_action(g_ptr_array_index(doc_array
, i
), action
);
1357 g_ptr_array_free(doc_array
, TRUE
);
1362 static void change_focus_to_editor(GeanyDocument
*doc
, GtkWidget
*source_widget
)
1364 if (may_steal_focus
)
1365 document_try_focus(doc
, source_widget
);
1366 may_steal_focus
= FALSE
;
1370 static gboolean
openfiles_go_to_selection(GtkTreeSelection
*selection
, guint keyval
)
1373 GtkTreeModel
*model
;
1374 GeanyDocument
*doc
= NULL
;
1376 /* use switch_notebook_page to ignore changing the notebook page because it is already done */
1377 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
) && ! ignore_callback
)
1379 gtk_tree_model_get(model
, &iter
, DOCUMENTS_DOCUMENT
, &doc
, -1);
1381 return FALSE
; /* parent */
1383 /* switch to the doc and grab the focus */
1384 document_show_tab(doc
);
1385 if (keyval
!= GDK_KEY_space
)
1386 change_focus_to_editor(doc
, tv
.tree_openfiles
);
1392 static gboolean
taglist_go_to_selection(GtkTreeSelection
*selection
, guint keyval
, guint state
)
1395 GtkTreeModel
*model
;
1397 gboolean handled
= TRUE
;
1399 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
))
1403 gtk_tree_model_get(model
, &iter
, SYMBOLS_COLUMN_TAG
, &tag
, -1);
1410 GeanyDocument
*doc
= document_get_current();
1414 navqueue_goto_line(doc
, doc
, line
);
1415 state
= keybindings_get_modifiers(state
);
1416 if (keyval
!= GDK_KEY_space
&& ! (state
& GEANY_PRIMARY_MOD_MASK
))
1417 change_focus_to_editor(doc
, NULL
);
1428 static gboolean
sidebar_key_press_cb(GtkWidget
*widget
, GdkEventKey
*event
,
1431 may_steal_focus
= FALSE
;
1432 if (ui_is_keyval_enter_or_return(event
->keyval
) || event
->keyval
== GDK_KEY_space
)
1434 GtkWidgetClass
*widget_class
= GTK_WIDGET_GET_CLASS(widget
);
1435 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
1436 may_steal_focus
= TRUE
;
1438 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
1439 * doing so will prevent further handlers to be run in most cases, but the only one is our
1440 * own, so guess it's fine. */
1441 if (widget_class
->key_press_event
)
1442 widget_class
->key_press_event(widget
, event
);
1444 if (widget
== tv
.tree_openfiles
) /* tag and doc list have separate handlers */
1445 openfiles_go_to_selection(selection
, event
->keyval
);
1447 taglist_go_to_selection(selection
, event
->keyval
, event
->state
);
1455 static gboolean
sidebar_button_press_cb(GtkWidget
*widget
, GdkEventButton
*event
,
1456 G_GNUC_UNUSED gpointer user_data
)
1458 GtkTreeSelection
*selection
;
1459 GtkWidgetClass
*widget_class
= GTK_WIDGET_GET_CLASS(widget
);
1460 gboolean handled
= FALSE
;
1462 /* force the TreeView handler to run before us for it to do its job (selection & stuff).
1463 * doing so will prevent further handlers to be run in most cases, but the only one is our own,
1464 * so guess it's fine. */
1465 if (widget_class
->button_press_event
)
1466 handled
= widget_class
->button_press_event(widget
, event
);
1468 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
1469 may_steal_focus
= TRUE
;
1471 if (event
->type
== GDK_2BUTTON_PRESS
)
1472 { /* double click on parent node(section) expands/collapses it */
1473 GtkTreeModel
*model
;
1476 if (gtk_tree_selection_get_selected(selection
, &model
, &iter
))
1478 if (gtk_tree_model_iter_has_child(model
, &iter
))
1480 GtkTreePath
*path
= gtk_tree_model_get_path(model
, &iter
);
1482 if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget
), path
))
1483 gtk_tree_view_collapse_row(GTK_TREE_VIEW(widget
), path
);
1485 gtk_tree_view_expand_row(GTK_TREE_VIEW(widget
), path
, FALSE
);
1487 gtk_tree_path_free(path
);
1492 else if (event
->button
== 1)
1493 { /* allow reclicking of taglist treeview item */
1494 if (widget
== tv
.tree_openfiles
)
1496 openfiles_go_to_selection(selection
, 0);
1500 handled
= taglist_go_to_selection(selection
, 0, event
->state
);
1502 else if (event
->button
== 2)
1504 if (widget
== tv
.tree_openfiles
)
1505 on_openfiles_document_action(NULL
, GINT_TO_POINTER(OPENFILES_ACTION_REMOVE
));
1507 else if (event
->button
== 3)
1509 if (widget
== tv
.tree_openfiles
)
1511 if (!openfiles_popup_menu
)
1512 create_openfiles_popup_menu();
1514 /* update menu item sensitivity */
1515 documents_menu_update(selection
);
1516 gtk_menu_popup_at_pointer(GTK_MENU(openfiles_popup_menu
), (GdkEvent
*) event
);
1520 gtk_menu_popup_at_pointer(GTK_MENU(tv
.popup_taglist
), (GdkEvent
*) event
);
1528 static void documents_menu_update(GtkTreeSelection
*selection
)
1530 GtkTreeModel
*model
;
1533 gchar
*shortname
= NULL
;
1534 GeanyDocument
*doc
= NULL
;
1536 /* maybe no selection e.g. if ctrl-click deselected */
1537 sel
= gtk_tree_selection_get_selected(selection
, &model
, &iter
);
1540 gtk_tree_model_get(model
, &iter
,
1541 DOCUMENTS_DOCUMENT
, &doc
,
1542 DOCUMENTS_SHORTNAME
, &shortname
,
1545 path
= !EMPTY(shortname
) &&
1546 (g_path_is_absolute(shortname
) ||
1547 (app
->project
&& g_str_has_prefix(shortname
, app
->project
->name
)));
1549 /* can close all, save all (except shortname), but only reload individually ATM */
1550 gtk_widget_set_sensitive(doc_items
.close
, sel
);
1551 gtk_widget_set_sensitive(doc_items
.save
, (doc
&& doc
->real_path
) || path
);
1552 gtk_widget_set_sensitive(doc_items
.reload
, doc
&& doc
->real_path
);
1553 gtk_widget_set_sensitive(doc_items
.find_in_files
, sel
);
1556 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(doc_items
.show_paths
[interface_prefs
.openfiles_path_mode
]), TRUE
);
1557 gtk_widget_set_sensitive(doc_items
.expand_all
, interface_prefs
.openfiles_path_mode
);
1558 gtk_widget_set_sensitive(doc_items
.collapse_all
, interface_prefs
.openfiles_path_mode
);
1562 static StashGroup
*stash_group
= NULL
;
1564 static void on_load_settings(void)
1566 if (interface_prefs
.openfiles_path_mode
< 0
1567 || interface_prefs
.openfiles_path_mode
>= OPENFILES_PATHS_COUNT
)
1568 interface_prefs
.openfiles_path_mode
= OPENFILES_PATHS_TREE
;
1570 tag_window
= ui_lookup_widget(main_widgets
.window
, "scrolledwindow2");
1572 prepare_openfiles();
1573 /* note: ui_prefs.sidebar_page is reapplied after plugins are loaded */
1574 stash_group_display(stash_group
, NULL
);
1575 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
1579 static void on_save_settings(void)
1581 stash_group_update(stash_group
, NULL
);
1582 sidebar_tabs_show_hide(GTK_NOTEBOOK(main_widgets
.sidebar_notebook
), NULL
, 0, NULL
);
1586 static void on_sidebar_switch_page(GtkNotebook
*notebook
,
1587 gpointer page
, guint page_num
, gpointer user_data
)
1589 if (page_num
== TREEVIEW_SYMBOL
)
1590 sidebar_update_tag_list(document_get_current(), FALSE
);
1594 void sidebar_init(void)
1598 group
= stash_group_new(PACKAGE
);
1599 stash_group_add_widget_property(group
, &ui_prefs
.sidebar_page
, "sidebar_page", GINT_TO_POINTER(0),
1600 main_widgets
.sidebar_notebook
, "page", 0);
1601 configuration_add_session_group(group
, FALSE
);
1602 stash_group
= group
;
1604 /* Delay building documents treeview until sidebar font has been read and prefs are sanitized */
1605 g_signal_connect(geany_object
, "load-settings", on_load_settings
, NULL
);
1606 g_signal_connect(geany_object
, "save-settings", on_save_settings
, NULL
);
1608 g_signal_connect(main_widgets
.sidebar_notebook
, "page-added",
1609 G_CALLBACK(sidebar_tabs_show_hide
), NULL
);
1610 g_signal_connect(main_widgets
.sidebar_notebook
, "page-removed",
1611 G_CALLBACK(sidebar_tabs_show_hide
), NULL
);
1612 /* tabs may have changed when sidebar is reshown */
1613 g_signal_connect(main_widgets
.sidebar_notebook
, "show",
1614 G_CALLBACK(sidebar_tabs_show_hide
), NULL
);
1615 g_signal_connect_after(main_widgets
.sidebar_notebook
, "switch-page",
1616 G_CALLBACK(on_sidebar_switch_page
), NULL
);
1619 #define WIDGET(w) w && GTK_IS_WIDGET(w)
1621 void sidebar_finalize(void)
1623 if (WIDGET(tv
.default_tag_tree
))
1625 gtk_widget_destroy(tv
.default_tag_tree
); /* make GTK release its references, if any... */
1626 g_object_unref(tv
.default_tag_tree
); /* ...and release our own */
1628 if (WIDGET(tv
.popup_taglist
))
1629 gtk_widget_destroy(tv
.popup_taglist
);
1630 if (WIDGET(openfiles_popup_menu
))
1631 gtk_widget_destroy(openfiles_popup_menu
);
1635 void sidebar_focus_openfiles_tab(void)
1637 if (ui_prefs
.sidebar_visible
&& interface_prefs
.sidebar_openfiles_visible
)
1639 GtkNotebook
*notebook
= GTK_NOTEBOOK(main_widgets
.sidebar_notebook
);
1641 gtk_notebook_set_current_page(notebook
, TREEVIEW_OPENFILES
);
1642 gtk_widget_grab_focus(tv
.tree_openfiles
);
1647 void sidebar_focus_symbols_tab(void)
1649 if (ui_prefs
.sidebar_visible
&& interface_prefs
.sidebar_symbol_visible
)
1651 GtkNotebook
*notebook
= GTK_NOTEBOOK(main_widgets
.sidebar_notebook
);
1652 GtkWidget
*symbol_list_scrollwin
= ui_lookup_widget(main_widgets
.window
, "scrolledwindow2");
1654 gtk_notebook_set_current_page(notebook
, TREEVIEW_SYMBOL
);
1655 gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(symbol_list_scrollwin
)));
1660 static void sidebar_tabs_show_hide(GtkNotebook
*notebook
, GtkWidget
*child
,
1661 guint page_num
, gpointer data
)
1663 gint tabs
= gtk_notebook_get_n_pages(notebook
);
1665 if (interface_prefs
.sidebar_symbol_visible
== FALSE
)
1667 if (interface_prefs
.sidebar_openfiles_visible
== FALSE
)
1670 gtk_notebook_set_show_tabs(notebook
, (tabs
> 1));