/* * filebrowser.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2007 Enrico Tröger * Copyright 2007 Nick Treleaven * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * $Id$ */ /* Sidebar file browser plugin. */ #include "geany.h" #include #include "support.h" #include "prefs.h" #include "document.h" #include "utils.h" #include "keybindings.h" #include "project.h" #include "plugindata.h" #include "pluginmacros.h" PluginFields *plugin_fields; GeanyData *geany_data; VERSION_CHECK(26) PLUGIN_INFO(_("File Browser"), _("Adds a file browser tab to the sidebar."), VERSION, _("The Geany developer team")) enum { FILEVIEW_COLUMN_ICON = 0, FILEVIEW_COLUMN_NAME, FILEVIEW_N_COLUMNS, }; static gboolean show_hidden_files = FALSE; static gboolean hide_object_files = TRUE; static GtkWidget *file_view_vbox; static GtkWidget *file_view; static GtkListStore *file_store; static GtkTreeIter *last_dir_iter = NULL; static GtkWidget *path_entry; static gchar *current_dir = NULL; // in locale-encoding // Returns: whether name should be hidden. static gboolean check_hidden(const gchar *base_name) { gsize len; if (! NZV(base_name)) return FALSE; if (base_name[0] == '.') return TRUE; len = strlen(base_name); if (base_name[len - 1] == '~') return TRUE; if (hide_object_files) { const gchar *exts[] = {".o", ".obj", ".so", ".dll", ".a", ".lib"}; guint i; for (i = 0; i < G_N_ELEMENTS(exts); i++) { const gchar *ext = exts[i]; if (utils->str_equal(&base_name[len - strlen(ext)], ext)) return TRUE; } } return FALSE; } // name is in locale encoding static void add_item(const gchar *name) { GtkTreeIter iter; gchar *fname, *utf8_name; gboolean dir; if (! show_hidden_files && check_hidden(name)) return; fname = g_strconcat(current_dir, G_DIR_SEPARATOR_S, name, NULL); dir = g_file_test(fname, G_FILE_TEST_IS_DIR); g_free(fname); if (dir) { if (last_dir_iter == NULL) gtk_list_store_prepend(file_store, &iter); else { gtk_list_store_insert_after(file_store, &iter, last_dir_iter); gtk_tree_iter_free(last_dir_iter); } last_dir_iter = gtk_tree_iter_copy(&iter); } else gtk_list_store_append(file_store, &iter); utf8_name = utils->get_utf8_from_locale(name); gtk_list_store_set(file_store, &iter, FILEVIEW_COLUMN_ICON, (dir) ? GTK_STOCK_DIRECTORY : GTK_STOCK_FILE, FILEVIEW_COLUMN_NAME, utf8_name, -1); g_free(utf8_name); } static void clear() { gtk_list_store_clear(file_store); // reset the directory item pointer if (last_dir_iter != NULL) gtk_tree_iter_free(last_dir_iter); last_dir_iter = NULL; } // recreate the tree model from current_dir. static void refresh() { gchar *utf8_dir; GSList *list; clear(); utf8_dir = utils->get_utf8_from_locale(current_dir); gtk_entry_set_text(GTK_ENTRY(path_entry), utf8_dir); g_free(utf8_dir); list = utils->get_file_list(current_dir, NULL, NULL); if (list != NULL) { g_slist_foreach(list, (GFunc) add_item, NULL); g_slist_foreach(list, (GFunc) g_free, NULL); g_slist_free(list); } } static void on_go_home() { setptr(current_dir, g_strdup(g_get_home_dir())); refresh(); } static gchar *get_default_dir() { const gchar *dir = NULL; if (project) dir = project->base_path; if (NZV(dir)) return g_strdup(dir); return g_get_current_dir(); } static void on_current_path() { gchar *fname; gchar *dir; gint idx = documents->get_cur_idx(); if (! DOC_IDX_VALID(idx) || doc_list[idx].file_name == NULL || ! g_path_is_absolute(doc_list[idx].file_name)) { setptr(current_dir, get_default_dir()); refresh(); return; } fname = doc_list[idx].file_name; fname = utils->get_locale_from_utf8(fname); dir = g_path_get_dirname(fname); g_free(fname); setptr(current_dir, dir); refresh(); } static void handle_selection(GList *list, GtkTreeSelection *treesel) { GList *item; GtkTreeModel *model = GTK_TREE_MODEL(file_store); GtkTreePath *treepath; GtkTreeIter iter; gboolean dir_found = FALSE; for (item = list; item != NULL; item = g_list_next(item)) { gchar *icon; treepath = (GtkTreePath*) item->data; gtk_tree_model_get_iter(model, &iter, treepath); gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_ICON, &icon, -1); if (utils->str_equal(icon, GTK_STOCK_DIRECTORY)) { dir_found = TRUE; g_free(icon); break; } g_free(icon); } if (dir_found && gtk_tree_selection_count_selected_rows(treesel) > 1) { ui->set_statusbar(FALSE, _("Too many items selected!")); return; } for (item = list; item != NULL; item = g_list_next(item)) { gchar *name, *fname, *dir_sep = G_DIR_SEPARATOR_S; treepath = (GtkTreePath*) item->data; gtk_tree_model_get_iter(model, &iter, treepath); gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_NAME, &name, -1); setptr(name, utils->get_locale_from_utf8(name)); if (utils->str_equal(current_dir, G_DIR_SEPARATOR_S)) dir_sep = ""; fname = g_strconcat(current_dir, dir_sep, name, NULL); g_free(name); if (dir_found) { setptr(current_dir, fname); refresh(); return; } else { documents->open_file(fname, FALSE, NULL, NULL); g_free(fname); } } } static void open_selected_files() { GtkTreeSelection *treesel; GtkTreeModel *model; GList *list; treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view)); list = gtk_tree_selection_get_selected_rows(treesel, &model); handle_selection(list, treesel); g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL); g_list_free(list); } static void on_hidden_files_clicked(GtkCheckMenuItem *item) { show_hidden_files = gtk_check_menu_item_get_active(item); refresh(); } static GtkWidget *create_popup_menu() { GtkWidget *item, *menu; menu = gtk_menu_new(); item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(menu), item); g_signal_connect((gpointer) item, "activate", G_CALLBACK(open_selected_files), NULL); item = gtk_separator_menu_item_new(); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(menu), item); item = gtk_check_menu_item_new_with_mnemonic(_("Show _Hidden Files")); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(menu), item); g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_hidden_files_clicked), NULL); item = gtk_separator_menu_item_new(); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(menu), item); item = gtk_image_menu_item_new_with_mnemonic(_("H_ide Sidebar")); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), gtk_image_new_from_stock("gtk-close", GTK_ICON_SIZE_MENU)); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(menu), item); g_signal_connect_swapped((gpointer) item, "activate", G_CALLBACK(keybindings->send_command), GINT_TO_POINTER(GEANY_KEYS_MENU_SIDEBAR)); return menu; } static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) open_selected_files(); else if (event->button == 3) { static GtkWidget *popup_menu = NULL; if (popup_menu == NULL) popup_menu = create_popup_menu(); gtk_menu_popup(GTK_MENU(popup_menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } return FALSE; } static void on_go_up() { // remove the highest directory part (which becomes the basename of current_dir) setptr(current_dir, g_path_get_dirname(current_dir)); refresh(); } static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) { if (event->keyval == GDK_Return || event->keyval == GDK_ISO_Enter || event->keyval == GDK_KP_Enter || event->keyval == GDK_space) open_selected_files(); if ((event->keyval == GDK_Up || event->keyval == GDK_KP_Up) && (event->state & GDK_MOD1_MASK)) // FIXME: Alt-Up doesn't seem to work! on_go_up(); return FALSE; } static void prepare_file_view() { GtkCellRenderer *text_renderer, *icon_renderer; GtkTreeViewColumn *column; GtkTreeSelection *select; PangoFontDescription *pfd; file_store = gtk_list_store_new(FILEVIEW_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model(GTK_TREE_VIEW(file_view), GTK_TREE_MODEL(file_store)); icon_renderer = gtk_cell_renderer_pixbuf_new(); text_renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new(); gtk_tree_view_column_pack_start(column, icon_renderer, FALSE); gtk_tree_view_column_set_attributes(column, icon_renderer, "stock-id", FILEVIEW_COLUMN_ICON, NULL); gtk_tree_view_column_pack_start(column, text_renderer, TRUE); gtk_tree_view_column_set_attributes(column, text_renderer, "text", FILEVIEW_COLUMN_NAME, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(file_view), column); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(file_view), FALSE); gtk_tree_view_set_enable_search(GTK_TREE_VIEW(file_view), TRUE); gtk_tree_view_set_search_column(GTK_TREE_VIEW(file_view), FILEVIEW_COLUMN_NAME); pfd = pango_font_description_from_string(prefs->tagbar_font); gtk_widget_modify_font(file_view, pfd); pango_font_description_free(pfd); // selection handling select = gtk_tree_view_get_selection(GTK_TREE_VIEW(file_view)); gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE); g_signal_connect(G_OBJECT(file_view), "realize", G_CALLBACK(on_current_path), NULL); g_signal_connect(G_OBJECT(file_view), "button-press-event", G_CALLBACK(on_button_press), NULL); g_signal_connect(G_OBJECT(file_view), "key-press-event", G_CALLBACK(on_key_press), NULL); } static GtkWidget *make_toolbar() { GtkWidget *wid, *toolbar; GtkTooltips *tooltips = GTK_TOOLTIPS(support->lookup_widget( app->window, "tooltips")); toolbar = gtk_toolbar_new(); gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU); gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS); wid = (GtkWidget *) gtk_tool_button_new_from_stock(GTK_STOCK_GO_UP); gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(wid), tooltips, _("Up"), NULL); g_signal_connect(G_OBJECT(wid), "clicked", G_CALLBACK(on_go_up), NULL); gtk_container_add(GTK_CONTAINER(toolbar), wid); wid = (GtkWidget *) gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH); gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(wid), tooltips, _("Refresh"), NULL); g_signal_connect(G_OBJECT(wid), "clicked", G_CALLBACK(refresh), NULL); gtk_container_add(GTK_CONTAINER(toolbar), wid); wid = (GtkWidget *) gtk_tool_button_new_from_stock(GTK_STOCK_HOME); gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(wid), tooltips, _("Home"), NULL); g_signal_connect(G_OBJECT(wid), "clicked", G_CALLBACK(on_go_home), NULL); gtk_container_add(GTK_CONTAINER(toolbar), wid); wid = (GtkWidget *) gtk_tool_button_new_from_stock(GTK_STOCK_JUMP_TO); gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(wid), tooltips, _("Set path from document"), NULL); g_signal_connect(G_OBJECT(wid), "clicked", G_CALLBACK(on_current_path), NULL); gtk_container_add(GTK_CONTAINER(toolbar), wid); return toolbar; } void init(GeanyData *data) { GtkWidget *scrollwin, *toolbar; file_view_vbox = gtk_vbox_new(FALSE, 0); toolbar = make_toolbar(); gtk_box_pack_start(GTK_BOX(file_view_vbox), toolbar, FALSE, FALSE, 0); path_entry = gtk_entry_new(); gtk_editable_set_editable(GTK_EDITABLE(path_entry), FALSE); gtk_box_pack_start(GTK_BOX(file_view_vbox), path_entry, FALSE, FALSE, 2); file_view = gtk_tree_view_new(); prepare_file_view(); scrollwin = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add(GTK_CONTAINER(scrollwin), file_view); gtk_container_add(GTK_CONTAINER(file_view_vbox), scrollwin); gtk_widget_show_all(file_view_vbox); gtk_notebook_append_page(GTK_NOTEBOOK(app->treeview_notebook), file_view_vbox, gtk_label_new(_("Files"))); } void cleanup() { gtk_widget_destroy(file_view_vbox); }