Update Ignore Tags section with example and menu item.
[geany-mirror.git] / plugins / filebrowser.c
blob4c6e9109911526a0bbf7ec31e4bd88b4a9fae060
1 /*
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,
20 * MA 02110-1301, USA.
22 * $Id$
25 /* Sidebar file browser plugin. */
27 #include "geanyplugin.h"
28 #include <string.h>
30 #include <gdk/gdkkeysyms.h>
32 #ifdef G_OS_WIN32
33 # include <windows.h>
34 #endif
36 GeanyPlugin *geany_plugin;
37 GeanyData *geany_data;
38 GeanyFunctions *geany_functions;
41 PLUGIN_VERSION_CHECK(GEANY_API_VERSION)
43 PLUGIN_SET_INFO(_("File Browser"), _("Adds a file browser tab to the sidebar."), VERSION,
44 _("The Geany developer team"))
47 /* Keybinding(s) */
48 enum
50 KB_FOCUS_FILE_LIST,
51 KB_FOCUS_PATH_ENTRY,
52 KB_COUNT
55 PLUGIN_KEY_GROUP(file_browser, KB_COUNT)
58 enum
60 FILEVIEW_COLUMN_ICON = 0,
61 FILEVIEW_COLUMN_NAME,
62 FILEVIEW_COLUMN_FILENAME, /* the full filename, including path for display as tooltip */
63 FILEVIEW_N_COLUMNS
66 static gboolean fb_set_project_base_path = FALSE;
67 static gboolean fb_follow_path = FALSE;
68 static gboolean show_hidden_files = FALSE;
69 static gboolean hide_object_files = TRUE;
71 static GtkWidget *file_view_vbox;
72 static GtkWidget *file_view;
73 static GtkListStore *file_store;
74 static GtkTreeIter *last_dir_iter = NULL;
75 static GtkEntryCompletion *entry_completion = NULL;
77 static GtkWidget *filter_entry;
78 static GtkWidget *path_combo;
79 static GtkWidget *path_entry;
80 static gchar *current_dir = NULL; /* in locale-encoding */
81 static gchar *open_cmd; /* in locale-encoding */
82 static gchar *config_file;
83 static gchar *filter = NULL;
85 static gint page_number = 0;
87 static struct
89 GtkWidget *open;
90 GtkWidget *open_external;
91 GtkWidget *find_in_files;
92 GtkWidget *show_hidden_files;
93 } popup_items;
96 static void project_change_cb(GObject *obj, GKeyFile *config, gpointer data);
98 PluginCallback plugin_callbacks[] =
100 { "project-open", (GCallback) &project_change_cb, TRUE, NULL },
101 { "project-save", (GCallback) &project_change_cb, TRUE, NULL },
102 { NULL, NULL, FALSE, NULL }
106 #ifdef G_OS_WIN32
107 static gboolean win32_check_hidden(const gchar *filename)
109 DWORD attrs;
110 static wchar_t w_filename[MAX_PATH];
111 MultiByteToWideChar(CP_UTF8, 0, filename, -1, w_filename, sizeof(w_filename));
112 attrs = GetFileAttributesW(w_filename);
113 if (attrs != INVALID_FILE_ATTRIBUTES && attrs & FILE_ATTRIBUTE_HIDDEN)
114 return TRUE;
115 return FALSE;
117 #endif
120 /* Returns: whether name should be hidden. */
121 static gboolean check_hidden(const gchar *filename, const gchar *base_name)
123 gsize len;
125 if (! NZV(base_name))
126 return FALSE;
128 #ifdef G_OS_WIN32
129 if (win32_check_hidden(filename))
130 return TRUE;
131 #else
132 if (base_name[0] == '.')
133 return TRUE;
134 #endif
136 len = strlen(base_name);
137 if (base_name[len - 1] == '~')
138 return TRUE;
140 if (hide_object_files)
142 const gchar *exts[] = {".o", ".obj", ".so", ".dll", ".a", ".lib"};
143 guint i, exts_len;
145 exts_len = G_N_ELEMENTS(exts);
146 for (i = 0; i < exts_len; i++)
148 const gchar *ext = exts[i];
150 if (g_str_has_suffix(base_name, ext))
151 return TRUE;
154 return FALSE;
158 /* Returns: whether filename should be removed. */
159 static gboolean check_filtered(const gchar *base_name)
161 if (filter == NULL)
162 return FALSE;
164 if (! utils_str_equal(base_name, "*") && ! g_pattern_match_simple(filter, base_name))
166 return TRUE;
168 return FALSE;
172 /* name is in locale encoding */
173 static void add_item(const gchar *name)
175 GtkTreeIter iter;
176 gchar *fname, *utf8_name, *utf8_fullname;
177 const gchar *sep;
178 gboolean dir;
180 sep = (utils_str_equal(current_dir, "/")) ? "" : G_DIR_SEPARATOR_S;
181 fname = g_strconcat(current_dir, sep, name, NULL);
182 dir = g_file_test(fname, G_FILE_TEST_IS_DIR);
183 utf8_fullname = utils_get_locale_from_utf8(fname);
184 utf8_name = utils_get_utf8_from_locale(name);
185 g_free(fname);
187 if (! show_hidden_files && check_hidden(utf8_fullname, name))
189 g_free(utf8_name);
190 g_free(utf8_fullname);
191 return;
194 if (dir)
196 if (last_dir_iter == NULL)
197 gtk_list_store_prepend(file_store, &iter);
198 else
200 gtk_list_store_insert_after(file_store, &iter, last_dir_iter);
201 gtk_tree_iter_free(last_dir_iter);
203 last_dir_iter = gtk_tree_iter_copy(&iter);
205 else
207 if (check_filtered(utf8_name))
209 g_free(utf8_name);
210 g_free(utf8_fullname);
211 return;
213 gtk_list_store_append(file_store, &iter);
215 gtk_list_store_set(file_store, &iter,
216 FILEVIEW_COLUMN_ICON, (dir) ? GTK_STOCK_DIRECTORY : GTK_STOCK_FILE,
217 FILEVIEW_COLUMN_NAME, utf8_name,
218 FILEVIEW_COLUMN_FILENAME, utf8_fullname,
219 -1);
220 g_free(utf8_name);
221 g_free(utf8_fullname);
225 /* adds ".." to the start of the file list */
226 static void add_top_level_entry(void)
228 GtkTreeIter iter;
229 gchar *utf8_dir;
231 if (! NZV(g_path_skip_root(current_dir)))
232 return; /* ignore 'C:\' or '/' */
234 utf8_dir = g_path_get_dirname(current_dir);
235 setptr(utf8_dir, utils_get_utf8_from_locale(utf8_dir));
237 gtk_list_store_prepend(file_store, &iter);
238 last_dir_iter = gtk_tree_iter_copy(&iter);
240 gtk_list_store_set(file_store, &iter,
241 FILEVIEW_COLUMN_ICON, GTK_STOCK_DIRECTORY,
242 FILEVIEW_COLUMN_NAME, "..",
243 FILEVIEW_COLUMN_FILENAME, utf8_dir,
244 -1);
245 g_free(utf8_dir);
249 static void clear(void)
251 gtk_list_store_clear(file_store);
253 /* reset the directory item pointer */
254 if (last_dir_iter != NULL)
255 gtk_tree_iter_free(last_dir_iter);
256 last_dir_iter = NULL;
260 /* recreate the tree model from current_dir. */
261 static void refresh(void)
263 gchar *utf8_dir;
264 GSList *list, *node;
266 /* don't clear when the new path doesn't exist */
267 if (! g_file_test(current_dir, G_FILE_TEST_EXISTS))
268 return;
270 clear();
272 utf8_dir = utils_get_utf8_from_locale(current_dir);
273 gtk_entry_set_text(GTK_ENTRY(path_entry), utf8_dir);
274 ui_combo_box_add_to_history(GTK_COMBO_BOX_ENTRY(path_combo), utf8_dir, 0);
275 g_free(utf8_dir);
277 add_top_level_entry(); /* ".." item */
279 list = utils_get_file_list(current_dir, NULL, NULL);
280 if (list != NULL)
282 /* free filenames as we go through the list */
283 foreach_slist(node, list)
285 gchar *fname = node->data;
287 add_item(fname);
288 g_free(fname);
290 g_slist_free(list);
292 gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(file_store));
296 static void on_go_home(void)
298 setptr(current_dir, g_strdup(g_get_home_dir()));
299 refresh();
303 /* TODO: use utils_get_default_dir_utf8() */
304 static gchar *get_default_dir(void)
306 const gchar *dir = NULL;
307 GeanyProject *project = geany->app->project;
309 if (project)
310 dir = project->base_path;
311 else
312 dir = geany->prefs->default_open_path;
314 if (NZV(dir))
315 return utils_get_locale_from_utf8(dir);
317 return g_get_current_dir();
321 static void on_current_path(void)
323 gchar *fname;
324 gchar *dir;
325 GeanyDocument *doc = document_get_current();
327 if (doc == NULL || doc->file_name == NULL || ! g_path_is_absolute(doc->file_name))
329 setptr(current_dir, get_default_dir());
330 refresh();
331 return;
333 fname = doc->file_name;
334 fname = utils_get_locale_from_utf8(fname);
335 dir = g_path_get_dirname(fname);
336 g_free(fname);
338 setptr(current_dir, dir);
339 refresh();
343 static void on_go_up(void)
345 gsize len = strlen(current_dir);
346 if (current_dir[len-1] == G_DIR_SEPARATOR)
347 current_dir[len-1] = '\0';
348 /* remove the highest directory part (which becomes the basename of current_dir) */
349 setptr(current_dir, g_path_get_dirname(current_dir));
350 refresh();
354 static gboolean check_single_selection(GtkTreeSelection *treesel)
356 if (gtk_tree_selection_count_selected_rows(treesel) == 1)
357 return TRUE;
359 ui_set_statusbar(FALSE, _("Too many items selected!"));
360 return FALSE;
364 /* Returns: TRUE if at least one of selected_items is a folder. */
365 static gboolean is_folder_selected(GList *selected_items)
367 GList *item;
368 GtkTreeModel *model = GTK_TREE_MODEL(file_store);
369 gboolean dir_found = FALSE;
371 for (item = selected_items; item != NULL; item = g_list_next(item))
373 gchar *icon;
374 GtkTreeIter iter;
375 GtkTreePath *treepath;
377 treepath = (GtkTreePath*) item->data;
378 gtk_tree_model_get_iter(model, &iter, treepath);
379 gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_ICON, &icon, -1);
381 if (utils_str_equal(icon, GTK_STOCK_DIRECTORY))
383 dir_found = TRUE;
384 g_free(icon);
385 break;
387 g_free(icon);
389 return dir_found;
393 /* Returns: the full filename in locale encoding. */
394 static gchar *get_tree_path_filename(GtkTreePath *treepath)
396 GtkTreeModel *model = GTK_TREE_MODEL(file_store);
397 GtkTreeIter iter;
398 gchar *name, *fname;
400 gtk_tree_model_get_iter(model, &iter, treepath);
401 gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_FILENAME, &name, -1);
403 fname = utils_get_locale_from_utf8(name);
404 g_free(name);
406 return fname;
410 static void open_external(const gchar *fname, gboolean dir_found)
412 gchar *cmd;
413 gchar *locale_cmd;
414 gchar *dir;
415 GString *cmd_str = g_string_new(open_cmd);
416 GError *error = NULL;
418 if (! dir_found)
419 dir = g_path_get_dirname(fname);
420 else
421 dir = g_strdup(fname);
423 utils_string_replace_all(cmd_str, "%f", fname);
424 utils_string_replace_all(cmd_str, "%d", dir);
426 cmd = g_string_free(cmd_str, FALSE);
427 locale_cmd = utils_get_locale_from_utf8(cmd);
428 if (! g_spawn_command_line_async(locale_cmd, &error))
430 gchar *c = strchr(cmd, ' ');
432 if (c != NULL)
433 *c = '\0';
434 ui_set_statusbar(TRUE,
435 _("Could not execute configured external command '%s' (%s)."),
436 cmd, error->message);
437 g_error_free(error);
439 g_free(locale_cmd);
440 g_free(cmd);
441 g_free(dir);
445 static void on_external_open(GtkMenuItem *menuitem, gpointer user_data)
447 GtkTreeSelection *treesel;
448 GtkTreeModel *model;
449 GList *list;
450 gboolean dir_found;
452 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
454 list = gtk_tree_selection_get_selected_rows(treesel, &model);
455 dir_found = is_folder_selected(list);
457 if (! dir_found || check_single_selection(treesel))
459 GList *item;
461 for (item = list; item != NULL; item = g_list_next(item))
463 GtkTreePath *treepath = item->data;
464 gchar *fname = get_tree_path_filename(treepath);
466 open_external(fname, dir_found);
467 g_free(fname);
471 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
472 g_list_free(list);
476 /* We use document_open_files() as it's more efficient. */
477 static void open_selected_files(GList *list, gboolean do_not_focus)
479 GSList *files = NULL;
480 GList *item;
481 GeanyDocument *doc;
483 for (item = list; item != NULL; item = g_list_next(item))
485 GtkTreePath *treepath = item->data;
486 gchar *fname = get_tree_path_filename(treepath);
488 files = g_slist_append(files, fname);
490 document_open_files(files, FALSE, NULL, NULL);
491 doc = document_get_current();
492 if (doc != NULL && ! do_not_focus)
493 keybindings_send_command(GEANY_KEY_GROUP_FOCUS, GEANY_KEYS_FOCUS_EDITOR);
495 g_slist_foreach(files, (GFunc) g_free, NULL); /* free filenames */
496 g_slist_free(files);
500 static void open_folder(GtkTreePath *treepath)
502 gchar *fname = get_tree_path_filename(treepath);
504 setptr(current_dir, fname);
505 refresh();
509 static void on_open_clicked(GtkMenuItem *menuitem, gpointer user_data)
511 GtkTreeSelection *treesel;
512 GtkTreeModel *model;
513 GList *list;
514 gboolean dir_found;
516 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
518 list = gtk_tree_selection_get_selected_rows(treesel, &model);
519 dir_found = is_folder_selected(list);
521 if (dir_found)
523 if (check_single_selection(treesel))
525 GtkTreePath *treepath = list->data; /* first selected item */
527 open_folder(treepath);
530 else
531 open_selected_files(list, GPOINTER_TO_INT(user_data));
533 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
534 g_list_free(list);
538 static void on_find_in_files(GtkMenuItem *menuitem, gpointer user_data)
540 GtkTreeSelection *treesel;
541 GtkTreeModel *model;
542 GList *list;
543 gchar *dir;
544 gboolean is_dir = FALSE;
546 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
547 /* allow 0 or 1 selections */
548 if (gtk_tree_selection_count_selected_rows(treesel) > 0 &&
549 ! check_single_selection(treesel))
550 return;
552 list = gtk_tree_selection_get_selected_rows(treesel, &model);
553 is_dir = is_folder_selected(list);
555 if (is_dir)
557 GtkTreePath *treepath = list->data; /* first selected item */
559 dir = get_tree_path_filename(treepath);
561 else
562 dir = g_strdup(current_dir);
564 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
565 g_list_free(list);
567 setptr(dir, utils_get_utf8_from_locale(dir));
568 search_show_find_in_files_dialog(dir);
569 g_free(dir);
573 static void on_hidden_files_clicked(GtkCheckMenuItem *item)
575 show_hidden_files = gtk_check_menu_item_get_active(item);
576 refresh();
580 static void on_hide_sidebar(void)
582 keybindings_send_command(GEANY_KEY_GROUP_VIEW, GEANY_KEYS_VIEW_SIDEBAR);
586 static void on_show_preferences(void)
588 plugin_show_configure(geany_plugin);
592 static GtkWidget *create_popup_menu(void)
594 GtkWidget *item, *menu;
596 menu = gtk_menu_new();
598 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
599 gtk_widget_show(item);
600 gtk_container_add(GTK_CONTAINER(menu), item);
601 g_signal_connect(item, "activate", G_CALLBACK(on_open_clicked), NULL);
602 popup_items.open = item;
604 item = ui_image_menu_item_new(GTK_STOCK_OPEN, _("Open _externally"));
605 gtk_widget_show(item);
606 gtk_container_add(GTK_CONTAINER(menu), item);
607 g_signal_connect(item, "activate", G_CALLBACK(on_external_open), NULL);
608 popup_items.open_external = item;
610 item = gtk_separator_menu_item_new();
611 gtk_widget_show(item);
612 gtk_container_add(GTK_CONTAINER(menu), item);
614 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_REFRESH, NULL);
615 gtk_widget_show(item);
616 gtk_container_add(GTK_CONTAINER(menu), item);
617 g_signal_connect(item, "activate", G_CALLBACK(refresh), NULL);
619 item = ui_image_menu_item_new(GTK_STOCK_FIND, _("_Find in Files"));
620 gtk_widget_show(item);
621 gtk_container_add(GTK_CONTAINER(menu), item);
622 g_signal_connect(item, "activate", G_CALLBACK(on_find_in_files), NULL);
623 popup_items.find_in_files = item;
625 item = gtk_separator_menu_item_new();
626 gtk_widget_show(item);
627 gtk_container_add(GTK_CONTAINER(menu), item);
629 item = gtk_check_menu_item_new_with_mnemonic(_("Show _Hidden Files"));
630 gtk_widget_show(item);
631 gtk_container_add(GTK_CONTAINER(menu), item);
632 g_signal_connect(item, "activate", G_CALLBACK(on_hidden_files_clicked), NULL);
633 popup_items.show_hidden_files = item;
635 item = gtk_separator_menu_item_new();
636 gtk_widget_show(item);
637 gtk_container_add(GTK_CONTAINER(menu), item);
639 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
640 gtk_widget_show(item);
641 gtk_container_add(GTK_CONTAINER(menu), item);
642 g_signal_connect(item, "activate", G_CALLBACK(on_show_preferences), NULL);
644 item = gtk_separator_menu_item_new();
645 gtk_widget_show(item);
646 gtk_container_add(GTK_CONTAINER(menu), item);
648 item = ui_image_menu_item_new(GTK_STOCK_CLOSE, _("H_ide Sidebar"));
649 gtk_widget_show(item);
650 gtk_container_add(GTK_CONTAINER(menu), item);
651 g_signal_connect(item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
653 return menu;
657 static void on_tree_selection_changed(GtkTreeSelection *selection, gpointer data)
659 gboolean have_sel = (gtk_tree_selection_count_selected_rows(selection) > 0);
660 gboolean multi_sel = (gtk_tree_selection_count_selected_rows(selection) > 1);
662 if (popup_items.open != NULL)
663 gtk_widget_set_sensitive(popup_items.open, have_sel);
664 if (popup_items.open_external != NULL)
665 gtk_widget_set_sensitive(popup_items.open_external, have_sel);
666 if (popup_items.find_in_files != NULL)
667 gtk_widget_set_sensitive(popup_items.find_in_files, have_sel && ! multi_sel);
671 static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
673 if (event->button == 1 && event->type == GDK_2BUTTON_PRESS)
675 on_open_clicked(NULL, NULL);
676 return TRUE;
678 else if (event->button == 3)
680 static GtkWidget *popup_menu = NULL;
682 if (popup_menu == NULL)
683 popup_menu = create_popup_menu();
685 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(popup_items.show_hidden_files),
686 show_hidden_files);
687 gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL, event->button, event->time);
688 /* don't return TRUE here, unless the selection won't be changed */
690 return FALSE;
694 static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
696 if (ui_is_keyval_enter_or_return(event->keyval))
698 on_open_clicked(NULL, NULL);
699 return TRUE;
702 if (event->keyval == GDK_space)
704 on_open_clicked(NULL, GINT_TO_POINTER(TRUE));
705 return TRUE;
708 if ((event->keyval == GDK_Up ||
709 event->keyval == GDK_KP_Up) &&
710 (event->state & GDK_MOD1_MASK)) /* FIXME: Alt-Up doesn't seem to work! */
712 on_go_up();
713 return TRUE;
716 if ((event->keyval == GDK_F10 && event->state & GDK_SHIFT_MASK) || event->keyval == GDK_Menu)
718 GdkEventButton button_event;
720 button_event.time = event->time;
721 button_event.button = 3;
723 on_button_press(widget, &button_event, data);
724 return TRUE;
727 return FALSE;
731 static void on_clear_filter(GtkEntry *entry, gpointer user_data)
733 setptr(filter, NULL);
735 gtk_entry_set_text(GTK_ENTRY(filter_entry), "");
737 refresh();
741 static void on_path_entry_activate(GtkEntry *entry, gpointer user_data)
743 gchar *new_dir = (gchar*) gtk_entry_get_text(entry);
745 if (NZV(new_dir))
747 if (g_str_has_suffix(new_dir, ".."))
749 on_go_up();
750 return;
752 else if (new_dir[0] == '~')
754 GString *str = g_string_new(new_dir);
755 utils_string_replace_first(str, "~", g_get_home_dir());
756 new_dir = g_string_free(str, FALSE);
758 else
759 new_dir = utils_get_locale_from_utf8(new_dir);
761 else
762 new_dir = g_strdup(g_get_home_dir());
764 setptr(current_dir, new_dir);
766 on_clear_filter(NULL, NULL);
770 static void on_path_combo_changed(GtkComboBox *combo, gpointer user_data)
772 /* we get this callback on typing as well as choosing an item */
773 if (gtk_combo_box_get_active(combo) >= 0)
774 on_path_entry_activate(GTK_ENTRY(path_entry), NULL);
778 static void on_filter_activate(GtkEntry *entry, gpointer user_data)
780 setptr(filter, g_strdup(gtk_entry_get_text(entry)));
782 if (! NZV(filter))
784 setptr(filter, g_strdup("*"));
787 refresh();
791 static void on_filter_clear(GtkEntry *entry, gint icon_pos,
792 GdkEvent *event, gpointer data)
794 setptr(filter, g_strdup("*"));
796 refresh();
800 static void prepare_file_view(void)
802 GtkCellRenderer *text_renderer, *icon_renderer;
803 GtkTreeViewColumn *column;
804 GtkTreeSelection *selection;
806 file_store = gtk_list_store_new(FILEVIEW_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
808 gtk_tree_view_set_model(GTK_TREE_VIEW(file_view), GTK_TREE_MODEL(file_store));
809 g_object_unref(file_store);
811 icon_renderer = gtk_cell_renderer_pixbuf_new();
812 text_renderer = gtk_cell_renderer_text_new();
813 column = gtk_tree_view_column_new();
814 gtk_tree_view_column_pack_start(column, icon_renderer, FALSE);
815 gtk_tree_view_column_set_attributes(column, icon_renderer, "stock-id", FILEVIEW_COLUMN_ICON, NULL);
816 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
817 gtk_tree_view_column_set_attributes(column, text_renderer, "text", FILEVIEW_COLUMN_NAME, NULL);
818 gtk_tree_view_append_column(GTK_TREE_VIEW(file_view), column);
819 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(file_view), FALSE);
821 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(file_view), TRUE);
822 gtk_tree_view_set_search_column(GTK_TREE_VIEW(file_view), FILEVIEW_COLUMN_NAME);
824 ui_widget_modify_font_from_string(file_view, geany->interface_prefs->tagbar_font);
826 /* GTK 2.12 tooltips */
827 if (gtk_check_version(2, 12, 0) == NULL)
828 g_object_set(file_view, "has-tooltip", TRUE, "tooltip-column", FILEVIEW_COLUMN_FILENAME, NULL);
830 /* selection handling */
831 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
832 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
834 g_signal_connect(file_view, "realize", G_CALLBACK(on_current_path), NULL);
835 g_signal_connect(selection, "changed", G_CALLBACK(on_tree_selection_changed), NULL);
836 g_signal_connect(file_view, "button-press-event", G_CALLBACK(on_button_press), NULL);
837 g_signal_connect(file_view, "key-press-event", G_CALLBACK(on_key_press), NULL);
841 static GtkWidget *make_toolbar(void)
843 GtkWidget *wid, *toolbar;
845 toolbar = gtk_toolbar_new();
846 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU);
847 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
849 wid = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_GO_UP));
850 ui_widget_set_tooltip_text(wid, _("Up"));
851 g_signal_connect(wid, "clicked", G_CALLBACK(on_go_up), NULL);
852 gtk_container_add(GTK_CONTAINER(toolbar), wid);
854 wid = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH));
855 ui_widget_set_tooltip_text(wid, _("Refresh"));
856 g_signal_connect(wid, "clicked", G_CALLBACK(refresh), NULL);
857 gtk_container_add(GTK_CONTAINER(toolbar), wid);
859 wid = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_HOME));
860 ui_widget_set_tooltip_text(wid, _("Home"));
861 g_signal_connect(wid, "clicked", G_CALLBACK(on_go_home), NULL);
862 gtk_container_add(GTK_CONTAINER(toolbar), wid);
864 wid = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_JUMP_TO));
865 ui_widget_set_tooltip_text(wid, _("Set path from document"));
866 g_signal_connect(wid, "clicked", G_CALLBACK(on_current_path), NULL);
867 gtk_container_add(GTK_CONTAINER(toolbar), wid);
869 if (gtk_check_version(2, 15, 2) != NULL)
871 wid = GTK_WIDGET(gtk_separator_tool_item_new());
872 gtk_container_add(GTK_CONTAINER(toolbar), wid);
874 wid = GTK_WIDGET(gtk_tool_button_new_from_stock(GTK_STOCK_CLEAR));
875 ui_widget_set_tooltip_text(wid, _("Clear the filter"));
876 g_signal_connect(wid, "clicked", G_CALLBACK(on_clear_filter), NULL);
877 gtk_container_add(GTK_CONTAINER(toolbar), wid);
879 return toolbar;
883 static GtkWidget *make_filterbar(void)
885 GtkWidget *label, *filterbar;
887 filterbar = gtk_hbox_new(FALSE, 1);
889 label = gtk_label_new(_("Filter:"));
891 filter_entry = gtk_entry_new();
893 if (gtk_check_version(2, 15, 2) == NULL)
895 ui_entry_add_clear_icon(GTK_ENTRY(filter_entry));
896 g_signal_connect(filter_entry, "icon-release", G_CALLBACK(on_filter_clear), NULL);
898 ui_widget_set_tooltip_text(filter_entry,
899 _("Filter your files with usual wildcards"));
900 g_signal_connect(filter_entry, "activate", G_CALLBACK(on_filter_activate), NULL);
902 gtk_box_pack_start(GTK_BOX(filterbar), label, FALSE, FALSE, 0);
903 gtk_box_pack_start(GTK_BOX(filterbar), filter_entry, TRUE, TRUE, 0);
905 return filterbar;
909 static gboolean completion_match_func(GtkEntryCompletion *completion, const gchar *key,
910 GtkTreeIter *iter, gpointer user_data)
912 gchar *str, *icon;
913 gboolean result = FALSE;
915 gtk_tree_model_get(GTK_TREE_MODEL(file_store), iter,
916 FILEVIEW_COLUMN_ICON, &icon, FILEVIEW_COLUMN_NAME, &str, -1);
918 if (str != NULL && icon != NULL && utils_str_equal(icon, GTK_STOCK_DIRECTORY) &&
919 ! g_str_has_suffix(key, G_DIR_SEPARATOR_S))
921 /* key is something like "/tmp/te" and str is a filename like "test",
922 * so strip the path from key to make them comparable */
923 gchar *base_name = g_path_get_basename(key);
924 gchar *str_lowered = g_utf8_strdown(str, -1);
925 result = g_str_has_prefix(str_lowered, base_name);
926 g_free(base_name);
927 g_free(str_lowered);
929 g_free(str);
930 g_free(icon);
932 return result;
936 static gboolean completion_match_selected(GtkEntryCompletion *widget, GtkTreeModel *model,
937 GtkTreeIter *iter, gpointer user_data)
939 gchar *str;
940 gtk_tree_model_get(model, iter, FILEVIEW_COLUMN_NAME, &str, -1);
941 if (str != NULL)
943 gchar *text = g_strconcat(current_dir, G_DIR_SEPARATOR_S, str, NULL);
944 gtk_entry_set_text(GTK_ENTRY(path_entry), text);
945 gtk_editable_set_position(GTK_EDITABLE(path_entry), -1);
946 /* force change of directory when completion is done */
947 on_path_entry_activate(GTK_ENTRY(path_entry), NULL);
948 g_free(text);
950 g_free(str);
952 return TRUE;
956 static void completion_create(void)
958 entry_completion = gtk_entry_completion_new();
960 gtk_entry_completion_set_inline_completion(entry_completion, FALSE);
961 gtk_entry_completion_set_popup_completion(entry_completion, TRUE);
962 gtk_entry_completion_set_text_column(entry_completion, FILEVIEW_COLUMN_NAME);
963 gtk_entry_completion_set_match_func(entry_completion, completion_match_func, NULL, NULL);
965 g_signal_connect(entry_completion, "match-selected",
966 G_CALLBACK(completion_match_selected), NULL);
968 gtk_entry_set_completion(GTK_ENTRY(path_entry), entry_completion);
972 #define CHECK_READ_SETTING(var, error, tmp) \
973 if ((error) != NULL) \
975 g_error_free((error)); \
976 (error) = NULL; \
978 else \
979 (var) = (tmp);
981 static void load_settings(void)
983 GKeyFile *config = g_key_file_new();
984 GError *error = NULL;
985 gboolean tmp;
987 config_file = g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S, "plugins", G_DIR_SEPARATOR_S,
988 "filebrowser", G_DIR_SEPARATOR_S, "filebrowser.conf", NULL);
989 g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
990 open_cmd = g_key_file_get_string(config, "filebrowser", "open_command", &error);
991 if (error != NULL)
993 open_cmd = g_strdup("nautilus \"%d\"");
994 g_error_free(error);
995 error = NULL;
997 tmp = g_key_file_get_boolean(config, "filebrowser", "show_hidden_files", &error);
998 CHECK_READ_SETTING(show_hidden_files, error, tmp);
999 tmp = g_key_file_get_boolean(config, "filebrowser", "hide_object_files", &error);
1000 CHECK_READ_SETTING(hide_object_files, error, tmp);
1001 tmp = g_key_file_get_boolean(config, "filebrowser", "fb_follow_path", &error);
1002 CHECK_READ_SETTING(fb_follow_path, error, tmp);
1003 tmp = g_key_file_get_boolean(config, "filebrowser", "fb_set_project_base_path", &error);
1004 CHECK_READ_SETTING(fb_set_project_base_path, error, tmp);
1006 g_key_file_free(config);
1010 static void project_change_cb(G_GNUC_UNUSED GObject *obj, G_GNUC_UNUSED GKeyFile *config,
1011 G_GNUC_UNUSED gpointer data)
1013 gchar *new_dir;
1014 GeanyProject *project = geany->app->project;
1016 if (! fb_set_project_base_path || project == NULL || ! NZV(project->base_path))
1017 return;
1019 /* TODO this is a copy of project_get_base_path(), add it to the plugin API */
1020 if (g_path_is_absolute(project->base_path))
1021 new_dir = g_strdup(project->base_path);
1022 else
1023 { /* build base_path out of project file name's dir and base_path */
1024 gchar *dir = g_path_get_dirname(project->file_name);
1026 new_dir = g_strconcat(dir, G_DIR_SEPARATOR_S, project->base_path, NULL);
1027 g_free(dir);
1029 /* get it into locale encoding */
1030 setptr(new_dir, utils_get_locale_from_utf8(new_dir));
1032 if (! utils_str_equal(current_dir, new_dir))
1034 setptr(current_dir, new_dir);
1035 refresh();
1037 else
1038 g_free(new_dir);
1042 static void document_activate_cb(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc,
1043 G_GNUC_UNUSED gpointer data)
1045 gchar *new_dir;
1047 if (! fb_follow_path || doc->file_name == NULL || ! g_path_is_absolute(doc->file_name))
1048 return;
1050 new_dir = g_path_get_dirname(doc->file_name);
1051 setptr(new_dir, utils_get_locale_from_utf8(new_dir));
1053 if (! utils_str_equal(current_dir, new_dir))
1055 setptr(current_dir, new_dir);
1056 refresh();
1058 else
1059 g_free(new_dir);
1063 static void kb_activate(guint key_id)
1065 gtk_notebook_set_current_page(GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook), page_number);
1066 switch (key_id)
1068 case KB_FOCUS_FILE_LIST:
1069 gtk_widget_grab_focus(file_view);
1070 break;
1071 case KB_FOCUS_PATH_ENTRY:
1072 gtk_widget_grab_focus(path_entry);
1073 break;
1078 void plugin_init(GeanyData *data)
1080 GtkWidget *scrollwin, *toolbar, *filterbar;
1082 filter = NULL;
1084 file_view_vbox = gtk_vbox_new(FALSE, 0);
1085 toolbar = make_toolbar();
1086 gtk_box_pack_start(GTK_BOX(file_view_vbox), toolbar, FALSE, FALSE, 0);
1088 filterbar = make_filterbar();
1089 gtk_box_pack_start(GTK_BOX(file_view_vbox), filterbar, FALSE, FALSE, 0);
1091 path_combo = gtk_combo_box_entry_new_text();
1092 gtk_box_pack_start(GTK_BOX(file_view_vbox), path_combo, FALSE, FALSE, 2);
1093 g_signal_connect(path_combo, "changed", G_CALLBACK(on_path_combo_changed), NULL);
1094 path_entry = GTK_BIN(path_combo)->child;
1095 g_signal_connect(path_entry, "activate", G_CALLBACK(on_path_entry_activate), NULL);
1097 file_view = gtk_tree_view_new();
1098 prepare_file_view();
1099 completion_create();
1101 popup_items.open = popup_items.open_external = popup_items.find_in_files = NULL;
1103 scrollwin = gtk_scrolled_window_new(NULL, NULL);
1104 gtk_scrolled_window_set_policy(
1105 GTK_SCROLLED_WINDOW(scrollwin),
1106 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1107 gtk_container_add(GTK_CONTAINER(scrollwin), file_view);
1108 gtk_container_add(GTK_CONTAINER(file_view_vbox), scrollwin);
1110 gtk_widget_show_all(file_view_vbox);
1111 page_number = gtk_notebook_append_page(GTK_NOTEBOOK(geany->main_widgets->sidebar_notebook),
1112 file_view_vbox, gtk_label_new(_("Files")));
1114 load_settings();
1116 /* setup keybindings */
1117 keybindings_set_item(plugin_key_group, KB_FOCUS_FILE_LIST, kb_activate,
1118 0, 0, "focus_file_list", _("Focus File List"), NULL);
1119 keybindings_set_item(plugin_key_group, KB_FOCUS_PATH_ENTRY, kb_activate,
1120 0, 0, "focus_path_entry", _("Focus Path Entry"), NULL);
1122 plugin_signal_connect(geany_plugin, NULL, "document-activate", TRUE,
1123 (GCallback) &document_activate_cb, NULL);
1127 static void save_settings(void)
1129 GKeyFile *config = g_key_file_new();
1130 gchar *data;
1131 gchar *config_dir = g_path_get_dirname(config_file);
1133 g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
1135 g_key_file_set_string(config, "filebrowser", "open_command", open_cmd);
1136 g_key_file_set_boolean(config, "filebrowser", "show_hidden_files", show_hidden_files);
1137 g_key_file_set_boolean(config, "filebrowser", "hide_object_files", hide_object_files);
1138 g_key_file_set_boolean(config, "filebrowser", "fb_follow_path", fb_follow_path);
1139 g_key_file_set_boolean(config, "filebrowser", "fb_set_project_base_path",
1140 fb_set_project_base_path);
1142 if (! g_file_test(config_dir, G_FILE_TEST_IS_DIR) && utils_mkdir(config_dir, TRUE) != 0)
1144 dialogs_show_msgbox(GTK_MESSAGE_ERROR,
1145 _("Plugin configuration directory could not be created."));
1147 else
1149 /* write config to file */
1150 data = g_key_file_to_data(config, NULL, NULL);
1151 utils_write_file(config_file, data);
1152 g_free(data);
1154 g_free(config_dir);
1155 g_key_file_free(config);
1159 static struct
1161 GtkWidget *open_cmd_entry;
1162 GtkWidget *show_hidden_checkbox;
1163 GtkWidget *hide_objects_checkbox;
1164 GtkWidget *follow_path_checkbox;
1165 GtkWidget *set_project_base_path_checkbox;
1167 pref_widgets;
1169 static void
1170 on_configure_response(GtkDialog *dialog, gint response, gpointer user_data)
1172 if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY)
1174 g_free(open_cmd);
1175 open_cmd = g_strdup(gtk_entry_get_text(GTK_ENTRY(pref_widgets.open_cmd_entry)));
1176 show_hidden_files = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets.show_hidden_checkbox));
1177 hide_object_files = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets.hide_objects_checkbox));
1178 fb_follow_path = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets.follow_path_checkbox));
1179 fb_set_project_base_path = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
1180 pref_widgets.set_project_base_path_checkbox));
1182 /* apply the changes */
1183 refresh();
1188 GtkWidget *plugin_configure(GtkDialog *dialog)
1190 GtkWidget *label, *entry, *checkbox_of, *checkbox_hf, *checkbox_fp, *checkbox_pb, *vbox;
1191 GtkWidget *box;
1193 vbox = gtk_vbox_new(FALSE, 6);
1194 box = gtk_vbox_new(FALSE, 3);
1196 label = gtk_label_new(_("External open command:"));
1197 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1198 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
1200 entry = gtk_entry_new();
1201 gtk_widget_show(entry);
1202 if (open_cmd != NULL)
1203 gtk_entry_set_text(GTK_ENTRY(entry), open_cmd);
1204 ui_widget_set_tooltip_text(entry,
1205 _("The command to execute when using \"Open with\". You can use %f and %d wildcards.\n"
1206 "%f will be replaced with the filename including full path\n"
1207 "%d will be replaced with the path name of the selected file without the filename"));
1208 gtk_box_pack_start(GTK_BOX(box), entry, FALSE, FALSE, 0);
1209 pref_widgets.open_cmd_entry = entry;
1211 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 6);
1213 checkbox_hf = gtk_check_button_new_with_label(_("Show hidden files"));
1214 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_hf), FALSE);
1215 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_hf), show_hidden_files);
1216 gtk_box_pack_start(GTK_BOX(vbox), checkbox_hf, FALSE, FALSE, 0);
1217 pref_widgets.show_hidden_checkbox = checkbox_hf;
1219 checkbox_of = gtk_check_button_new_with_label(_("Hide object files"));
1220 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_of), FALSE);
1221 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_of), hide_object_files);
1222 ui_widget_set_tooltip_text(checkbox_of,
1223 _("Don't show generated object files in the file browser, this includes "
1224 "*.o, *.obj. *.so, *.dll, *.a, *.lib"));
1225 gtk_box_pack_start(GTK_BOX(vbox), checkbox_of, FALSE, FALSE, 0);
1226 pref_widgets.hide_objects_checkbox = checkbox_of;
1228 checkbox_fp = gtk_check_button_new_with_label(_("Follow the path of the current file"));
1229 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_fp), FALSE);
1230 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_fp), fb_follow_path);
1231 gtk_box_pack_start(GTK_BOX(vbox), checkbox_fp, FALSE, FALSE, 0);
1232 pref_widgets.follow_path_checkbox = checkbox_fp;
1234 checkbox_pb = gtk_check_button_new_with_label(_("Use the project's base directory"));
1235 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_pb), FALSE);
1236 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_pb), fb_set_project_base_path);
1237 ui_widget_set_tooltip_text(checkbox_pb,
1238 _("Change the directory to the base directory of the currently opened project"));
1239 gtk_box_pack_start(GTK_BOX(vbox), checkbox_pb, FALSE, FALSE, 0);
1240 pref_widgets.set_project_base_path_checkbox = checkbox_pb;
1242 gtk_widget_show_all(vbox);
1244 g_signal_connect(dialog, "response", G_CALLBACK(on_configure_response), NULL);
1245 return vbox;
1249 void plugin_cleanup(void)
1251 save_settings();
1253 g_free(config_file);
1254 g_free(open_cmd);
1255 g_free(filter);
1256 gtk_widget_destroy(file_view_vbox);
1257 g_object_unref(G_OBJECT(entry_completion));