2 * filebrowser.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2007-2010 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2007-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,
25 /* Sidebar file browser plugin. */
27 #include "geanyplugin.h"
30 #include <gdk/gdkkeysyms.h>
33 GeanyPlugin
*geany_plugin
;
34 GeanyData
*geany_data
;
35 GeanyFunctions
*geany_functions
;
38 PLUGIN_VERSION_CHECK(175)
40 PLUGIN_SET_INFO(_("File Browser"), _("Adds a file browser tab to the sidebar."), VERSION
,
41 _("The Geany developer team"))
52 PLUGIN_KEY_GROUP(file_browser
, KB_COUNT
)
57 FILEVIEW_COLUMN_ICON
= 0,
59 FILEVIEW_COLUMN_FILENAME
, /* the full filename, including path for display as tooltip */
63 static gboolean fb_set_project_base_path
= FALSE
;
64 static gboolean fb_follow_path
= FALSE
;
65 static gboolean show_hidden_files
= FALSE
;
66 static gboolean hide_object_files
= TRUE
;
68 static GtkWidget
*file_view_vbox
;
69 static GtkWidget
*file_view
;
70 static GtkListStore
*file_store
;
71 static GtkTreeIter
*last_dir_iter
= NULL
;
72 static GtkEntryCompletion
*entry_completion
= NULL
;
74 static GtkWidget
*filter_entry
;
75 static GtkWidget
*path_entry
;
76 static gchar
*current_dir
= NULL
; /* in locale-encoding */
77 static gchar
*open_cmd
; /* in locale-encoding */
78 static gchar
*config_file
;
79 static gchar
*filter
= NULL
;
81 static gint page_number
= 0;
86 GtkWidget
*open_external
;
87 GtkWidget
*find_in_files
;
88 GtkWidget
*show_hidden_files
;
92 static void project_change_cb(GObject
*obj
, GKeyFile
*config
, gpointer data
);
94 PluginCallback plugin_callbacks
[] =
96 { "project-open", (GCallback
) &project_change_cb
, TRUE
, NULL
},
97 { "project-save", (GCallback
) &project_change_cb
, TRUE
, NULL
},
98 { NULL
, NULL
, FALSE
, NULL
}
102 /* Returns: whether name should be hidden. */
103 static gboolean
check_hidden(const gchar
*base_name
)
107 if (! NZV(base_name
))
110 if (base_name
[0] == '.')
113 len
= strlen(base_name
);
114 if (base_name
[len
- 1] == '~')
117 if (hide_object_files
)
119 const gchar
*exts
[] = {".o", ".obj", ".so", ".dll", ".a", ".lib"};
122 exts_len
= G_N_ELEMENTS(exts
);
123 for (i
= 0; i
< exts_len
; i
++)
125 const gchar
*ext
= exts
[i
];
127 if (g_str_has_suffix(base_name
, ext
))
135 /* Returns: whether filename should be removed. */
136 static gboolean
check_filtered(const gchar
*base_name
)
141 if (! utils_str_equal(base_name
, "*") && ! g_pattern_match_simple(filter
, base_name
))
149 /* name is in locale encoding */
150 static void add_item(const gchar
*name
)
153 gchar
*fname
, *utf8_name
, *utf8_fullname
, *sep
;
156 if (! show_hidden_files
&& check_hidden(name
))
159 sep
= (utils_str_equal(current_dir
, "/")) ? "" : G_DIR_SEPARATOR_S
;
160 fname
= g_strconcat(current_dir
, sep
, name
, NULL
);
161 dir
= g_file_test(fname
, G_FILE_TEST_IS_DIR
);
162 utf8_fullname
= utils_get_locale_from_utf8(fname
);
163 utf8_name
= utils_get_utf8_from_locale(name
);
168 if (last_dir_iter
== NULL
)
169 gtk_list_store_prepend(file_store
, &iter
);
172 gtk_list_store_insert_after(file_store
, &iter
, last_dir_iter
);
173 gtk_tree_iter_free(last_dir_iter
);
175 last_dir_iter
= gtk_tree_iter_copy(&iter
);
179 if (check_filtered(utf8_name
))
182 g_free(utf8_fullname
);
185 gtk_list_store_append(file_store
, &iter
);
187 gtk_list_store_set(file_store
, &iter
,
188 FILEVIEW_COLUMN_ICON
, (dir
) ? GTK_STOCK_DIRECTORY
: GTK_STOCK_FILE
,
189 FILEVIEW_COLUMN_NAME
, utf8_name
,
190 FILEVIEW_COLUMN_FILENAME
, utf8_fullname
,
193 g_free(utf8_fullname
);
197 /* adds ".." to the start of the file list */
198 static void add_top_level_entry(void)
203 if (! NZV(g_path_skip_root(current_dir
)))
204 return; /* ignore 'C:\' or '/' */
206 utf8_dir
= g_path_get_dirname(current_dir
);
207 setptr(utf8_dir
, utils_get_utf8_from_locale(utf8_dir
));
209 gtk_list_store_prepend(file_store
, &iter
);
210 last_dir_iter
= gtk_tree_iter_copy(&iter
);
212 gtk_list_store_set(file_store
, &iter
,
213 FILEVIEW_COLUMN_ICON
, GTK_STOCK_DIRECTORY
,
214 FILEVIEW_COLUMN_NAME
, "..",
215 FILEVIEW_COLUMN_FILENAME
, utf8_dir
,
221 static void clear(void)
223 gtk_list_store_clear(file_store
);
225 /* reset the directory item pointer */
226 if (last_dir_iter
!= NULL
)
227 gtk_tree_iter_free(last_dir_iter
);
228 last_dir_iter
= NULL
;
232 /* Reuses list to free each node, so list must be a variable */
233 #define foreach_slist_free(node, list) \
234 for (node = list, list = NULL; g_slist_free_1(list), node != NULL; list = node, node = node->next)
236 /* recreate the tree model from current_dir. */
237 static void refresh(void)
242 /* don't clear when the new path doesn't exist */
243 if (! g_file_test(current_dir
, G_FILE_TEST_EXISTS
))
248 utf8_dir
= utils_get_utf8_from_locale(current_dir
);
249 gtk_entry_set_text(GTK_ENTRY(path_entry
), utf8_dir
);
252 add_top_level_entry(); /* ".." item */
254 list
= utils_get_file_list(current_dir
, NULL
, NULL
);
257 /* free filenames & nodes as we go through the list */
258 foreach_slist_free(node
, list
)
260 gchar
*fname
= node
->data
;
266 gtk_entry_completion_set_model(entry_completion
, GTK_TREE_MODEL(file_store
));
270 static void on_go_home(void)
272 setptr(current_dir
, g_strdup(g_get_home_dir()));
277 /* TODO: use utils_get_default_dir_utf8() */
278 static gchar
*get_default_dir(void)
280 const gchar
*dir
= NULL
;
281 GeanyProject
*project
= geany
->app
->project
;
284 dir
= project
->base_path
;
286 dir
= geany
->prefs
->default_open_path
;
289 return utils_get_locale_from_utf8(dir
);
291 return g_get_current_dir();
295 static void on_current_path(void)
299 GeanyDocument
*doc
= document_get_current();
301 if (doc
== NULL
|| doc
->file_name
== NULL
|| ! g_path_is_absolute(doc
->file_name
))
303 setptr(current_dir
, get_default_dir());
307 fname
= doc
->file_name
;
308 fname
= utils_get_locale_from_utf8(fname
);
309 dir
= g_path_get_dirname(fname
);
312 setptr(current_dir
, dir
);
317 static void on_go_up(void)
319 /* remove the highest directory part (which becomes the basename of current_dir) */
320 setptr(current_dir
, g_path_get_dirname(current_dir
));
325 static gboolean
check_single_selection(GtkTreeSelection
*treesel
)
327 if (gtk_tree_selection_count_selected_rows(treesel
) == 1)
330 ui_set_statusbar(FALSE
, _("Too many items selected!"));
335 /* Returns: TRUE if at least one of selected_items is a folder. */
336 static gboolean
is_folder_selected(GList
*selected_items
)
339 GtkTreeModel
*model
= GTK_TREE_MODEL(file_store
);
340 gboolean dir_found
= FALSE
;
342 for (item
= selected_items
; item
!= NULL
; item
= g_list_next(item
))
346 GtkTreePath
*treepath
;
348 treepath
= (GtkTreePath
*) item
->data
;
349 gtk_tree_model_get_iter(model
, &iter
, treepath
);
350 gtk_tree_model_get(model
, &iter
, FILEVIEW_COLUMN_ICON
, &icon
, -1);
352 if (utils_str_equal(icon
, GTK_STOCK_DIRECTORY
))
364 /* Returns: the full filename in locale encoding. */
365 static gchar
*get_tree_path_filename(GtkTreePath
*treepath
)
367 GtkTreeModel
*model
= GTK_TREE_MODEL(file_store
);
371 gtk_tree_model_get_iter(model
, &iter
, treepath
);
372 gtk_tree_model_get(model
, &iter
, FILEVIEW_COLUMN_FILENAME
, &name
, -1);
374 fname
= utils_get_locale_from_utf8(name
);
381 static void open_external(const gchar
*fname
, gboolean dir_found
)
386 GString
*cmd_str
= g_string_new(open_cmd
);
387 GError
*error
= NULL
;
390 dir
= g_path_get_dirname(fname
);
392 dir
= g_strdup(fname
);
394 utils_string_replace_all(cmd_str
, "%f", fname
);
395 utils_string_replace_all(cmd_str
, "%d", dir
);
397 cmd
= g_string_free(cmd_str
, FALSE
);
398 locale_cmd
= utils_get_locale_from_utf8(cmd
);
399 if (! g_spawn_command_line_async(locale_cmd
, &error
))
401 gchar
*c
= strchr(cmd
, ' ');
405 ui_set_statusbar(TRUE
,
406 _("Could not execute configured external command '%s' (%s)."),
407 cmd
, error
->message
);
416 static void on_external_open(GtkMenuItem
*menuitem
, gpointer user_data
)
418 GtkTreeSelection
*treesel
;
423 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
425 list
= gtk_tree_selection_get_selected_rows(treesel
, &model
);
426 dir_found
= is_folder_selected(list
);
428 if (! dir_found
|| check_single_selection(treesel
))
432 for (item
= list
; item
!= NULL
; item
= g_list_next(item
))
434 GtkTreePath
*treepath
= item
->data
;
435 gchar
*fname
= get_tree_path_filename(treepath
);
437 open_external(fname
, dir_found
);
442 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
447 /* We use document_open_files() as it's more efficient. */
448 static void open_selected_files(GList
*list
, gboolean do_not_focus
)
450 GSList
*files
= NULL
;
454 for (item
= list
; item
!= NULL
; item
= g_list_next(item
))
456 GtkTreePath
*treepath
= item
->data
;
457 gchar
*fname
= get_tree_path_filename(treepath
);
459 files
= g_slist_append(files
, fname
);
461 document_open_files(files
, FALSE
, NULL
, NULL
);
462 doc
= document_get_current();
463 if (doc
!= NULL
&& ! do_not_focus
)
464 keybindings_send_command(GEANY_KEY_GROUP_FOCUS
, GEANY_KEYS_FOCUS_EDITOR
);
466 g_slist_foreach(files
, (GFunc
) g_free
, NULL
); /* free filenames */
471 static void open_folder(GtkTreePath
*treepath
)
473 gchar
*fname
= get_tree_path_filename(treepath
);
475 setptr(current_dir
, fname
);
480 static void on_open_clicked(GtkMenuItem
*menuitem
, gpointer user_data
)
482 GtkTreeSelection
*treesel
;
487 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
489 list
= gtk_tree_selection_get_selected_rows(treesel
, &model
);
490 dir_found
= is_folder_selected(list
);
494 if (check_single_selection(treesel
))
496 GtkTreePath
*treepath
= list
->data
; /* first selected item */
498 open_folder(treepath
);
502 open_selected_files(list
, GPOINTER_TO_INT(user_data
));
504 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
509 static void on_find_in_files(GtkMenuItem
*menuitem
, gpointer user_data
)
511 GtkTreeSelection
*treesel
;
515 gboolean is_dir
= FALSE
;
517 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
518 if (! check_single_selection(treesel
))
521 list
= gtk_tree_selection_get_selected_rows(treesel
, &model
);
522 is_dir
= is_folder_selected(list
);
526 GtkTreePath
*treepath
= list
->data
; /* first selected item */
528 dir
= get_tree_path_filename(treepath
);
531 dir
= g_strdup(current_dir
);
533 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
536 setptr(dir
, utils_get_utf8_from_locale(dir
));
537 search_show_find_in_files_dialog(dir
);
542 static void on_hidden_files_clicked(GtkCheckMenuItem
*item
)
544 show_hidden_files
= gtk_check_menu_item_get_active(item
);
549 static void on_hide_sidebar(void)
551 keybindings_send_command(GEANY_KEY_GROUP_VIEW
, GEANY_KEYS_VIEW_SIDEBAR
);
555 static void on_show_preferences(void)
557 plugin_show_configure(geany_plugin
);
561 static GtkWidget
*create_popup_menu(void)
563 GtkWidget
*item
, *menu
;
565 menu
= gtk_menu_new();
567 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN
, NULL
);
568 gtk_widget_show(item
);
569 gtk_container_add(GTK_CONTAINER(menu
), item
);
570 g_signal_connect(item
, "activate", G_CALLBACK(on_open_clicked
), NULL
);
571 popup_items
.open
= item
;
573 item
= ui_image_menu_item_new(GTK_STOCK_OPEN
, _("Open _externally"));
574 gtk_widget_show(item
);
575 gtk_container_add(GTK_CONTAINER(menu
), item
);
576 g_signal_connect(item
, "activate", G_CALLBACK(on_external_open
), NULL
);
577 popup_items
.open_external
= item
;
579 item
= ui_image_menu_item_new(GTK_STOCK_FIND
, _("_Find in Files"));
580 gtk_widget_show(item
);
581 gtk_container_add(GTK_CONTAINER(menu
), item
);
582 g_signal_connect(item
, "activate", G_CALLBACK(on_find_in_files
), NULL
);
583 popup_items
.find_in_files
= item
;
585 item
= gtk_separator_menu_item_new();
586 gtk_widget_show(item
);
587 gtk_container_add(GTK_CONTAINER(menu
), item
);
589 item
= gtk_check_menu_item_new_with_mnemonic(_("Show _Hidden Files"));
590 gtk_widget_show(item
);
591 gtk_container_add(GTK_CONTAINER(menu
), item
);
592 g_signal_connect(item
, "activate", G_CALLBACK(on_hidden_files_clicked
), NULL
);
593 popup_items
.show_hidden_files
= item
;
595 item
= gtk_separator_menu_item_new();
596 gtk_widget_show(item
);
597 gtk_container_add(GTK_CONTAINER(menu
), item
);
599 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES
, NULL
);
600 gtk_widget_show(item
);
601 gtk_container_add(GTK_CONTAINER(menu
), item
);
602 g_signal_connect(item
, "activate", G_CALLBACK(on_show_preferences
), NULL
);
604 item
= gtk_separator_menu_item_new();
605 gtk_widget_show(item
);
606 gtk_container_add(GTK_CONTAINER(menu
), item
);
608 item
= ui_image_menu_item_new(GTK_STOCK_CLOSE
, _("H_ide Sidebar"));
609 gtk_widget_show(item
);
610 gtk_container_add(GTK_CONTAINER(menu
), item
);
611 g_signal_connect(item
, "activate", G_CALLBACK(on_hide_sidebar
), NULL
);
617 static void on_tree_selection_changed(GtkTreeSelection
*selection
, gpointer data
)
619 gboolean have_sel
= (gtk_tree_selection_count_selected_rows(selection
) > 0);
620 gboolean multi_sel
= (gtk_tree_selection_count_selected_rows(selection
) > 1);
622 if (popup_items
.open
!= NULL
)
623 gtk_widget_set_sensitive(popup_items
.open
, have_sel
);
624 if (popup_items
.open_external
!= NULL
)
625 gtk_widget_set_sensitive(popup_items
.open_external
, have_sel
);
626 if (popup_items
.find_in_files
!= NULL
)
627 gtk_widget_set_sensitive(popup_items
.find_in_files
, have_sel
&& ! multi_sel
);
631 static gboolean
on_button_press(GtkWidget
*widget
, GdkEventButton
*event
, gpointer user_data
)
633 if (event
->button
== 1 && event
->type
== GDK_2BUTTON_PRESS
)
635 on_open_clicked(NULL
, NULL
);
638 else if (event
->button
== 3)
640 static GtkWidget
*popup_menu
= NULL
;
642 if (popup_menu
== NULL
)
643 popup_menu
= create_popup_menu();
645 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(popup_items
.show_hidden_files
),
647 gtk_menu_popup(GTK_MENU(popup_menu
), NULL
, NULL
, NULL
, NULL
, event
->button
, event
->time
);
648 /* don't return TRUE here, unless the selection won't be changed */
654 static gboolean
on_key_press(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
656 if (ui_is_keyval_enter_or_return(event
->keyval
))
658 on_open_clicked(NULL
, NULL
);
662 if (event
->keyval
== GDK_space
)
664 on_open_clicked(NULL
, GINT_TO_POINTER(TRUE
));
668 if ((event
->keyval
== GDK_Up
||
669 event
->keyval
== GDK_KP_Up
) &&
670 (event
->state
& GDK_MOD1_MASK
)) /* FIXME: Alt-Up doesn't seem to work! */
676 if ((event
->keyval
== GDK_F10
&& event
->state
& GDK_SHIFT_MASK
) || event
->keyval
== GDK_Menu
)
678 GdkEventButton button_event
;
680 button_event
.time
= event
->time
;
681 button_event
.button
= 3;
683 on_button_press(widget
, &button_event
, data
);
691 static void on_clear_filter(GtkEntry
*entry
, gpointer user_data
)
693 setptr(filter
, NULL
);
695 gtk_entry_set_text(GTK_ENTRY(filter_entry
), "");
701 static void on_path_entry_activate(GtkEntry
*entry
, gpointer user_data
)
703 gchar
*new_dir
= (gchar
*) gtk_entry_get_text(entry
);
707 if (g_str_has_suffix(new_dir
, ".."))
712 else if (new_dir
[0] == '~')
714 GString
*str
= g_string_new(new_dir
);
715 utils_string_replace_first(str
, "~", g_get_home_dir());
716 new_dir
= g_string_free(str
, FALSE
);
719 new_dir
= utils_get_locale_from_utf8(new_dir
);
722 new_dir
= g_strdup(g_get_home_dir());
724 setptr(current_dir
, new_dir
);
726 on_clear_filter(NULL
, NULL
);
730 static void on_filter_activate(GtkEntry
*entry
, gpointer user_data
)
732 setptr(filter
, g_strdup(gtk_entry_get_text(entry
)));
736 setptr(filter
, g_strdup("*"));
743 static void on_filter_clear(GtkEntry
*entry
, gint icon_pos
,
744 GdkEvent
*event
, gpointer data
)
746 setptr(filter
, g_strdup("*"));
752 static void prepare_file_view(void)
754 GtkCellRenderer
*text_renderer
, *icon_renderer
;
755 GtkTreeViewColumn
*column
;
756 GtkTreeSelection
*selection
;
758 file_store
= gtk_list_store_new(FILEVIEW_N_COLUMNS
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
);
760 gtk_tree_view_set_model(GTK_TREE_VIEW(file_view
), GTK_TREE_MODEL(file_store
));
761 g_object_unref(file_store
);
763 icon_renderer
= gtk_cell_renderer_pixbuf_new();
764 text_renderer
= gtk_cell_renderer_text_new();
765 column
= gtk_tree_view_column_new();
766 gtk_tree_view_column_pack_start(column
, icon_renderer
, FALSE
);
767 gtk_tree_view_column_set_attributes(column
, icon_renderer
, "stock-id", FILEVIEW_COLUMN_ICON
, NULL
);
768 gtk_tree_view_column_pack_start(column
, text_renderer
, TRUE
);
769 gtk_tree_view_column_set_attributes(column
, text_renderer
, "text", FILEVIEW_COLUMN_NAME
, NULL
);
770 gtk_tree_view_append_column(GTK_TREE_VIEW(file_view
), column
);
771 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(file_view
), FALSE
);
773 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(file_view
), TRUE
);
774 gtk_tree_view_set_search_column(GTK_TREE_VIEW(file_view
), FILEVIEW_COLUMN_NAME
);
776 ui_widget_modify_font_from_string(file_view
, geany
->interface_prefs
->tagbar_font
);
778 /* GTK 2.12 tooltips */
779 if (gtk_check_version(2, 12, 0) == NULL
)
780 g_object_set(file_view
, "has-tooltip", TRUE
, "tooltip-column", FILEVIEW_COLUMN_FILENAME
, NULL
);
782 /* selection handling */
783 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
784 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_MULTIPLE
);
786 g_signal_connect(file_view
, "realize", G_CALLBACK(on_current_path
), NULL
);
787 g_signal_connect(selection
, "changed", G_CALLBACK(on_tree_selection_changed
), NULL
);
788 g_signal_connect(file_view
, "button-press-event", G_CALLBACK(on_button_press
), NULL
);
789 g_signal_connect(file_view
, "key-press-event", G_CALLBACK(on_key_press
), NULL
);
793 static GtkWidget
*make_toolbar(void)
795 GtkWidget
*wid
, *toolbar
;
797 toolbar
= gtk_toolbar_new();
798 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar
), GTK_ICON_SIZE_MENU
);
799 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar
), GTK_TOOLBAR_ICONS
);
801 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_GO_UP
));
802 ui_widget_set_tooltip_text(wid
, _("Up"));
803 g_signal_connect(wid
, "clicked", G_CALLBACK(on_go_up
), NULL
);
804 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
806 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH
));
807 ui_widget_set_tooltip_text(wid
, _("Refresh"));
808 g_signal_connect(wid
, "clicked", G_CALLBACK(refresh
), NULL
);
809 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
811 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_HOME
));
812 ui_widget_set_tooltip_text(wid
, _("Home"));
813 g_signal_connect(wid
, "clicked", G_CALLBACK(on_go_home
), NULL
);
814 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
816 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_JUMP_TO
));
817 ui_widget_set_tooltip_text(wid
, _("Set path from document"));
818 g_signal_connect(wid
, "clicked", G_CALLBACK(on_current_path
), NULL
);
819 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
821 if (gtk_check_version(2, 15, 2) != NULL
)
823 wid
= GTK_WIDGET(gtk_separator_tool_item_new());
824 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
826 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_CLEAR
));
827 ui_widget_set_tooltip_text(wid
, _("Clear the filter"));
828 g_signal_connect(wid
, "clicked", G_CALLBACK(on_clear_filter
), NULL
);
829 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
835 static GtkWidget
*make_filterbar(void)
837 GtkWidget
*label
, *filterbar
;
839 filterbar
= gtk_hbox_new(FALSE
, 1);
841 label
= gtk_label_new(_("Filter:"));
843 filter_entry
= gtk_entry_new();
845 if (gtk_check_version(2, 15, 2) == NULL
)
847 ui_entry_add_clear_icon(GTK_ENTRY(filter_entry
));
848 g_signal_connect(filter_entry
, "icon-release", G_CALLBACK(on_filter_clear
), NULL
);
850 ui_widget_set_tooltip_text(filter_entry
,
851 _("Filter your files with usual wildcards"));
852 g_signal_connect(filter_entry
, "activate", G_CALLBACK(on_filter_activate
), NULL
);
854 gtk_box_pack_start(GTK_BOX(filterbar
), label
, FALSE
, FALSE
, 0);
855 gtk_box_pack_start(GTK_BOX(filterbar
), filter_entry
, TRUE
, TRUE
, 0);
861 static gboolean
completion_match_func(GtkEntryCompletion
*completion
, const gchar
*key
,
862 GtkTreeIter
*iter
, gpointer user_data
)
865 gboolean result
= FALSE
;
867 gtk_tree_model_get(GTK_TREE_MODEL(file_store
), iter
,
868 FILEVIEW_COLUMN_ICON
, &icon
, FILEVIEW_COLUMN_NAME
, &str
, -1);
870 if (str
!= NULL
&& icon
!= NULL
&& utils_str_equal(icon
, GTK_STOCK_DIRECTORY
) &&
871 ! g_str_has_suffix(key
, G_DIR_SEPARATOR_S
))
873 /* key is something like "/tmp/te" and str is a filename like "test",
874 * so strip the path from key to make them comparable */
875 gchar
*base_name
= g_path_get_basename(key
);
876 gchar
*str_lowered
= g_utf8_strdown(str
, -1);
877 result
= g_str_has_prefix(str_lowered
, base_name
);
888 static gboolean
completion_match_selected(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
889 GtkTreeIter
*iter
, gpointer user_data
)
892 gtk_tree_model_get(model
, iter
, FILEVIEW_COLUMN_NAME
, &str
, -1);
895 gchar
*text
= g_strconcat(current_dir
, G_DIR_SEPARATOR_S
, str
, NULL
);
896 gtk_entry_set_text(GTK_ENTRY(path_entry
), text
);
897 gtk_editable_set_position(GTK_EDITABLE(path_entry
), -1);
898 /* force change of directory when completion is done */
899 on_path_entry_activate(GTK_ENTRY(path_entry
), NULL
);
908 static void completion_create(void)
910 entry_completion
= gtk_entry_completion_new();
912 gtk_entry_completion_set_inline_completion(entry_completion
, FALSE
);
913 gtk_entry_completion_set_popup_completion(entry_completion
, TRUE
);
914 gtk_entry_completion_set_text_column(entry_completion
, FILEVIEW_COLUMN_NAME
);
915 gtk_entry_completion_set_match_func(entry_completion
, completion_match_func
, NULL
, NULL
);
917 g_signal_connect(entry_completion
, "match-selected",
918 G_CALLBACK(completion_match_selected
), NULL
);
920 gtk_entry_set_completion(GTK_ENTRY(path_entry
), entry_completion
);
924 #define CHECK_READ_SETTING(var, error, tmp) \
925 if ((error) != NULL) \
927 g_error_free((error)); \
933 static void load_settings(void)
935 GKeyFile
*config
= g_key_file_new();
936 GError
*error
= NULL
;
939 config_file
= g_strconcat(geany
->app
->configdir
, G_DIR_SEPARATOR_S
, "plugins", G_DIR_SEPARATOR_S
,
940 "filebrowser", G_DIR_SEPARATOR_S
, "filebrowser.conf", NULL
);
941 g_key_file_load_from_file(config
, config_file
, G_KEY_FILE_NONE
, NULL
);
942 open_cmd
= g_key_file_get_string(config
, "filebrowser", "open_command", &error
);
945 open_cmd
= g_strdup("nautilus \"%d\"");
949 tmp
= g_key_file_get_boolean(config
, "filebrowser", "show_hidden_files", &error
);
950 CHECK_READ_SETTING(show_hidden_files
, error
, tmp
);
951 tmp
= g_key_file_get_boolean(config
, "filebrowser", "hide_object_files", &error
);
952 CHECK_READ_SETTING(hide_object_files
, error
, tmp
);
953 tmp
= g_key_file_get_boolean(config
, "filebrowser", "fb_follow_path", &error
);
954 CHECK_READ_SETTING(fb_follow_path
, error
, tmp
);
955 tmp
= g_key_file_get_boolean(config
, "filebrowser", "fb_set_project_base_path", &error
);
956 CHECK_READ_SETTING(fb_set_project_base_path
, error
, tmp
);
958 g_key_file_free(config
);
962 static void project_change_cb(G_GNUC_UNUSED GObject
*obj
, G_GNUC_UNUSED GKeyFile
*config
,
963 G_GNUC_UNUSED gpointer data
)
966 GeanyProject
*project
= geany
->app
->project
;
968 if (! fb_set_project_base_path
|| project
== NULL
|| ! NZV(project
->base_path
))
971 /* TODO this is a copy of project_get_base_path(), add it to the plugin API */
972 if (g_path_is_absolute(project
->base_path
))
973 new_dir
= g_strdup(project
->base_path
);
975 { /* build base_path out of project file name's dir and base_path */
976 gchar
*dir
= g_path_get_dirname(project
->file_name
);
978 new_dir
= g_strconcat(dir
, G_DIR_SEPARATOR_S
, project
->base_path
, NULL
);
981 /* get it into locale encoding */
982 setptr(new_dir
, utils_get_locale_from_utf8(new_dir
));
984 if (! utils_str_equal(current_dir
, new_dir
))
986 setptr(current_dir
, new_dir
);
994 static void document_activate_cb(G_GNUC_UNUSED GObject
*obj
, GeanyDocument
*doc
,
995 G_GNUC_UNUSED gpointer data
)
999 if (! fb_follow_path
|| doc
->file_name
== NULL
|| ! g_path_is_absolute(doc
->file_name
))
1002 new_dir
= g_path_get_dirname(doc
->file_name
);
1003 setptr(new_dir
, utils_get_locale_from_utf8(new_dir
));
1005 if (! utils_str_equal(current_dir
, new_dir
))
1007 setptr(current_dir
, new_dir
);
1015 static void kb_activate(guint key_id
)
1017 gtk_notebook_set_current_page(GTK_NOTEBOOK(geany
->main_widgets
->sidebar_notebook
), page_number
);
1020 case KB_FOCUS_FILE_LIST
:
1021 gtk_widget_grab_focus(file_view
);
1023 case KB_FOCUS_PATH_ENTRY
:
1024 gtk_widget_grab_focus(path_entry
);
1030 void plugin_init(GeanyData
*data
)
1032 GtkWidget
*scrollwin
, *toolbar
, *filterbar
;
1036 file_view_vbox
= gtk_vbox_new(FALSE
, 0);
1037 toolbar
= make_toolbar();
1038 gtk_box_pack_start(GTK_BOX(file_view_vbox
), toolbar
, FALSE
, FALSE
, 0);
1040 filterbar
= make_filterbar();
1041 gtk_box_pack_start(GTK_BOX(file_view_vbox
), filterbar
, FALSE
, FALSE
, 0);
1043 path_entry
= gtk_entry_new();
1044 gtk_box_pack_start(GTK_BOX(file_view_vbox
), path_entry
, FALSE
, FALSE
, 2);
1045 g_signal_connect(path_entry
, "activate", G_CALLBACK(on_path_entry_activate
), NULL
);
1047 file_view
= gtk_tree_view_new();
1048 prepare_file_view();
1049 completion_create();
1051 popup_items
.open
= popup_items
.open_external
= popup_items
.find_in_files
= NULL
;
1053 scrollwin
= gtk_scrolled_window_new(NULL
, NULL
);
1054 gtk_scrolled_window_set_policy(
1055 GTK_SCROLLED_WINDOW(scrollwin
),
1056 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
1057 gtk_container_add(GTK_CONTAINER(scrollwin
), file_view
);
1058 gtk_container_add(GTK_CONTAINER(file_view_vbox
), scrollwin
);
1060 gtk_widget_show_all(file_view_vbox
);
1061 page_number
= gtk_notebook_append_page(GTK_NOTEBOOK(geany
->main_widgets
->sidebar_notebook
),
1062 file_view_vbox
, gtk_label_new(_("Files")));
1066 /* setup keybindings */
1067 keybindings_set_item(plugin_key_group
, KB_FOCUS_FILE_LIST
, kb_activate
,
1068 0, 0, "focus_file_list", _("Focus File List"), NULL
);
1069 keybindings_set_item(plugin_key_group
, KB_FOCUS_PATH_ENTRY
, kb_activate
,
1070 0, 0, "focus_path_entry", _("Focus Path Entry"), NULL
);
1072 plugin_signal_connect(geany_plugin
, NULL
, "document-activate", TRUE
,
1073 (GCallback
) &document_activate_cb
, NULL
);
1077 static void save_settings(void)
1079 GKeyFile
*config
= g_key_file_new();
1081 gchar
*config_dir
= g_path_get_dirname(config_file
);
1083 g_key_file_load_from_file(config
, config_file
, G_KEY_FILE_NONE
, NULL
);
1085 g_key_file_set_string(config
, "filebrowser", "open_command", open_cmd
);
1086 g_key_file_set_boolean(config
, "filebrowser", "show_hidden_files", show_hidden_files
);
1087 g_key_file_set_boolean(config
, "filebrowser", "hide_object_files", hide_object_files
);
1088 g_key_file_set_boolean(config
, "filebrowser", "fb_follow_path", fb_follow_path
);
1089 g_key_file_set_boolean(config
, "filebrowser", "fb_set_project_base_path",
1090 fb_set_project_base_path
);
1092 if (! g_file_test(config_dir
, G_FILE_TEST_IS_DIR
) && utils_mkdir(config_dir
, TRUE
) != 0)
1094 dialogs_show_msgbox(GTK_MESSAGE_ERROR
,
1095 _("Plugin configuration directory could not be created."));
1099 /* write config to file */
1100 data
= g_key_file_to_data(config
, NULL
, NULL
);
1101 utils_write_file(config_file
, data
);
1105 g_key_file_free(config
);
1111 GtkWidget
*open_cmd_entry
;
1112 GtkWidget
*show_hidden_checkbox
;
1113 GtkWidget
*hide_objects_checkbox
;
1114 GtkWidget
*follow_path_checkbox
;
1115 GtkWidget
*set_project_base_path_checkbox
;
1120 on_configure_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1122 if (response
== GTK_RESPONSE_OK
|| response
== GTK_RESPONSE_APPLY
)
1125 open_cmd
= g_strdup(gtk_entry_get_text(GTK_ENTRY(pref_widgets
.open_cmd_entry
)));
1126 show_hidden_files
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets
.show_hidden_checkbox
));
1127 hide_object_files
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets
.hide_objects_checkbox
));
1128 fb_follow_path
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets
.follow_path_checkbox
));
1129 fb_set_project_base_path
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1130 pref_widgets
.set_project_base_path_checkbox
));
1132 /* apply the changes */
1138 GtkWidget
*plugin_configure(GtkDialog
*dialog
)
1140 GtkWidget
*label
, *entry
, *checkbox_of
, *checkbox_hf
, *checkbox_fp
, *checkbox_pb
, *vbox
;
1143 vbox
= gtk_vbox_new(FALSE
, 6);
1144 box
= gtk_vbox_new(FALSE
, 3);
1146 label
= gtk_label_new(_("External open command:"));
1147 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
1148 gtk_box_pack_start(GTK_BOX(box
), label
, FALSE
, FALSE
, 0);
1150 entry
= gtk_entry_new();
1151 gtk_widget_show(entry
);
1152 if (open_cmd
!= NULL
)
1153 gtk_entry_set_text(GTK_ENTRY(entry
), open_cmd
);
1154 ui_widget_set_tooltip_text(entry
,
1155 _("The command to execute when using \"Open with\". You can use %f and %d wildcards.\n"
1156 "%f will be replaced with the filename including full path\n"
1157 "%d will be replaced with the path name of the selected file without the filename"));
1158 gtk_box_pack_start(GTK_BOX(box
), entry
, FALSE
, FALSE
, 0);
1159 pref_widgets
.open_cmd_entry
= entry
;
1161 gtk_box_pack_start(GTK_BOX(vbox
), box
, FALSE
, FALSE
, 6);
1163 checkbox_hf
= gtk_check_button_new_with_label(_("Show hidden files"));
1164 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_hf
), FALSE
);
1165 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_hf
), show_hidden_files
);
1166 gtk_box_pack_start(GTK_BOX(vbox
), checkbox_hf
, FALSE
, FALSE
, 0);
1167 pref_widgets
.show_hidden_checkbox
= checkbox_hf
;
1169 checkbox_of
= gtk_check_button_new_with_label(_("Hide object files"));
1170 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_of
), FALSE
);
1171 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_of
), hide_object_files
);
1172 ui_widget_set_tooltip_text(checkbox_of
,
1173 _("Don't show generated object files in the file browser, this includes "
1174 "*.o, *.obj. *.so, *.dll, *.a, *.lib"));
1175 gtk_box_pack_start(GTK_BOX(vbox
), checkbox_of
, FALSE
, FALSE
, 0);
1176 pref_widgets
.hide_objects_checkbox
= checkbox_of
;
1178 checkbox_fp
= gtk_check_button_new_with_label(_("Follow the path of the current file"));
1179 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_fp
), FALSE
);
1180 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_fp
), fb_follow_path
);
1181 gtk_box_pack_start(GTK_BOX(vbox
), checkbox_fp
, FALSE
, FALSE
, 0);
1182 pref_widgets
.follow_path_checkbox
= checkbox_fp
;
1184 checkbox_pb
= gtk_check_button_new_with_label(_("Use the project's base directory"));
1185 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_pb
), FALSE
);
1186 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_pb
), fb_set_project_base_path
);
1187 ui_widget_set_tooltip_text(checkbox_pb
,
1188 _("Change the directory to the base directory of the currently opened project"));
1189 gtk_box_pack_start(GTK_BOX(vbox
), checkbox_pb
, FALSE
, FALSE
, 0);
1190 pref_widgets
.set_project_base_path_checkbox
= checkbox_pb
;
1192 gtk_widget_show_all(vbox
);
1194 g_signal_connect(dialog
, "response", G_CALLBACK(on_configure_response
), NULL
);
1199 void plugin_cleanup(void)
1203 g_free(config_file
);
1206 gtk_widget_destroy(file_view_vbox
);
1207 g_object_unref(G_OBJECT(entry_completion
));