Support {ob} and {cb} wildcards for snippets too (fixes #2937008).
[geany-mirror.git] / plugins / filebrowser.c
blobd9ce88197884d1eceadbcfd2683cc1bf3b67b3a8
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>
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"))
44 /* Keybinding(s) */
45 enum
47 KB_FOCUS_FILE_LIST,
48 KB_FOCUS_PATH_ENTRY,
49 KB_COUNT
52 PLUGIN_KEY_GROUP(file_browser, KB_COUNT)
55 enum
57 FILEVIEW_COLUMN_ICON = 0,
58 FILEVIEW_COLUMN_NAME,
59 FILEVIEW_COLUMN_FILENAME, /* the full filename, including path for display as tooltip */
60 FILEVIEW_N_COLUMNS
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;
83 static struct
85 GtkWidget *open;
86 GtkWidget *open_external;
87 GtkWidget *find_in_files;
88 GtkWidget *show_hidden_files;
89 } popup_items;
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)
105 gsize len;
107 if (! NZV(base_name))
108 return FALSE;
110 if (base_name[0] == '.')
111 return TRUE;
113 len = strlen(base_name);
114 if (base_name[len - 1] == '~')
115 return TRUE;
117 if (hide_object_files)
119 const gchar *exts[] = {".o", ".obj", ".so", ".dll", ".a", ".lib"};
120 guint i, exts_len;
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))
128 return TRUE;
131 return FALSE;
135 /* Returns: whether filename should be removed. */
136 static gboolean check_filtered(const gchar *base_name)
138 if (filter == NULL)
139 return FALSE;
141 if (! utils_str_equal(base_name, "*") && ! g_pattern_match_simple(filter, base_name))
143 return TRUE;
145 return FALSE;
149 /* name is in locale encoding */
150 static void add_item(const gchar *name)
152 GtkTreeIter iter;
153 gchar *fname, *utf8_name, *utf8_fullname, *sep;
154 gboolean dir;
156 if (! show_hidden_files && check_hidden(name))
157 return;
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);
164 g_free(fname);
166 if (dir)
168 if (last_dir_iter == NULL)
169 gtk_list_store_prepend(file_store, &iter);
170 else
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);
177 else
179 if (check_filtered(utf8_name))
181 g_free(utf8_name);
182 g_free(utf8_fullname);
183 return;
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,
191 -1);
192 g_free(utf8_name);
193 g_free(utf8_fullname);
197 /* adds ".." to the start of the file list */
198 static void add_top_level_entry(void)
200 GtkTreeIter iter;
201 gchar *utf8_dir;
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,
216 -1);
217 g_free(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)
239 gchar *utf8_dir;
240 GSList *list, *node;
242 /* don't clear when the new path doesn't exist */
243 if (! g_file_test(current_dir, G_FILE_TEST_EXISTS))
244 return;
246 clear();
248 utf8_dir = utils_get_utf8_from_locale(current_dir);
249 gtk_entry_set_text(GTK_ENTRY(path_entry), utf8_dir);
250 g_free(utf8_dir);
252 add_top_level_entry(); /* ".." item */
254 list = utils_get_file_list(current_dir, NULL, NULL);
255 if (list != NULL)
257 /* free filenames & nodes as we go through the list */
258 foreach_slist_free(node, list)
260 gchar *fname = node->data;
262 add_item(fname);
263 g_free(fname);
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()));
273 refresh();
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;
283 if (project)
284 dir = project->base_path;
285 else
286 dir = geany->prefs->default_open_path;
288 if (NZV(dir))
289 return utils_get_locale_from_utf8(dir);
291 return g_get_current_dir();
295 static void on_current_path(void)
297 gchar *fname;
298 gchar *dir;
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());
304 refresh();
305 return;
307 fname = doc->file_name;
308 fname = utils_get_locale_from_utf8(fname);
309 dir = g_path_get_dirname(fname);
310 g_free(fname);
312 setptr(current_dir, dir);
313 refresh();
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));
321 refresh();
325 static gboolean check_single_selection(GtkTreeSelection *treesel)
327 if (gtk_tree_selection_count_selected_rows(treesel) == 1)
328 return TRUE;
330 ui_set_statusbar(FALSE, _("Too many items selected!"));
331 return FALSE;
335 /* Returns: TRUE if at least one of selected_items is a folder. */
336 static gboolean is_folder_selected(GList *selected_items)
338 GList *item;
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))
344 gchar *icon;
345 GtkTreeIter iter;
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))
354 dir_found = TRUE;
355 g_free(icon);
356 break;
358 g_free(icon);
360 return dir_found;
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);
368 GtkTreeIter iter;
369 gchar *name, *fname;
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);
375 g_free(name);
377 return fname;
381 static void open_external(const gchar *fname, gboolean dir_found)
383 gchar *cmd;
384 gchar *locale_cmd;
385 gchar *dir;
386 GString *cmd_str = g_string_new(open_cmd);
387 GError *error = NULL;
389 if (! dir_found)
390 dir = g_path_get_dirname(fname);
391 else
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, ' ');
403 if (c != NULL)
404 *c = '\0';
405 ui_set_statusbar(TRUE,
406 _("Could not execute configured external command '%s' (%s)."),
407 cmd, error->message);
408 g_error_free(error);
410 g_free(locale_cmd);
411 g_free(cmd);
412 g_free(dir);
416 static void on_external_open(GtkMenuItem *menuitem, gpointer user_data)
418 GtkTreeSelection *treesel;
419 GtkTreeModel *model;
420 GList *list;
421 gboolean dir_found;
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))
430 GList *item;
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);
438 g_free(fname);
442 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
443 g_list_free(list);
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;
451 GList *item;
452 GeanyDocument *doc;
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 */
467 g_slist_free(files);
471 static void open_folder(GtkTreePath *treepath)
473 gchar *fname = get_tree_path_filename(treepath);
475 setptr(current_dir, fname);
476 refresh();
480 static void on_open_clicked(GtkMenuItem *menuitem, gpointer user_data)
482 GtkTreeSelection *treesel;
483 GtkTreeModel *model;
484 GList *list;
485 gboolean dir_found;
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);
492 if (dir_found)
494 if (check_single_selection(treesel))
496 GtkTreePath *treepath = list->data; /* first selected item */
498 open_folder(treepath);
501 else
502 open_selected_files(list, GPOINTER_TO_INT(user_data));
504 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
505 g_list_free(list);
509 static void on_find_in_files(GtkMenuItem *menuitem, gpointer user_data)
511 GtkTreeSelection *treesel;
512 GtkTreeModel *model;
513 GList *list;
514 gchar *dir;
515 gboolean is_dir = FALSE;
517 treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view));
518 if (! check_single_selection(treesel))
519 return;
521 list = gtk_tree_selection_get_selected_rows(treesel, &model);
522 is_dir = is_folder_selected(list);
524 if (is_dir)
526 GtkTreePath *treepath = list->data; /* first selected item */
528 dir = get_tree_path_filename(treepath);
530 else
531 dir = g_strdup(current_dir);
533 g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
534 g_list_free(list);
536 setptr(dir, utils_get_utf8_from_locale(dir));
537 search_show_find_in_files_dialog(dir);
538 g_free(dir);
542 static void on_hidden_files_clicked(GtkCheckMenuItem *item)
544 show_hidden_files = gtk_check_menu_item_get_active(item);
545 refresh();
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);
613 return menu;
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);
636 return TRUE;
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),
646 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 */
650 return FALSE;
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);
659 return TRUE;
662 if (event->keyval == GDK_space)
664 on_open_clicked(NULL, GINT_TO_POINTER(TRUE));
665 return 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! */
672 on_go_up();
673 return TRUE;
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);
684 return TRUE;
687 return FALSE;
691 static void on_clear_filter(GtkEntry *entry, gpointer user_data)
693 setptr(filter, NULL);
695 gtk_entry_set_text(GTK_ENTRY(filter_entry), "");
697 refresh();
701 static void on_path_entry_activate(GtkEntry *entry, gpointer user_data)
703 gchar *new_dir = (gchar*) gtk_entry_get_text(entry);
705 if (NZV(new_dir))
707 if (g_str_has_suffix(new_dir, ".."))
709 on_go_up();
710 return;
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);
718 else
719 new_dir = utils_get_locale_from_utf8(new_dir);
721 else
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)));
734 if (! NZV(filter))
736 setptr(filter, g_strdup("*"));
739 refresh();
743 static void on_filter_clear(GtkEntry *entry, gint icon_pos,
744 GdkEvent *event, gpointer data)
746 setptr(filter, g_strdup("*"));
748 refresh();
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);
831 return toolbar;
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);
857 return filterbar;
861 static gboolean completion_match_func(GtkEntryCompletion *completion, const gchar *key,
862 GtkTreeIter *iter, gpointer user_data)
864 gchar *str, *icon;
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);
878 g_free(base_name);
879 g_free(str_lowered);
881 g_free(str);
882 g_free(icon);
884 return result;
888 static gboolean completion_match_selected(GtkEntryCompletion *widget, GtkTreeModel *model,
889 GtkTreeIter *iter, gpointer user_data)
891 gchar *str;
892 gtk_tree_model_get(model, iter, FILEVIEW_COLUMN_NAME, &str, -1);
893 if (str != NULL)
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);
900 g_free(text);
902 g_free(str);
904 return TRUE;
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)); \
928 (error) = NULL; \
930 else \
931 (var) = (tmp);
933 static void load_settings(void)
935 GKeyFile *config = g_key_file_new();
936 GError *error = NULL;
937 gboolean tmp;
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);
943 if (error != NULL)
945 open_cmd = g_strdup("nautilus \"%d\"");
946 g_error_free(error);
947 error = NULL;
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)
965 gchar *new_dir;
966 GeanyProject *project = geany->app->project;
968 if (! fb_set_project_base_path || project == NULL || ! NZV(project->base_path))
969 return;
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);
974 else
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);
979 g_free(dir);
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);
987 refresh();
989 else
990 g_free(new_dir);
994 static void document_activate_cb(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc,
995 G_GNUC_UNUSED gpointer data)
997 gchar *new_dir;
999 if (! fb_follow_path || doc->file_name == NULL || ! g_path_is_absolute(doc->file_name))
1000 return;
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);
1008 refresh();
1010 else
1011 g_free(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);
1018 switch (key_id)
1020 case KB_FOCUS_FILE_LIST:
1021 gtk_widget_grab_focus(file_view);
1022 break;
1023 case KB_FOCUS_PATH_ENTRY:
1024 gtk_widget_grab_focus(path_entry);
1025 break;
1030 void plugin_init(GeanyData *data)
1032 GtkWidget *scrollwin, *toolbar, *filterbar;
1034 filter = NULL;
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")));
1064 load_settings();
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();
1080 gchar *data;
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."));
1097 else
1099 /* write config to file */
1100 data = g_key_file_to_data(config, NULL, NULL);
1101 utils_write_file(config_file, data);
1102 g_free(data);
1104 g_free(config_dir);
1105 g_key_file_free(config);
1109 static struct
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;
1117 pref_widgets;
1119 static void
1120 on_configure_response(GtkDialog *dialog, gint response, gpointer user_data)
1122 if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY)
1124 g_free(open_cmd);
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 */
1133 refresh();
1138 GtkWidget *plugin_configure(GtkDialog *dialog)
1140 GtkWidget *label, *entry, *checkbox_of, *checkbox_hf, *checkbox_fp, *checkbox_pb, *vbox;
1141 GtkWidget *box;
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);
1195 return vbox;
1199 void plugin_cleanup(void)
1201 save_settings();
1203 g_free(config_file);
1204 g_free(open_cmd);
1205 g_free(filter);
1206 gtk_widget_destroy(file_view_vbox);
1207 g_object_unref(G_OBJECT(entry_completion));