2 * filebrowser.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2007 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.
21 /* Sidebar file browser plugin. */
27 #include "geanyplugin.h"
28 #include "gtkcompat.h"
31 #include <gdk/gdkkeysyms.h>
36 # define OPEN_CMD "explorer \"%d\""
37 #elif defined(__APPLE__)
38 # define OPEN_CMD "open \"%d\""
40 # define OPEN_CMD "nautilus \"%d\""
43 GeanyPlugin
*geany_plugin
;
44 GeanyData
*geany_data
;
47 PLUGIN_VERSION_CHECK(GEANY_API_VERSION
)
49 PLUGIN_SET_INFO(_("File Browser"), _("Adds a file browser tab to the sidebar."),
50 PACKAGE_VERSION
, _("The Geany developer team"))
64 FILEVIEW_COLUMN_ICON
= 0,
66 FILEVIEW_COLUMN_FILENAME
, /* the full filename, including path for display as tooltip */
67 FILEVIEW_COLUMN_IS_DIR
,
71 static gboolean fb_set_project_base_path
= FALSE
;
72 static gboolean fb_follow_path
= FALSE
;
73 static gboolean show_hidden_files
= FALSE
;
74 static gboolean hide_object_files
= TRUE
;
76 static GtkWidget
*file_view_vbox
;
77 static GtkWidget
*file_view
;
78 static GtkListStore
*file_store
;
79 static GtkTreeIter
*last_dir_iter
= NULL
;
80 static GtkEntryCompletion
*entry_completion
= NULL
;
82 static GtkWidget
*filter_combo
;
83 static GtkWidget
*filter_entry
;
84 static GtkWidget
*path_combo
;
85 static GtkWidget
*path_entry
;
86 static gchar
*current_dir
= NULL
; /* in locale-encoding */
87 static gchar
*open_cmd
; /* in locale-encoding */
88 static gchar
*config_file
;
89 static gchar
**filter
= NULL
;
90 static gchar
*hidden_file_extensions
= NULL
;
92 static gint page_number
= 0;
97 GtkWidget
*open_external
;
98 GtkWidget
*find_in_files
;
99 GtkWidget
*show_hidden_files
;
103 static void project_open_cb(GObject
*obj
, GKeyFile
*config
, gpointer data
);
105 /* note: other callbacks connected in plugin_init */
106 PluginCallback plugin_callbacks
[] =
108 { "project-open", (GCallback
) &project_open_cb
, TRUE
, NULL
},
109 { NULL
, NULL
, FALSE
, NULL
}
114 static gboolean
win32_check_hidden(const gchar
*filename
)
117 static wchar_t w_filename
[MAX_PATH
];
118 MultiByteToWideChar(CP_UTF8
, 0, filename
, -1, w_filename
, sizeof(w_filename
));
119 attrs
= GetFileAttributesW(w_filename
);
120 if (attrs
!= INVALID_FILE_ATTRIBUTES
&& attrs
& FILE_ATTRIBUTE_HIDDEN
)
127 /* Returns: whether name should be hidden. */
128 static gboolean
check_hidden(const gchar
*filename
, const gchar
*base_name
)
133 if (win32_check_hidden(filename
))
136 if (base_name
[0] == '.')
140 len
= strlen(base_name
);
141 return base_name
[len
- 1] == '~';
145 static gboolean
check_object(const gchar
*base_name
)
147 gboolean ret
= FALSE
;
149 gchar
**exts
= g_strsplit(hidden_file_extensions
, " ", -1);
151 foreach_strv(ptr
, exts
)
153 if (g_str_has_suffix(base_name
, *ptr
))
164 /* Returns: whether filename should be removed. */
165 static gboolean
check_filtered(const gchar
*base_name
)
172 foreach_strv(filter_item
, filter
)
174 if (utils_str_equal(*filter_item
, "*") || g_pattern_match_simple(*filter_item
, base_name
))
183 static GIcon
*get_icon(const gchar
*fname
)
188 ctype
= g_content_type_guess(fname
, NULL
, 0, NULL
);
192 icon
= g_content_type_get_icon(ctype
);
195 GtkIconInfo
*icon_info
;
197 icon_info
= gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon
, 16, 0);
200 g_object_unref(icon
);
204 gtk_icon_info_free(icon_info
);
210 icon
= g_themed_icon_new("text-x-generic");
216 /* name is in locale encoding */
217 static void add_item(const gchar
*name
)
220 gchar
*fname
, *utf8_name
, *utf8_fullname
;
225 if (G_UNLIKELY(EMPTY(name
)))
228 /* root directory doesn't need separator */
229 sep
= (utils_str_equal(current_dir
, "/")) ? "" : G_DIR_SEPARATOR_S
;
230 fname
= g_strconcat(current_dir
, sep
, name
, NULL
);
231 dir
= g_file_test(fname
, G_FILE_TEST_IS_DIR
);
232 utf8_fullname
= utils_get_utf8_from_locale(fname
);
233 utf8_name
= utils_get_utf8_from_locale(name
);
236 if (! show_hidden_files
&& check_hidden(utf8_fullname
, utf8_name
))
241 if (last_dir_iter
== NULL
)
242 gtk_list_store_prepend(file_store
, &iter
);
245 gtk_list_store_insert_after(file_store
, &iter
, last_dir_iter
);
246 gtk_tree_iter_free(last_dir_iter
);
248 last_dir_iter
= gtk_tree_iter_copy(&iter
);
252 if (! show_hidden_files
&& hide_object_files
&& check_object(utf8_name
))
254 if (check_filtered(utf8_name
))
257 gtk_list_store_append(file_store
, &iter
);
260 icon
= dir
? g_themed_icon_new("folder") : get_icon(utf8_name
);
261 gtk_list_store_set(file_store
, &iter
,
262 FILEVIEW_COLUMN_ICON
, icon
,
263 FILEVIEW_COLUMN_NAME
, utf8_name
,
264 FILEVIEW_COLUMN_FILENAME
, utf8_fullname
,
265 FILEVIEW_COLUMN_IS_DIR
, dir
,
267 g_object_unref(icon
);
270 g_free(utf8_fullname
);
274 /* adds ".." to the start of the file list */
275 static void add_top_level_entry(void)
281 if (EMPTY(g_path_skip_root(current_dir
)))
282 return; /* ignore 'C:\' or '/' */
284 utf8_dir
= g_path_get_dirname(current_dir
);
285 SETPTR(utf8_dir
, utils_get_utf8_from_locale(utf8_dir
));
287 gtk_list_store_prepend(file_store
, &iter
);
288 last_dir_iter
= gtk_tree_iter_copy(&iter
);
290 icon
= g_themed_icon_new("folder");
291 gtk_list_store_set(file_store
, &iter
,
292 FILEVIEW_COLUMN_ICON
, icon
,
293 FILEVIEW_COLUMN_NAME
, "..",
294 FILEVIEW_COLUMN_FILENAME
, utf8_dir
,
295 FILEVIEW_COLUMN_IS_DIR
, TRUE
,
297 g_object_unref(icon
);
302 static void clear(void)
304 gtk_list_store_clear(file_store
);
306 /* reset the directory item pointer */
307 if (last_dir_iter
!= NULL
)
308 gtk_tree_iter_free(last_dir_iter
);
309 last_dir_iter
= NULL
;
313 /* recreate the tree model from current_dir. */
314 static void refresh(void)
319 /* don't clear when the new path doesn't exist */
320 if (! g_file_test(current_dir
, G_FILE_TEST_EXISTS
))
325 utf8_dir
= utils_get_utf8_from_locale(current_dir
);
326 gtk_entry_set_text(GTK_ENTRY(path_entry
), utf8_dir
);
327 gtk_widget_set_tooltip_text(path_entry
, utf8_dir
);
328 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(path_combo
), utf8_dir
, 0);
331 add_top_level_entry(); /* ".." item */
333 list
= utils_get_file_list(current_dir
, NULL
, NULL
);
336 /* free filenames as we go through the list */
337 foreach_slist(node
, list
)
339 gchar
*fname
= node
->data
;
346 gtk_entry_completion_set_model(entry_completion
, GTK_TREE_MODEL(file_store
));
350 static void on_go_home(void)
352 SETPTR(current_dir
, g_strdup(g_get_home_dir()));
357 /* TODO: use utils_get_default_dir_utf8() */
358 static gchar
*get_default_dir(void)
360 const gchar
*dir
= NULL
;
361 GeanyProject
*project
= geany
->app
->project
;
364 dir
= project
->base_path
;
366 dir
= geany
->prefs
->default_open_path
;
369 return utils_get_locale_from_utf8(dir
);
371 return g_get_current_dir();
375 static void on_current_path(void)
379 GeanyDocument
*doc
= document_get_current();
381 if (doc
== NULL
|| doc
->file_name
== NULL
|| ! g_path_is_absolute(doc
->file_name
))
383 SETPTR(current_dir
, get_default_dir());
387 fname
= doc
->file_name
;
388 fname
= utils_get_locale_from_utf8(fname
);
389 dir
= g_path_get_dirname(fname
);
392 SETPTR(current_dir
, dir
);
397 static void on_realized(void)
399 GeanyProject
*project
= geany
->app
->project
;
401 /* if fb_set_project_base_path and project open, the path has already been set */
402 if (! fb_set_project_base_path
|| project
== NULL
|| EMPTY(project
->base_path
))
407 static void on_go_up(void)
409 gsize len
= strlen(current_dir
);
410 if (current_dir
[len
-1] == G_DIR_SEPARATOR
)
411 current_dir
[len
-1] = '\0';
412 /* remove the highest directory part (which becomes the basename of current_dir) */
413 SETPTR(current_dir
, g_path_get_dirname(current_dir
));
418 static gboolean
check_single_selection(GtkTreeSelection
*treesel
)
420 if (gtk_tree_selection_count_selected_rows(treesel
) == 1)
423 ui_set_statusbar(FALSE
, _("Too many items selected!"));
428 /* Returns: TRUE if at least one of selected_items is a folder. */
429 static gboolean
is_folder_selected(GList
*selected_items
)
432 GtkTreeModel
*model
= GTK_TREE_MODEL(file_store
);
433 gboolean dir_found
= FALSE
;
435 for (item
= selected_items
; item
!= NULL
; item
= g_list_next(item
))
438 GtkTreePath
*treepath
;
440 treepath
= (GtkTreePath
*) item
->data
;
441 gtk_tree_model_get_iter(model
, &iter
, treepath
);
442 gtk_tree_model_get(model
, &iter
, FILEVIEW_COLUMN_IS_DIR
, &dir_found
, -1);
451 /* Returns: the full filename in locale encoding. */
452 static gchar
*get_tree_path_filename(GtkTreePath
*treepath
)
454 GtkTreeModel
*model
= GTK_TREE_MODEL(file_store
);
458 gtk_tree_model_get_iter(model
, &iter
, treepath
);
459 gtk_tree_model_get(model
, &iter
, FILEVIEW_COLUMN_FILENAME
, &name
, -1);
461 fname
= utils_get_locale_from_utf8(name
);
468 static void open_external(const gchar
*fname
, gboolean dir_found
)
473 GString
*cmd_str
= g_string_new(open_cmd
);
474 GError
*error
= NULL
;
477 dir
= g_path_get_dirname(fname
);
479 dir
= g_strdup(fname
);
481 utils_string_replace_all(cmd_str
, "%f", fname
);
482 utils_string_replace_all(cmd_str
, "%d", dir
);
484 cmd
= g_string_free(cmd_str
, FALSE
);
485 locale_cmd
= utils_get_locale_from_utf8(cmd
);
486 if (! spawn_async(NULL
, locale_cmd
, NULL
, NULL
, NULL
, &error
))
488 gchar
*c
= strchr(cmd
, ' ');
492 ui_set_statusbar(TRUE
,
493 _("Could not execute configured external command '%s' (%s)."),
494 cmd
, error
->message
);
503 static void on_external_open(GtkMenuItem
*menuitem
, gpointer user_data
)
505 GtkTreeSelection
*treesel
;
510 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
512 list
= gtk_tree_selection_get_selected_rows(treesel
, &model
);
513 dir_found
= is_folder_selected(list
);
515 if (! dir_found
|| check_single_selection(treesel
))
519 for (item
= list
; item
!= NULL
; item
= g_list_next(item
))
521 GtkTreePath
*treepath
= item
->data
;
522 gchar
*fname
= get_tree_path_filename(treepath
);
524 open_external(fname
, dir_found
);
529 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
534 /* We use document_open_files() as it's more efficient. */
535 static void open_selected_files(GList
*list
, gboolean do_not_focus
)
537 GSList
*files
= NULL
;
541 for (item
= list
; item
!= NULL
; item
= g_list_next(item
))
543 GtkTreePath
*treepath
= item
->data
;
544 gchar
*fname
= get_tree_path_filename(treepath
);
546 files
= g_slist_prepend(files
, fname
);
548 files
= g_slist_reverse(files
);
549 document_open_files(files
, FALSE
, NULL
, NULL
);
550 doc
= document_get_current();
551 if (doc
!= NULL
&& ! do_not_focus
)
552 keybindings_send_command(GEANY_KEY_GROUP_FOCUS
, GEANY_KEYS_FOCUS_EDITOR
);
554 g_slist_foreach(files
, (GFunc
) g_free
, NULL
); /* free filenames */
559 static void open_folder(GtkTreePath
*treepath
)
561 gchar
*fname
= get_tree_path_filename(treepath
);
563 SETPTR(current_dir
, fname
);
568 static void on_open_clicked(GtkMenuItem
*menuitem
, gpointer user_data
)
570 GtkTreeSelection
*treesel
;
575 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
577 list
= gtk_tree_selection_get_selected_rows(treesel
, &model
);
578 dir_found
= is_folder_selected(list
);
582 if (check_single_selection(treesel
))
584 GtkTreePath
*treepath
= list
->data
; /* first selected item */
586 open_folder(treepath
);
590 open_selected_files(list
, GPOINTER_TO_INT(user_data
));
592 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
597 static void on_find_in_files(GtkMenuItem
*menuitem
, gpointer user_data
)
599 GtkTreeSelection
*treesel
;
603 gboolean is_dir
= FALSE
;
605 treesel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
606 /* allow 0 or 1 selections */
607 if (gtk_tree_selection_count_selected_rows(treesel
) > 0 &&
608 ! check_single_selection(treesel
))
611 list
= gtk_tree_selection_get_selected_rows(treesel
, &model
);
612 is_dir
= is_folder_selected(list
);
616 GtkTreePath
*treepath
= list
->data
; /* first selected item */
618 dir
= get_tree_path_filename(treepath
);
621 dir
= g_strdup(current_dir
);
623 g_list_foreach(list
, (GFunc
) gtk_tree_path_free
, NULL
);
626 SETPTR(dir
, utils_get_utf8_from_locale(dir
));
627 search_show_find_in_files_dialog(dir
);
632 static void on_hidden_files_clicked(GtkCheckMenuItem
*item
)
634 show_hidden_files
= gtk_check_menu_item_get_active(item
);
639 static void on_hide_sidebar(void)
641 keybindings_send_command(GEANY_KEY_GROUP_VIEW
, GEANY_KEYS_VIEW_SIDEBAR
);
645 static void on_show_preferences(void)
647 plugin_show_configure(geany_plugin
);
651 static GtkWidget
*create_popup_menu(void)
653 GtkWidget
*item
, *menu
;
655 menu
= gtk_menu_new();
657 item
= ui_image_menu_item_new(GTK_STOCK_OPEN
, _("Open in _Geany"));
658 gtk_widget_show(item
);
659 gtk_container_add(GTK_CONTAINER(menu
), item
);
660 g_signal_connect(item
, "activate", G_CALLBACK(on_open_clicked
), NULL
);
661 popup_items
.open
= item
;
663 item
= ui_image_menu_item_new(GTK_STOCK_OPEN
, _("Open _Externally"));
664 gtk_widget_show(item
);
665 gtk_container_add(GTK_CONTAINER(menu
), item
);
666 g_signal_connect(item
, "activate", G_CALLBACK(on_external_open
), NULL
);
667 popup_items
.open_external
= item
;
669 item
= gtk_separator_menu_item_new();
670 gtk_widget_show(item
);
671 gtk_container_add(GTK_CONTAINER(menu
), item
);
673 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_REFRESH
, NULL
);
674 gtk_widget_show(item
);
675 gtk_container_add(GTK_CONTAINER(menu
), item
);
676 g_signal_connect(item
, "activate", G_CALLBACK(refresh
), NULL
);
678 item
= ui_image_menu_item_new(GTK_STOCK_FIND
, _("_Find in Files..."));
679 gtk_widget_show(item
);
680 gtk_container_add(GTK_CONTAINER(menu
), item
);
681 g_signal_connect(item
, "activate", G_CALLBACK(on_find_in_files
), NULL
);
682 popup_items
.find_in_files
= item
;
684 item
= gtk_separator_menu_item_new();
685 gtk_widget_show(item
);
686 gtk_container_add(GTK_CONTAINER(menu
), item
);
688 item
= gtk_check_menu_item_new_with_mnemonic(_("Show _Hidden Files"));
689 gtk_widget_show(item
);
690 gtk_container_add(GTK_CONTAINER(menu
), item
);
691 g_signal_connect(item
, "activate", G_CALLBACK(on_hidden_files_clicked
), NULL
);
692 popup_items
.show_hidden_files
= item
;
694 item
= gtk_separator_menu_item_new();
695 gtk_widget_show(item
);
696 gtk_container_add(GTK_CONTAINER(menu
), item
);
698 item
= gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES
, NULL
);
699 gtk_widget_show(item
);
700 gtk_container_add(GTK_CONTAINER(menu
), item
);
701 g_signal_connect(item
, "activate", G_CALLBACK(on_show_preferences
), NULL
);
703 item
= gtk_separator_menu_item_new();
704 gtk_widget_show(item
);
705 gtk_container_add(GTK_CONTAINER(menu
), item
);
707 item
= ui_image_menu_item_new(GTK_STOCK_CLOSE
, _("H_ide Sidebar"));
708 gtk_widget_show(item
);
709 gtk_container_add(GTK_CONTAINER(menu
), item
);
710 g_signal_connect(item
, "activate", G_CALLBACK(on_hide_sidebar
), NULL
);
716 static void on_tree_selection_changed(GtkTreeSelection
*selection
, gpointer data
)
718 gboolean have_sel
= (gtk_tree_selection_count_selected_rows(selection
) > 0);
719 gboolean multi_sel
= (gtk_tree_selection_count_selected_rows(selection
) > 1);
721 if (popup_items
.open
!= NULL
)
722 gtk_widget_set_sensitive(popup_items
.open
, have_sel
);
723 if (popup_items
.open_external
!= NULL
)
724 gtk_widget_set_sensitive(popup_items
.open_external
, have_sel
);
725 if (popup_items
.find_in_files
!= NULL
)
726 gtk_widget_set_sensitive(popup_items
.find_in_files
, have_sel
&& ! multi_sel
);
730 static gboolean
on_button_press(GtkWidget
*widget
, GdkEventButton
*event
, gpointer user_data
)
732 if (event
->button
== 1 && event
->type
== GDK_2BUTTON_PRESS
)
734 on_open_clicked(NULL
, NULL
);
737 else if (event
->button
== 3)
739 static GtkWidget
*popup_menu
= NULL
;
741 if (popup_menu
== NULL
)
742 popup_menu
= create_popup_menu();
744 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(popup_items
.show_hidden_files
),
746 gtk_menu_popup(GTK_MENU(popup_menu
), NULL
, NULL
, NULL
, NULL
, event
->button
, event
->time
);
747 /* don't return TRUE here, unless the selection won't be changed */
753 static gboolean
on_key_press(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
755 if (ui_is_keyval_enter_or_return(event
->keyval
))
757 on_open_clicked(NULL
, NULL
);
761 if (event
->keyval
== GDK_KEY_space
)
763 on_open_clicked(NULL
, GINT_TO_POINTER(TRUE
));
767 if (( (event
->keyval
== GDK_KEY_Up
|| event
->keyval
== GDK_KEY_KP_Up
) && (event
->state
& GDK_MOD1_MASK
)) || /* FIXME: Alt-Up doesn't seem to work! */
768 (event
->keyval
== GDK_KEY_BackSpace
) )
774 if ((event
->keyval
== GDK_KEY_F10
&& event
->state
& GDK_SHIFT_MASK
) || event
->keyval
== GDK_KEY_Menu
)
776 GdkEventButton button_event
;
778 button_event
.time
= event
->time
;
779 button_event
.button
= 3;
781 on_button_press(widget
, &button_event
, data
);
789 static void clear_filter(void)
799 static void on_clear_filter(GtkEntry
*entry
, gpointer user_data
)
803 gtk_entry_set_text(GTK_ENTRY(filter_entry
), "");
809 static void on_path_entry_activate(GtkEntry
*entry
, gpointer user_data
)
811 gchar
*new_dir
= (gchar
*) gtk_entry_get_text(entry
);
815 if (g_str_has_suffix(new_dir
, ".."))
820 else if (new_dir
[0] == '~')
822 GString
*str
= g_string_new(new_dir
);
823 utils_string_replace_first(str
, "~", g_get_home_dir());
824 new_dir
= g_string_free(str
, FALSE
);
827 new_dir
= utils_get_locale_from_utf8(new_dir
);
830 new_dir
= g_strdup(g_get_home_dir());
832 SETPTR(current_dir
, new_dir
);
834 on_clear_filter(NULL
, NULL
);
838 static void ui_combo_box_changed(GtkComboBox
*combo
, gpointer user_data
)
840 /* we get this callback on typing as well as choosing an item */
841 if (gtk_combo_box_get_active(combo
) >= 0)
842 gtk_widget_activate(gtk_bin_get_child(GTK_BIN(combo
)));
846 static void on_filter_activate(GtkEntry
*entry
, gpointer user_data
)
848 /* We use spaces for consistency with Find in Files file patterns
849 * ';' also supported like original patch. */
850 filter
= g_strsplit_set(gtk_entry_get_text(entry
), "; ", -1);
851 if (filter
== NULL
|| g_strv_length(filter
) == 0)
855 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(filter_combo
), NULL
, 0);
860 static void on_filter_clear(GtkEntry
*entry
, gint icon_pos
,
861 GdkEvent
*event
, gpointer data
)
868 static void prepare_file_view(void)
870 GtkCellRenderer
*text_renderer
, *icon_renderer
;
871 GtkTreeViewColumn
*column
;
872 GtkTreeSelection
*selection
;
874 file_store
= gtk_list_store_new(FILEVIEW_N_COLUMNS
, G_TYPE_ICON
, G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_BOOLEAN
);
876 gtk_tree_view_set_model(GTK_TREE_VIEW(file_view
), GTK_TREE_MODEL(file_store
));
877 g_object_unref(file_store
);
879 icon_renderer
= gtk_cell_renderer_pixbuf_new();
880 text_renderer
= gtk_cell_renderer_text_new();
881 column
= gtk_tree_view_column_new();
882 gtk_tree_view_column_pack_start(column
, icon_renderer
, FALSE
);
883 gtk_tree_view_column_set_attributes(column
, icon_renderer
, "gicon", FILEVIEW_COLUMN_ICON
, NULL
);
884 gtk_tree_view_column_pack_start(column
, text_renderer
, TRUE
);
885 gtk_tree_view_column_set_attributes(column
, text_renderer
, "text", FILEVIEW_COLUMN_NAME
, NULL
);
886 gtk_tree_view_append_column(GTK_TREE_VIEW(file_view
), column
);
887 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(file_view
), FALSE
);
889 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(file_view
), TRUE
);
890 gtk_tree_view_set_search_column(GTK_TREE_VIEW(file_view
), FILEVIEW_COLUMN_NAME
);
892 ui_widget_modify_font_from_string(file_view
, geany
->interface_prefs
->tagbar_font
);
895 ui_tree_view_set_tooltip_text_column(GTK_TREE_VIEW(file_view
), FILEVIEW_COLUMN_FILENAME
);
897 /* selection handling */
898 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view
));
899 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_MULTIPLE
);
901 /* Show the current path when the FB is first needed */
902 g_signal_connect(file_view
, "realize", G_CALLBACK(on_realized
), NULL
);
903 g_signal_connect(selection
, "changed", G_CALLBACK(on_tree_selection_changed
), NULL
);
904 g_signal_connect(file_view
, "button-press-event", G_CALLBACK(on_button_press
), NULL
);
905 g_signal_connect(file_view
, "key-press-event", G_CALLBACK(on_key_press
), NULL
);
909 static GtkWidget
*make_toolbar(void)
911 GtkWidget
*wid
, *toolbar
;
913 toolbar
= gtk_toolbar_new();
914 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar
), GTK_ICON_SIZE_MENU
);
915 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar
), GTK_TOOLBAR_ICONS
);
917 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_GO_UP
));
918 gtk_widget_set_tooltip_text(wid
, _("Up"));
919 g_signal_connect(wid
, "clicked", G_CALLBACK(on_go_up
), NULL
);
920 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
922 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH
));
923 gtk_widget_set_tooltip_text(wid
, _("Refresh"));
924 g_signal_connect(wid
, "clicked", G_CALLBACK(refresh
), NULL
);
925 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
927 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_HOME
));
928 gtk_widget_set_tooltip_text(wid
, _("Home"));
929 g_signal_connect(wid
, "clicked", G_CALLBACK(on_go_home
), NULL
);
930 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
932 wid
= GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_JUMP_TO
));
933 gtk_widget_set_tooltip_text(wid
, _("Set path from document"));
934 g_signal_connect(wid
, "clicked", G_CALLBACK(on_current_path
), NULL
);
935 gtk_container_add(GTK_CONTAINER(toolbar
), wid
);
941 static GtkWidget
*make_filterbar(void)
943 GtkWidget
*label
, *filterbar
;
945 filterbar
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 1);
947 label
= gtk_label_new(_("Filter:"));
949 filter_combo
= gtk_combo_box_text_new_with_entry();
950 filter_entry
= gtk_bin_get_child(GTK_BIN(filter_combo
));
952 ui_entry_add_clear_icon(GTK_ENTRY(filter_entry
));
953 g_signal_connect(filter_entry
, "icon-release", G_CALLBACK(on_filter_clear
), NULL
);
955 gtk_widget_set_tooltip_text(filter_entry
,
956 _("Filter your files with the usual wildcards. Separate multiple patterns with a space."));
957 g_signal_connect(filter_entry
, "activate", G_CALLBACK(on_filter_activate
), NULL
);
958 g_signal_connect(filter_combo
, "changed", G_CALLBACK(ui_combo_box_changed
), NULL
);
960 gtk_box_pack_start(GTK_BOX(filterbar
), label
, FALSE
, FALSE
, 0);
961 gtk_box_pack_start(GTK_BOX(filterbar
), filter_combo
, TRUE
, TRUE
, 0);
967 static gboolean
completion_match_func(GtkEntryCompletion
*completion
, const gchar
*key
,
968 GtkTreeIter
*iter
, gpointer user_data
)
972 gboolean result
= FALSE
;
974 gtk_tree_model_get(GTK_TREE_MODEL(file_store
), iter
,
975 FILEVIEW_COLUMN_IS_DIR
, &is_dir
, FILEVIEW_COLUMN_NAME
, &str
, -1);
977 if (str
!= NULL
&& is_dir
&& !g_str_has_suffix(key
, G_DIR_SEPARATOR_S
))
979 /* key is something like "/tmp/te" and str is a filename like "test",
980 * so strip the path from key to make them comparable */
981 gchar
*base_name
= g_path_get_basename(key
);
982 gchar
*str_lowered
= g_utf8_strdown(str
, -1);
983 result
= g_str_has_prefix(str_lowered
, base_name
);
993 static gboolean
completion_match_selected(GtkEntryCompletion
*widget
, GtkTreeModel
*model
,
994 GtkTreeIter
*iter
, gpointer user_data
)
997 gtk_tree_model_get(model
, iter
, FILEVIEW_COLUMN_NAME
, &str
, -1);
1000 gchar
*text
= g_strconcat(current_dir
, G_DIR_SEPARATOR_S
, str
, NULL
);
1001 gtk_entry_set_text(GTK_ENTRY(path_entry
), text
);
1002 gtk_editable_set_position(GTK_EDITABLE(path_entry
), -1);
1003 /* force change of directory when completion is done */
1004 on_path_entry_activate(GTK_ENTRY(path_entry
), NULL
);
1013 static void completion_create(void)
1015 entry_completion
= gtk_entry_completion_new();
1017 gtk_entry_completion_set_inline_completion(entry_completion
, FALSE
);
1018 gtk_entry_completion_set_popup_completion(entry_completion
, TRUE
);
1019 gtk_entry_completion_set_text_column(entry_completion
, FILEVIEW_COLUMN_NAME
);
1020 gtk_entry_completion_set_match_func(entry_completion
, completion_match_func
, NULL
, NULL
);
1022 g_signal_connect(entry_completion
, "match-selected",
1023 G_CALLBACK(completion_match_selected
), NULL
);
1025 gtk_entry_set_completion(GTK_ENTRY(path_entry
), entry_completion
);
1029 static void load_settings(void)
1031 GKeyFile
*config
= g_key_file_new();
1033 config_file
= g_strconcat(geany
->app
->configdir
, G_DIR_SEPARATOR_S
, "plugins", G_DIR_SEPARATOR_S
,
1034 "filebrowser", G_DIR_SEPARATOR_S
, "filebrowser.conf", NULL
);
1035 g_key_file_load_from_file(config
, config_file
, G_KEY_FILE_NONE
, NULL
);
1037 open_cmd
= utils_get_setting_string(config
, "filebrowser", "open_command", OPEN_CMD
);
1038 /* g_key_file_get_boolean defaults to FALSE */
1039 show_hidden_files
= g_key_file_get_boolean(config
, "filebrowser", "show_hidden_files", NULL
);
1040 hide_object_files
= utils_get_setting_boolean(config
, "filebrowser", "hide_object_files", TRUE
);
1041 hidden_file_extensions
= utils_get_setting_string(config
, "filebrowser", "hidden_file_extensions",
1042 ".o .obj .so .dll .a .lib .pyc");
1043 fb_follow_path
= g_key_file_get_boolean(config
, "filebrowser", "fb_follow_path", NULL
);
1044 fb_set_project_base_path
= g_key_file_get_boolean(config
, "filebrowser", "fb_set_project_base_path", NULL
);
1046 g_key_file_free(config
);
1050 static void project_open_cb(G_GNUC_UNUSED GObject
*obj
, G_GNUC_UNUSED GKeyFile
*config
,
1051 G_GNUC_UNUSED gpointer data
)
1054 GeanyProject
*project
= geany
->app
->project
;
1056 if (! fb_set_project_base_path
|| project
== NULL
|| EMPTY(project
->base_path
))
1059 /* TODO this is a copy of project_get_base_path(), add it to the plugin API */
1060 if (g_path_is_absolute(project
->base_path
))
1061 new_dir
= g_strdup(project
->base_path
);
1063 { /* build base_path out of project file name's dir and base_path */
1064 gchar
*dir
= g_path_get_dirname(project
->file_name
);
1066 new_dir
= g_strconcat(dir
, G_DIR_SEPARATOR_S
, project
->base_path
, NULL
);
1069 /* get it into locale encoding */
1070 SETPTR(new_dir
, utils_get_locale_from_utf8(new_dir
));
1072 if (! utils_str_equal(current_dir
, new_dir
))
1074 SETPTR(current_dir
, new_dir
);
1082 static gpointer last_activate_path
= NULL
;
1084 static void document_activate_cb(G_GNUC_UNUSED GObject
*obj
, GeanyDocument
*doc
,
1085 G_GNUC_UNUSED gpointer data
)
1089 last_activate_path
= doc
->real_path
;
1091 if (! fb_follow_path
|| doc
->file_name
== NULL
|| ! g_path_is_absolute(doc
->file_name
))
1094 new_dir
= g_path_get_dirname(doc
->file_name
);
1095 SETPTR(new_dir
, utils_get_locale_from_utf8(new_dir
));
1097 if (! utils_str_equal(current_dir
, new_dir
))
1099 SETPTR(current_dir
, new_dir
);
1107 static void document_save_cb(GObject
*obj
, GeanyDocument
*doc
, gpointer user_data
)
1109 if (!last_activate_path
)
1110 document_activate_cb(obj
, doc
, user_data
);
1114 static void kb_activate(guint key_id
)
1116 gtk_notebook_set_current_page(GTK_NOTEBOOK(geany
->main_widgets
->sidebar_notebook
), page_number
);
1119 case KB_FOCUS_FILE_LIST
:
1120 gtk_widget_grab_focus(file_view
);
1122 case KB_FOCUS_PATH_ENTRY
:
1123 gtk_widget_grab_focus(path_entry
);
1129 void plugin_init(GeanyData
*data
)
1131 GeanyKeyGroup
*key_group
;
1132 GtkWidget
*scrollwin
, *toolbar
, *filterbar
;
1136 file_view_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
1137 toolbar
= make_toolbar();
1138 gtk_box_pack_start(GTK_BOX(file_view_vbox
), toolbar
, FALSE
, FALSE
, 0);
1140 filterbar
= make_filterbar();
1141 gtk_box_pack_start(GTK_BOX(file_view_vbox
), filterbar
, FALSE
, FALSE
, 0);
1143 path_combo
= gtk_combo_box_text_new_with_entry();
1144 gtk_box_pack_start(GTK_BOX(file_view_vbox
), path_combo
, FALSE
, FALSE
, 2);
1145 g_signal_connect(path_combo
, "changed", G_CALLBACK(ui_combo_box_changed
), NULL
);
1146 path_entry
= gtk_bin_get_child(GTK_BIN(path_combo
));
1147 g_signal_connect(path_entry
, "activate", G_CALLBACK(on_path_entry_activate
), NULL
);
1149 file_view
= gtk_tree_view_new();
1150 prepare_file_view();
1151 completion_create();
1153 popup_items
.open
= popup_items
.open_external
= popup_items
.find_in_files
= NULL
;
1155 scrollwin
= gtk_scrolled_window_new(NULL
, NULL
);
1156 gtk_scrolled_window_set_policy(
1157 GTK_SCROLLED_WINDOW(scrollwin
),
1158 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
1159 gtk_container_add(GTK_CONTAINER(scrollwin
), file_view
);
1160 gtk_box_pack_start(GTK_BOX(file_view_vbox
), scrollwin
, TRUE
, TRUE
, 0);
1162 /* load settings before file_view "realize" callback */
1165 gtk_widget_show_all(file_view_vbox
);
1166 page_number
= gtk_notebook_append_page(GTK_NOTEBOOK(geany
->main_widgets
->sidebar_notebook
),
1167 file_view_vbox
, gtk_label_new(_("Files")));
1169 /* setup keybindings */
1170 key_group
= plugin_set_key_group(geany_plugin
, "file_browser", KB_COUNT
, NULL
);
1171 keybindings_set_item(key_group
, KB_FOCUS_FILE_LIST
, kb_activate
,
1172 0, 0, "focus_file_list", _("Focus File List"), NULL
);
1173 keybindings_set_item(key_group
, KB_FOCUS_PATH_ENTRY
, kb_activate
,
1174 0, 0, "focus_path_entry", _("Focus Path Entry"), NULL
);
1176 plugin_signal_connect(geany_plugin
, NULL
, "document-activate", TRUE
,
1177 (GCallback
) &document_activate_cb
, NULL
);
1178 plugin_signal_connect(geany_plugin
, NULL
, "document-save", TRUE
,
1179 (GCallback
) &document_save_cb
, NULL
);
1183 static void save_settings(void)
1185 GKeyFile
*config
= g_key_file_new();
1187 gchar
*config_dir
= g_path_get_dirname(config_file
);
1189 g_key_file_load_from_file(config
, config_file
, G_KEY_FILE_NONE
, NULL
);
1191 g_key_file_set_string(config
, "filebrowser", "open_command", open_cmd
);
1192 g_key_file_set_boolean(config
, "filebrowser", "show_hidden_files", show_hidden_files
);
1193 g_key_file_set_boolean(config
, "filebrowser", "hide_object_files", hide_object_files
);
1194 g_key_file_set_string(config
, "filebrowser", "hidden_file_extensions", hidden_file_extensions
);
1195 g_key_file_set_boolean(config
, "filebrowser", "fb_follow_path", fb_follow_path
);
1196 g_key_file_set_boolean(config
, "filebrowser", "fb_set_project_base_path",
1197 fb_set_project_base_path
);
1199 if (! g_file_test(config_dir
, G_FILE_TEST_IS_DIR
) && utils_mkdir(config_dir
, TRUE
) != 0)
1201 dialogs_show_msgbox(GTK_MESSAGE_ERROR
,
1202 _("Plugin configuration directory could not be created."));
1206 /* write config to file */
1207 data
= g_key_file_to_data(config
, NULL
, NULL
);
1208 utils_write_file(config_file
, data
);
1212 g_key_file_free(config
);
1218 GtkWidget
*open_cmd_entry
;
1219 GtkWidget
*show_hidden_checkbox
;
1220 GtkWidget
*hide_objects_checkbox
;
1221 GtkWidget
*hidden_files_entry
;
1222 GtkWidget
*follow_path_checkbox
;
1223 GtkWidget
*set_project_base_path_checkbox
;
1228 on_configure_response(GtkDialog
*dialog
, gint response
, gpointer user_data
)
1230 if (response
== GTK_RESPONSE_OK
|| response
== GTK_RESPONSE_APPLY
)
1233 open_cmd
= g_strdup(gtk_entry_get_text(GTK_ENTRY(pref_widgets
.open_cmd_entry
)));
1234 show_hidden_files
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets
.show_hidden_checkbox
));
1235 hide_object_files
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets
.hide_objects_checkbox
));
1236 g_free(hidden_file_extensions
);
1237 hidden_file_extensions
= g_strdup(gtk_entry_get_text(GTK_ENTRY(pref_widgets
.hidden_files_entry
)));
1238 fb_follow_path
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets
.follow_path_checkbox
));
1239 fb_set_project_base_path
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1240 pref_widgets
.set_project_base_path_checkbox
));
1242 /* apply the changes */
1248 static void on_toggle_hidden(void)
1250 gboolean enabled
= !gtk_toggle_button_get_active(
1251 GTK_TOGGLE_BUTTON(pref_widgets
.show_hidden_checkbox
));
1253 gtk_widget_set_sensitive(pref_widgets
.hide_objects_checkbox
, enabled
);
1254 enabled
&= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets
.hide_objects_checkbox
));
1255 gtk_widget_set_sensitive(pref_widgets
.hidden_files_entry
, enabled
);
1259 GtkWidget
*plugin_configure(GtkDialog
*dialog
)
1261 GtkWidget
*label
, *entry
, *checkbox_of
, *checkbox_hf
, *checkbox_fp
, *checkbox_pb
, *vbox
;
1262 GtkWidget
*box
, *align
;
1264 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
1265 box
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 3);
1267 label
= gtk_label_new(_("External open command:"));
1268 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
1269 gtk_box_pack_start(GTK_BOX(box
), label
, FALSE
, FALSE
, 0);
1271 entry
= gtk_entry_new();
1272 if (open_cmd
!= NULL
)
1273 gtk_entry_set_text(GTK_ENTRY(entry
), open_cmd
);
1274 gtk_widget_set_tooltip_text(entry
,
1275 _("The command to execute when using \"Open with\". You can use %f and %d wildcards.\n"
1276 "%f will be replaced with the filename including full path\n"
1277 "%d will be replaced with the path name of the selected file without the filename"));
1278 gtk_box_pack_start(GTK_BOX(box
), entry
, FALSE
, FALSE
, 0);
1279 pref_widgets
.open_cmd_entry
= entry
;
1281 gtk_box_pack_start(GTK_BOX(vbox
), box
, FALSE
, FALSE
, 3);
1283 checkbox_hf
= gtk_check_button_new_with_label(_("Show hidden files"));
1284 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_hf
), FALSE
);
1285 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_hf
), show_hidden_files
);
1286 gtk_box_pack_start(GTK_BOX(vbox
), checkbox_hf
, FALSE
, FALSE
, 0);
1287 pref_widgets
.show_hidden_checkbox
= checkbox_hf
;
1288 g_signal_connect(checkbox_hf
, "toggled", on_toggle_hidden
, NULL
);
1290 box
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 3);
1291 checkbox_of
= gtk_check_button_new_with_label(_("Hide file extensions:"));
1292 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_of
), FALSE
);
1293 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_of
), hide_object_files
);
1294 gtk_box_pack_start(GTK_BOX(box
), checkbox_of
, FALSE
, FALSE
, 0);
1295 pref_widgets
.hide_objects_checkbox
= checkbox_of
;
1296 g_signal_connect(checkbox_of
, "toggled", on_toggle_hidden
, NULL
);
1298 entry
= gtk_entry_new();
1299 if (hidden_file_extensions
!= NULL
)
1300 gtk_entry_set_text(GTK_ENTRY(entry
), hidden_file_extensions
);
1301 gtk_box_pack_start(GTK_BOX(box
), entry
, FALSE
, FALSE
, 0);
1302 pref_widgets
.hidden_files_entry
= entry
;
1304 align
= gtk_alignment_new(1, 0.5, 1, 1);
1305 gtk_alignment_set_padding(GTK_ALIGNMENT(align
), 0, 0, 12, 0);
1306 gtk_container_add(GTK_CONTAINER(align
), box
);
1307 gtk_box_pack_start(GTK_BOX(vbox
), align
, FALSE
, FALSE
, 0);
1310 checkbox_fp
= gtk_check_button_new_with_label(_("Follow the path of the current file"));
1311 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_fp
), FALSE
);
1312 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_fp
), fb_follow_path
);
1313 gtk_box_pack_start(GTK_BOX(vbox
), checkbox_fp
, FALSE
, FALSE
, 0);
1314 pref_widgets
.follow_path_checkbox
= checkbox_fp
;
1316 checkbox_pb
= gtk_check_button_new_with_label(_("Use the project's base directory"));
1317 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_pb
), FALSE
);
1318 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_pb
), fb_set_project_base_path
);
1319 gtk_widget_set_tooltip_text(checkbox_pb
,
1320 _("Change the directory to the base directory of the currently opened project"));
1321 gtk_box_pack_start(GTK_BOX(vbox
), checkbox_pb
, FALSE
, FALSE
, 0);
1322 pref_widgets
.set_project_base_path_checkbox
= checkbox_pb
;
1324 gtk_widget_show_all(vbox
);
1326 g_signal_connect(dialog
, "response", G_CALLBACK(on_configure_response
), NULL
);
1331 void plugin_cleanup(void)
1335 g_free(config_file
);
1337 g_free(hidden_file_extensions
);
1339 gtk_widget_destroy(file_view_vbox
);
1340 g_object_unref(G_OBJECT(entry_completion
));