/* PureAdmin * Copyright (C) 2003 Isak Savo * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * Dirbrowser-widget (similar to the file-selector distributet with GTK+). This is * in fact a stand-alone widget that easily can be used in other projects. Please give * me credit for it and leave the copyright-notice is untouched. Thx! * * Simply add dirbrowser.c and .h to you project and use the functions declared in the .h-file * to access the widget! * * Copyright (C) 2003 Isak Savo */ /* FIXME: the text in the entry is the one that is used. We need a nicer handling! * Proposal: add a property, called 'directory' or similar a'la fileselector! */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include "dirbrowser.h" /* These should not be included if used in other projects */ #include "globals.h" #include "helper.h" #ifndef MAXPATHLEN # ifdef PATH_MAX # define MAXPATHLEN PATH_MAX # else # define MAXPATHLEN 2048 # endif /* PATH:MAX */ #endif /* MAXPATHLEN */ static GtkWindowClass *parent_class = NULL; /* Internal functions */ gboolean dirbrowser_select (Dirbrowser *dbr, const gchar *directory); /*gboolean dirbrowser_populate (Dirbrowser *dbr, const gchar *directory);*/ void dirbrowser_populate_full_dir (Dirbrowser *dbr, const char *directory); gint dirbrowser_populate_children (Dirbrowser *dbr, GtkTreeIter parent_iter, const char *directory); static void dirbrowser_class_init (DirbrowserClass *klass); static void dirbrowser_init (Dirbrowser *dbr); static void dirbrowser_finalize (GObject *object); static void dirbrowser_destroy (GtkObject *object); static void dirbrowser_map (GtkWidget *widget); void dirbrowser_cursor_changed (GtkTreeView *treeview, gpointer user_data); gboolean dirbrowser_btnpress_event (GtkTreeView *treeview, GdkEventButton *event, gpointer user_data); void dirbrowser_entry_changed (GtkEditable *entry, gpointer data); void dirbrowser_btnhome_clicked (GtkButton *btn, gpointer data); gchar **_dirbrowser_directory_split (const gchar *directory); gchar *_dirbrowser_treepath_to_fspath (const GtkTreePath *c_path, GtkTreeModel *model); GList *_dirbrowser_get_dircontents (const gchar *directory); static void dirbrowser_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void dirbrowser_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); enum { COL_IMAGE = 0, COL_NAME, COL_FSNAME, N_DIR_COLUMNS }; enum { PROP_0, PROP_DIRECTORY }; static gboolean grab_default (GtkWidget *widget) { gtk_widget_grab_default (widget); return FALSE; } GType dirbrowser_get_type (void) { static GType dirbrowser_type = 0; if (!dirbrowser_type) { static const GTypeInfo dirbrowser_info = { sizeof (DirbrowserClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) dirbrowser_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (Dirbrowser), 0, /* n_preallocs */ (GInstanceInitFunc) dirbrowser_init, }; dirbrowser_type = g_type_register_static (GTK_TYPE_DIALOG, "Dirbrowser", &dirbrowser_info, 0); } return dirbrowser_type; } static void dirbrowser_class_init (DirbrowserClass *klass) { GObjectClass *gobject_class; GtkObjectClass *object_class; GtkWidgetClass *widget_class; gobject_class = (GObjectClass*) klass; object_class = (GtkObjectClass*) klass; widget_class = (GtkWidgetClass*) klass; parent_class = g_type_class_peek_parent (klass); gobject_class->finalize = dirbrowser_finalize; object_class->destroy = dirbrowser_destroy; widget_class->map = dirbrowser_map; gobject_class->set_property = dirbrowser_set_property; gobject_class->get_property = dirbrowser_get_property; g_object_class_install_property (gobject_class, PROP_DIRECTORY, g_param_spec_string ("directory", N_("Directory"), N_("The currently selected directory"), NULL, G_PARAM_READABLE | G_PARAM_WRITABLE)); } static void dirbrowser_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { Dirbrowser *db; db = DIRBROWSER (object); switch (prop_id) { case PROP_DIRECTORY: dirbrowser_set_directory (db, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void dirbrowser_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { Dirbrowser *db; db = DIRBROWSER (object); switch (prop_id) { case PROP_DIRECTORY: g_value_set_string (value, dirbrowser_get_directory (db)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void dirbrowser_init (Dirbrowser *dbr) { GtkDialog *dlg; GtkTreeStore *store; GtkTreeViewColumn *column; GtkTreeIter iter; GtkCellRenderer *renderer_img, *renderer_txt; GtkWidget *vbox; dlg = GTK_DIALOG (dbr); gtk_widget_set_size_request (GTK_WIDGET (dlg), 250, 350); gtk_container_set_border_width (GTK_CONTAINER (dlg), 6); gtk_window_set_title (GTK_WINDOW (dlg), _("Select directory")); gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE); vbox = GTK_DIALOG (dlg)->vbox; gtk_widget_show (vbox); dbr->main_vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (dbr->main_vbox); gtk_box_pack_start (GTK_BOX (vbox), dbr->main_vbox, TRUE, TRUE, 0); gtk_container_set_border_width (GTK_CONTAINER (dbr->main_vbox), 6); dbr->btnbox = gtk_hbutton_box_new (); gtk_widget_show (dbr->btnbox); gtk_box_pack_start (GTK_BOX (dbr->main_vbox), dbr->btnbox, FALSE, FALSE, 0); gtk_button_box_set_layout (GTK_BUTTON_BOX (dbr->btnbox), GTK_BUTTONBOX_START); dbr->btn_home = gtk_button_new_from_stock ("gtk-home"); gtk_widget_show (dbr->btn_home); gtk_container_add (GTK_CONTAINER (dbr->btnbox), dbr->btn_home); GTK_WIDGET_SET_FLAGS (dbr->btn_home, GTK_CAN_DEFAULT); gtk_button_set_relief (GTK_BUTTON (dbr->btn_home), GTK_RELIEF_HALF); dbr->location = gtk_entry_new (); gtk_widget_show (dbr->location); gtk_box_pack_start (GTK_BOX (dbr->main_vbox), dbr->location, FALSE, FALSE, 0); dbr->scr_win = gtk_scrolled_window_new (NULL, NULL); gtk_widget_show (dbr->scr_win); gtk_box_pack_start (GTK_BOX (dbr->main_vbox), dbr->scr_win, TRUE, TRUE, 0); dbr->dir_tree = gtk_tree_view_new (); gtk_widget_show (dbr->dir_tree); gtk_container_add (GTK_CONTAINER (dbr->scr_win), dbr->dir_tree); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dbr->dir_tree), TRUE); gtk_widget_show (dlg->action_area); gtk_button_box_set_layout (GTK_BUTTON_BOX (dlg->action_area), GTK_BUTTONBOX_END); dbr->btn_cancel = gtk_button_new_from_stock ("gtk-cancel"); gtk_widget_show (dbr->btn_cancel); gtk_dialog_add_action_widget (GTK_DIALOG (dlg), dbr->btn_cancel, GTK_RESPONSE_CANCEL); GTK_WIDGET_SET_FLAGS (dbr->btn_cancel, GTK_CAN_DEFAULT); dbr->btn_ok = gtk_button_new_from_stock ("gtk-ok"); gtk_widget_show (dbr->btn_ok); gtk_dialog_add_action_widget (GTK_DIALOG (dlg), dbr->btn_ok, GTK_RESPONSE_OK); GTK_WIDGET_SET_FLAGS (dbr->btn_ok, GTK_CAN_DEFAULT); /* Initiate the treeview */ gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (dbr->dir_tree)), GTK_SELECTION_SINGLE); store = gtk_tree_store_new (N_DIR_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model (GTK_TREE_VIEW (dbr->dir_tree), GTK_TREE_MODEL (store)); g_object_unref (G_OBJECT (store)); column = gtk_tree_view_column_new (); renderer_img = gtk_cell_renderer_pixbuf_new (); renderer_txt = gtk_cell_renderer_text_new (); /* Only create a view for the first two columns (icon and utf8 name), the * last column is the *real* fsname, as got from g_dir_read_name() */ gtk_tree_view_column_pack_start (column, renderer_img, FALSE); gtk_tree_view_column_add_attribute (column, renderer_img, "pixbuf", COL_IMAGE); gtk_tree_view_column_pack_end (column, renderer_txt, TRUE); gtk_tree_view_column_add_attribute (column, renderer_txt, "text", COL_NAME); gtk_tree_view_insert_column (GTK_TREE_VIEW (dbr->dir_tree), column, -1); /* Directory-icon -- Rendered using the current theme */ dbr->icon = gtk_widget_render_icon (GTK_WIDGET (dbr), GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU, NULL); /* Add root-directory w/ children */ gtk_tree_store_append (GTK_TREE_STORE (store), &iter, NULL); gtk_tree_store_set (GTK_TREE_STORE (store), &iter, COL_IMAGE, dbr->icon, COL_NAME, "/", COL_FSNAME, "/", -1); dirbrowser_populate_children (dbr, iter, "/"); dbr->hid_cursor_changed = g_signal_connect ((gpointer) dbr->dir_tree, "cursor_changed", G_CALLBACK (dirbrowser_cursor_changed), (gpointer) dbr); g_signal_connect ((gpointer) dbr->dir_tree, "button_press_event", G_CALLBACK (dirbrowser_btnpress_event), (gpointer) dbr); dbr->hid_entry_changed = g_signal_connect ((gpointer) dbr->location, "changed", G_CALLBACK (dirbrowser_entry_changed), (gpointer) dbr); g_signal_connect_swapped (dbr->location, "focus_in_event", G_CALLBACK (grab_default), dbr->btn_ok); g_signal_connect_swapped (dbr->location, "activate", G_CALLBACK (gtk_button_clicked), dbr->btn_ok); g_signal_connect ((gpointer) dbr->btn_home, "clicked", G_CALLBACK (dirbrowser_btnhome_clicked), (gpointer) dbr); gtk_widget_grab_focus (dbr->location); } /* FIXME: Are these needed?? */ static void dirbrowser_finalize (GObject *object) { G_OBJECT_CLASS (parent_class)->finalize (object); } static void dirbrowser_destroy (GtkObject *object) { GTK_OBJECT_CLASS (parent_class)->destroy (object); } static void dirbrowser_map (GtkWidget *widget) { GTK_WIDGET_CLASS (parent_class)->map (widget); } void dirbrowser_set_directory (Dirbrowser *dir_browser, const char *directory) { gchar *directory_utf8; g_return_if_fail (IS_DIRBROWSER (dir_browser)); g_return_if_fail (directory != NULL); g_return_if_fail (g_file_test (directory, G_FILE_TEST_IS_DIR)); directory_utf8 = g_filename_to_utf8 (directory, -1, NULL, NULL, NULL); g_return_if_fail (directory_utf8 != NULL); if (dir_browser->location) gtk_entry_set_text (GTK_ENTRY (dir_browser->location), directory_utf8); g_object_notify (G_OBJECT (dir_browser), "directory"); g_free (directory_utf8); } /** * dirbrowser_get_directory: * @dir_browser: a #Dirbrowser * * This functions returns the current selected directory in the current * on-disk encoding. (see g_filename_from_utf8() in gtk-docs). * The returned string points to a statically allocated buffer and * should be copied if you plan to keep it around. * * If no directory is selected, the empty string, "", is returned. * * Return value: currently selected directory in the on-disk encoding. **/ G_CONST_RETURN gchar *dirbrowser_get_directory (Dirbrowser *dir_browser) { static const gchar nothing[2] = ""; static gchar something[MAXPATHLEN+2]; const gchar *utf8_name; gchar *sys_name; g_return_val_if_fail (IS_DIRBROWSER (dir_browser), nothing); if (dir_browser->location) { utf8_name = gtk_entry_get_text (GTK_ENTRY (dir_browser->location)); sys_name = g_filename_from_utf8 (utf8_name, -1, NULL, NULL, NULL); if (!sys_name) return nothing; strncpy (something, sys_name, sizeof (something)); g_free (sys_name); return something; } return nothing; } GtkWidget *dirbrowser_new (const gchar *title) { Dirbrowser *dbr; dbr = g_object_new (DIRBROWSER_TYPE, NULL); if (title) gtk_window_set_title (GTK_WINDOW (dbr), title); return GTK_WIDGET (dbr); } void dirbrowser_entry_changed (GtkEditable *entry, gpointer user_data) { Dirbrowser *dbr; gchar *location; location = gtk_editable_get_chars (entry, 0, -1); dbr = DIRBROWSER (user_data); if (g_file_test (location, G_FILE_TEST_IS_DIR)) { dirbrowser_populate_full_dir (dbr, location); dirbrowser_select (dbr, location); } g_free (location); } void dirbrowser_cursor_changed (GtkTreeView *treeview, gpointer user_data) { Dirbrowser *dbr; GtkTreeIter iter; GtkTreeModel *store; GtkTreeSelection *sel; GtkTreePath *path; gchar *fs_path; gint i = 0; dbr = DIRBROWSER (user_data); g_return_if_fail (IS_DIRBROWSER (dbr)); store = gtk_tree_view_get_model (treeview); sel = gtk_tree_view_get_selection (treeview); g_return_if_fail (gtk_tree_selection_get_selected (sel, &store, &iter)); path = gtk_tree_model_get_path (store, &iter); fs_path = _dirbrowser_treepath_to_fspath (path, store); if (!gtk_tree_model_iter_n_children (store, &iter)) i = dirbrowser_populate_children (dbr, iter, fs_path); //gtk_tree_view_expand_row (treeview, path, FALSE); g_signal_handler_block ((gpointer) dbr->location, dbr->hid_entry_changed); gtk_entry_set_text (GTK_ENTRY (dbr->location), fs_path); g_signal_handler_unblock ((gpointer) dbr->location, dbr->hid_entry_changed); g_free (fs_path); } gboolean dirbrowser_btnpress_event (GtkTreeView *treeview, GdkEventButton *event, gpointer user_data) { Dirbrowser *dbr; GtkTreeSelection *sel; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; dbr = DIRBROWSER (user_data); g_return_val_if_fail (IS_DIRBROWSER (dbr), FALSE); model = gtk_tree_view_get_model (treeview); sel = gtk_tree_view_get_selection (treeview); g_return_val_if_fail (gtk_tree_selection_get_selected (sel, &model, &iter), FALSE); if (event->type == GDK_2BUTTON_PRESS) { path = gtk_tree_model_get_path (model, &iter); if (gtk_tree_view_row_expanded (treeview, path)) gtk_tree_view_collapse_row (treeview, path); else gtk_tree_view_expand_row (treeview, path, FALSE); gtk_tree_path_free (path); } return FALSE; } void dirbrowser_btnhome_clicked (GtkButton *btn, gpointer data) { Dirbrowser *dbr; dbr = DIRBROWSER (data); g_return_if_fail (IS_DIRBROWSER (dbr)); gtk_entry_set_text (GTK_ENTRY (dbr->location), g_get_home_dir ()); } /* This function is taken directly from the GTK+-2.6.9 sources to maintain compatibility with * GTK 2.0.x */ static void pureadmin_gtk_tree_view_expand_to_path (GtkTreeView *tree_view, GtkTreePath *path) { gint i, depth; gint *indices; GtkTreePath *tmp; g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); g_return_if_fail (path != NULL); depth = gtk_tree_path_get_depth (path); indices = gtk_tree_path_get_indices (path); tmp = gtk_tree_path_new (); g_return_if_fail (tmp != NULL); for (i = 0; i < depth; i++) { gtk_tree_path_append_index (tmp, indices[i]); gtk_tree_view_expand_row (tree_view, tmp, FALSE); } gtk_tree_path_free (tmp); } gboolean dirbrowser_select (Dirbrowser *dbr, const gchar *directory) { GtkTreeModel *model; GtkTreeIter iter, child_iter; GtkTreePath *path; GtkTreeSelection *sel; gchar **dir_v, *tmp; gint i = 0; gboolean found = FALSE; g_return_val_if_fail (IS_DIRBROWSER (dbr), FALSE); g_return_val_if_fail (g_path_is_absolute (directory), FALSE); model = gtk_tree_view_get_model (GTK_TREE_VIEW (dbr->dir_tree)); gtk_tree_model_get_iter_first (model, &iter); dir_v = _dirbrowser_directory_split (directory); while (dir_v[i]/* && gtk_tree_store_iter_is_valid (GTK_TREE_STORE (model), &iter) */) { found = FALSE; gtk_tree_model_get (model, &iter, COL_FSNAME, &tmp, -1); if (strcmp (dir_v[i], tmp) == 0) { /* MATCH */ found = TRUE; if (gtk_tree_model_iter_children (model, &child_iter, &iter)) { if (dir_v[i+1]) memcpy (&iter, &child_iter, sizeof (GtkTreeIter)); i++; } else { g_free (tmp); break; } } else gtk_tree_model_iter_next (model, &iter); g_free (tmp); } if (found) { sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (dbr->dir_tree)); path = gtk_tree_model_get_path (model, &iter); g_signal_handler_block ((gpointer) dbr->dir_tree, dbr->hid_cursor_changed); pureadmin_gtk_tree_view_expand_to_path (GTK_TREE_VIEW (dbr->dir_tree), path); gtk_tree_view_expand_row (GTK_TREE_VIEW (dbr->dir_tree), path, FALSE); gtk_tree_selection_select_path (sel, path); while (g_main_context_pending (NULL)) g_main_context_iteration (NULL, FALSE); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (dbr->dir_tree), path, NULL, TRUE, 0.3, 0); g_signal_handler_unblock ((gpointer) dbr->dir_tree, dbr->hid_cursor_changed); gtk_tree_path_free (path); } return TRUE; } void dirbrowser_populate_full_dir (Dirbrowser *dbr, const char *directory) { GtkTreeModel *model; GtkTreeIter iter, child_iter; gchar **dir_v, *tmp; GString *cur_dir; gint i = 0, count; g_return_if_fail (IS_DIRBROWSER (dbr)); g_return_if_fail (g_path_is_absolute (directory)); model = gtk_tree_view_get_model (GTK_TREE_VIEW (dbr->dir_tree)); gtk_tree_model_get_iter_first (model, &iter); cur_dir = g_string_new (""); dir_v = _dirbrowser_directory_split (directory); while (dir_v[i]) { gtk_tree_model_get (model, &iter, COL_FSNAME, &tmp, -1); if (strcmp (dir_v[i], tmp) == 0) { cur_dir = g_string_append (cur_dir, dir_v[i]); if (!gtk_tree_model_iter_n_children (model, &iter)) { count = dirbrowser_populate_children (dbr, iter, cur_dir->str); if (count < 1) break; } gtk_tree_model_iter_children (model, &child_iter, &iter); memcpy (&iter, &child_iter, sizeof (GtkTreeIter)); if (*dir_v[i] != '/') cur_dir = g_string_append_c (cur_dir, '/'); i++; } else gtk_tree_model_iter_next (model, &iter); } g_strfreev (dir_v); g_string_free (cur_dir, TRUE); } gint dirbrowser_populate_children (Dirbrowser *dbr, GtkTreeIter parent_iter, const gchar *directory) { GtkTreeModel *model; GtkTreeIter child_iter; gchar *utf8_entry, *entry; GList *dirlist, *node; gint count = 0; g_return_val_if_fail (IS_DIRBROWSER (dbr), 0); model = gtk_tree_view_get_model (GTK_TREE_VIEW (dbr->dir_tree)); g_return_val_if_fail (g_path_is_absolute (directory) || model != NULL , 0); node = dirlist = _dirbrowser_get_dircontents (directory); g_return_val_if_fail (dirlist != NULL, 0); do { entry = (gchar *) node->data; utf8_entry = g_filename_to_utf8 (entry, -1, NULL, NULL, NULL); if (utf8_entry && g_utf8_validate (utf8_entry, -1, NULL)) { count++; gtk_tree_store_append (GTK_TREE_STORE (model), &child_iter, &parent_iter); gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter, COL_IMAGE, dbr->icon, COL_NAME, utf8_entry, COL_FSNAME, entry, -1); } else g_warning ("%s: Invalid entry: %s\n", __FUNCTION__, entry); g_free (utf8_entry); g_free (node->data); } while ((node = g_list_next(node))); g_list_free (dirlist); return count; } /* Returns the contents of a directory in a sorted GList */ GList *_dirbrowser_get_dircontents (const gchar *directory) { const gchar *entry; gchar *full_path; GDir *dir; GList *retval = NULL; dir = g_dir_open (directory, 0, NULL); g_return_val_if_fail (dir != NULL, NULL); while ((entry = g_dir_read_name (dir))) { full_path = g_strdup_printf ("%s/%s",directory, entry); /* FIXME: strcasecmp needs to be replaced here! */ if (g_file_test (full_path, G_FILE_TEST_IS_DIR) && *entry != '.') retval = g_list_insert_sorted (retval, g_strdup (entry), (GCompareFunc) g_strcasecmp); g_free (full_path); } g_dir_close (dir); return g_list_first (retval); } gchar **_dirbrowser_directory_split (const gchar *directory) { gchar **ret; gchar **tmp_v; gint i = 0, j = 0; tmp_v = g_strsplit (directory, "/", 0); if (!tmp_v) return g_strsplit ("", "/", 0); while (tmp_v[i++]) ; /* Check how many parts we have.. */ ret = g_new0 (gchar *, i); /* Was: (gchar **) g_malloc0 (i * sizeof (gchar *)); */ i = -1; ret[j++] = g_strdup ("/"); while (tmp_v[++i]) { if (*tmp_v[i] == '\0') continue; ret[j++] = g_strdup (tmp_v[i]); } g_strfreev (tmp_v); return ret; } gchar *_dirbrowser_treepath_to_fspath (const GtkTreePath *c_path, GtkTreeModel *model) { GString *fs_path; GtkTreeIter iter; GtkTreePath *path; gchar *ret, *entry; path = gtk_tree_path_copy (c_path); if (gtk_tree_path_get_depth (path) == 1) return g_strdup ("/"); fs_path = g_string_new (""); while (gtk_tree_path_get_depth (path) > 1) { gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, COL_FSNAME, &entry, -1); g_string_prepend (fs_path, entry); if (*entry != '/') g_string_prepend_c (fs_path, '/'); gtk_tree_path_up (path); } ret = fs_path->str; g_string_free (fs_path, FALSE); gtk_tree_path_free (path); return ret; }