/* vi:set ts=8 sts=0 sw=8: * $Id: gtkefilesel.c,v 1.13 2000/03/14 07:17:48 kahn Exp kahn $ * * GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * Enhanced File Selection Dialog (GtkEfileSel) by Andy Kahn, 1999. * Modified from the original File Selection Dialog found in gtk+-1.2.3. * See below for details on modifications. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ /* * Gtk Enhanced File Selection by Andy Kahn, 1999. Tested with gtk+-1.0.6 and * gtk+-1.2.3. Changes include: * * - multiple filename selection. One can select multiple files within the * the same directory. The selection entry space will show the filenames * delimited with a space. * * What really happens is this: each time an entry in the file clist is * selected, that filename is added to a linked list of filenames. Each time * it is unselected, it is removed. When the linked list gets updated, so * does the entry text space. This might be a little misleading when the * filenames themselves contain spaces (however stupid and unlikely that may * be), but I don't see a clean way of doing this. * * There are now two interfaces for getting the selected file(s): * * 1. The original gtk_efilesel_get_filename(), which will only return * one filename. If there was more than one selected, only the first * filename that was selected is returned. * * 2. gtk_efilesel_get_filename_list(), will return a pointer to a singly * linked list of filenames (a GSList). The GSList is NOT newly * allocated, and neither is each node's data field, which contains * a filename. * * Refer to the header file (gtkefilesel.h) for any other details on how * the public functions should work. * * - a "refresh" button to refresh directory contents. * * - a "File Details" listing, where it shows you some details about the first * selected file. This includes the file's last modification time, the file * size, and the owner of the file. * * - ran all code through "indent -kr -i8" and did some indentation by hand as * I came across code. GNU indentation style is stupid. * * - rename identifiers with shorter names. I don't understand why people like * Microsoft-styled-super-long-identifier names. I was once even told that * longer names looked "more professional". What a moron. Long names just * beg for errors. One should only make it long enough for clarity, not * verbosity. Anyway, partial strings e.g., * * gtk_file_selection to gtk_efilesel * GtkFileSelection to GtkEfileSel * Completion to Cmpl * completion to cmpl * * - rename function names such that private/static functions do *NOT* start * with a "gtk_" prefix. Only public functions should... * * - there's still a lot of duplicate code/cruft left over from the original * GtkFileSelection code. For example, the functions efilesel_create_dir() * and efilesel_rename_file() are virtually identical. */ #include "../config.h" #ifdef USE_ENHANCED_FSEL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gtkefilesel.h" #include "misc.h" /* for my_basename() */ #include "dialog.h" /* for my_basename() */ #include "config.h" #include "gnpintl.h" #ifndef GTK_HAVE_FEATURES_1_1_0 # ifndef gtk_label_set_text # define gtk_label_set_text(wgt, str) gtk_label_set(wgt, str) # endif # ifndef gtk_container_set_border_width # define gtk_container_set_border_width(w, v) gtk_container_border_width(w, v) # endif # ifndef gtk_window_set_position # define gtk_window_set_position(gw, pos) gtk_window_position((gw), (pos)) # endif #endif /* * Remove the "#if 0" and the matching "endif" if you want to use gtkefilesel.c * separately, without gnpintl.h. */ #if 0 #ifdef ENABLE_NLS # include # define _(String) dgettext("gtk+",String) # ifdef gettext_noop # define N_(String) gettext_noop(String) # else # define N_(String) (String) # endif #else /* NLS is disabled */ # define _(String) (String) # define N_(String) (String) # define textdomain(String) (String) # define gettext(String) (String) # define dgettext(Domain,String) (String) # define dcgettext(Domain,String,Type) (String) # define bindtextdomain(Domain,Directory) (Domain) #endif #endif #define DIR_LIST_WIDTH 180 #define DIR_LIST_HEIGHT 180 #define FILE_LIST_WIDTH 180 #define FILE_LIST_HEIGHT 180 /* I've put this here so it doesn't get confused with the * file completion interface */ typedef struct _HistoryCallbackArg HistoryCallbackArg; struct _HistoryCallbackArg { char *directory; GtkWidget *menu_item; }; typedef struct _CmplState CmplState; typedef struct _CmplDir CmplDir; typedef struct _CmplDirSent CmplDirSent; typedef struct _CmplDirEntry CmplDirEntry; typedef struct _CmplUserDir CmplUserDir; typedef struct _PossibleCmpl PossibleCmpl; /* Non-external file completion decls and structures */ /* A contant telling PRCS how many directories to cache. Its actually * kept in a list, so the geometry isn't important. */ #define CMPL_DIRECTORY_CACHE_SIZE 10 /* A constant used to determine whether a substring was an exact * match by efilesel_first_diff_index() */ #define PATTERN_MATCH -1 /* The arguments used by all fnmatch() calls below */ #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD) #define CMPL_ERRNO_TOO_LONG ((1<<16)-1) /* This structure contains all the useful information about a directory * for the purposes of filename completion. These structures are cached * in the CmplState struct. CmplDir's are reference counted. */ struct _CmplDirSent { ino_t inode; time_t mtime; dev_t device; int entry_count; char *name_buffer; /* memory segment containing names of all entries */ struct _CmplDirEntry *entries; }; struct _CmplDir { CmplDirSent *sent; char *fullname; int fullname_len; struct _CmplDir *cmpl_parent; int cmpl_index; char *cmpl_text; }; /* This structure contains pairs of directory entry names with a flag saying * whether or not they are a valid directory. NOTE: This information is used * to provide the caller with information about whether to update its completions * or try to open a file. Since directories are cached by the directory mtime, * a symlink which points to an invalid file (which will not be a directory), * will not be reevaluated if that file is created, unless the containing * directory is touched. I consider this case to be worth ignoring (josh). */ struct _CmplDirEntry { int is_dir; char *entry_name; }; struct _CmplUserDir { char *login; char *homedir; }; struct _PossibleCmpl { /* accessible fields, all are accessed externally by functions * declared above */ char *text; int is_a_cmpl; int is_directory; /* Private fields */ int text_alloc; }; struct _CmplState { int last_valid_char; char *updated_text; int updated_text_len; int updated_text_alloc; int re_complete; char *user_dir_name_buffer; int user_directories_len; char *last_cmpl_text; int user_cmpl_index; /* if >= 0, currently completing ~user */ struct _CmplDir *cmpl_dir; /* directory completing from */ struct _CmplDir *active_cmpl_dir; struct _PossibleCmpl the_cmpl; struct _CmplDir *reference_dir; /* initial directory */ GList *directory_storage; GList *directory_sent_storage; struct _CmplUserDir *user_directories; }; /* File completion functions which would be external, were they used * outside of this file. */ static CmplState *cmpl_init_state(void); static void cmpl_free_state(CmplState * cmpl_state); static int efilesel_cmpl_state_okay(CmplState * cmpl_state); static char *efile_cmpl_strerror(int); static PossibleCmpl *cmpl_cmpl_matches(char * text_to_complete, char ** remaining_text, CmplState * cmpl_state); /* Returns a name for consideration, possibly a completion, this name * will be invalid after the next call to cmpl_next_cmpl. */ static char *cmpl_this_cmpl(PossibleCmpl *); /* True if this completion matches the given text. Otherwise, this * output can be used to have a list of non-completions. */ static int cmpl_is_a_cmpl(PossibleCmpl *); /* True if the completion is a directory */ static int cmpl_is_directory(PossibleCmpl *); /* Obtains the next completion, or NULL */ static PossibleCmpl *cmpl_next_cmpl(CmplState *); /* Updating completions: the return value of cmpl_updated_text() will * be text_to_complete completed as much as possible after the most * recent call to cmpl_cmpl_matches. For the present * application, this is the suggested replacement for the user's input * string. You must CALL THIS AFTER ALL cmpl_text_cmpls have * been received. */ static char *cmpl_updated_text(CmplState * cmpl_state); /* After updating, to see if the completion was a directory, call * this. If it was, you should consider re-calling cmpl_matches. */ static int cmpl_updated_dir(CmplState * cmpl_state); /* Current location: if using file completion, return the current * directory, from which file completion begins. More specifically, * the cwd concatenated with all exact completions up to the last * directory delimiter('/'). */ static char *cmpl_reference_position(CmplState * cmpl_state); /* backing up: if cmpl_cmpl_matches returns NULL, you may query * the index of the last completable character into cmpl_updated_text. */ static int cmpl_last_valid_char(CmplState * cmpl_state); /* When the user selects a non-directory, call cmpl_cmpl_fullname * to get the full name of the selected file. */ static char *cmpl_cmpl_fullname(char *, CmplState * cmpl_state); /* Directory operations. */ static CmplDir *open_ref_dir(char * text_to_complete, char ** remaining_text, CmplState * cmpl_state); static gboolean efilesel_check_dir(char * dir_name, struct stat *result, gboolean * stat_subdirs); static CmplDir *efilesel_open_dir(char * dir_name, CmplState * cmpl_state); static CmplDir *open_user_dir(char * text_to_complete, CmplState * cmpl_state); static CmplDir *open_relative_dir(char * dir_name, CmplDir * dir, CmplState * cmpl_state); static CmplDirSent *open_new_dir(char * dir_name, struct stat *sbuf, gboolean stat_subdirs); static int efilesel_correct_dir_fullname(CmplDir * cmpl_dir); static int efilesel_correct_parent(CmplDir * cmpl_dir, struct stat *sbuf); static char *efilesel_find_parent_dir_fullname(char * dirname); static CmplDir *efilesel_attach_dir(CmplDirSent * sent, char * dir_name, CmplState * cmpl_state); static void free_dir_sent(CmplDirSent * sent); static void free_dir(CmplDir * dir); static void prune_memory_usage(CmplState * cmpl_state); /* Cmpl operations */ static PossibleCmpl *efilesel_attempt_homedir_cmpl(char * text_to_complete, CmplState * cmpl_state); static PossibleCmpl *efilesel_attempt_file_cmpl(CmplState * cmpl_state); static CmplDir *efilesel_find_cmpl_dir(char * text_to_complete, char ** remaining_text, CmplState * cmpl_state); static PossibleCmpl *efilesel_append_cmpl_text(char * text, CmplState * cmpl_state); static int efilesel_get_pwdb(CmplState * cmpl_state); static int efilesel_first_diff_index(char * pat, char * text); static int efilesel_compare_user_dir(const void *a, const void *b); static int efilesel_compare_cmpl_dir(const void *a, const void *b); static void efilesel_update_cmpl(PossibleCmpl * poss, CmplState * cmpl_state); static void efilesel_class_init(GtkEfileSelClass * klass); static void efilesel_init(GtkEfileSel * fsel); static void efilesel_destroy(GtkObject * object); static int efilesel_key_press(GtkWidget * widget, GdkEventKey * event, gpointer user_data); static void efilesel_select_row_cb(GtkWidget * widget, int row, int column, GdkEventButton * bevent, gpointer user_data); static void efilesel_unselect_row_cb(GtkWidget * widget, int row, int column, GdkEventButton * bevent, gpointer user_data); static void efilesel_dir_button(GtkWidget * widget, int row, int column, GdkEventButton * bevent, gpointer data); static void efilesel_populate(GtkEfileSel *fs, char *rel_path, int try_cmplete); static void efilesel_abort(GtkEfileSel * fs); static void efilesel_update_history_menu(GtkEfileSel *fs, char *curdir); static void efilesel_create_dir(GtkWidget * widget, gpointer data); static void efilesel_delete_file(GtkWidget * widget, gpointer data); static void efilesel_rename_file(GtkWidget * widget, gpointer data); #if 0 static void efilesel_file_info_cb(GtkWidget *wgt, gpointer cbdata); #endif static void efilesel_reread_cb(GtkWidget *wgt, gpointer cbdata); static char *efilesel_fname_list_construct(GtkEfileSel *fs); static void efilesel_fname_list_add(GtkEfileSel *fs, char *fname); static void efilesel_fname_list_remove(GtkEfileSel *fs, const char *fname); static void efilesel_fname_list_destroy(GtkEfileSel *fsel); static void efilesel_reset_fname_list(GtkWidget *wgt, gpointer cbdata); static void efilesel_details_update(GtkEfileSel *fs, char *fullpath); static GtkWindowClass *parent_class = NULL; /* Saves errno when something cmpl does fails. */ static int cmpl_errno; GtkType gtk_efilesel_get_type(void) { static GtkType file_selection_type = 0; if (!file_selection_type) { static const GtkTypeInfo filesel_info = { "GtkEfileSel", sizeof(GtkEfileSel), sizeof(GtkEfileSelClass), (GtkClassInitFunc) efilesel_class_init, (GtkObjectInitFunc) efilesel_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, #ifdef GTK_HAVE_FEATURES_1_1_0 (GtkClassInitFunc) NULL, #endif }; file_selection_type = gtk_type_unique( GTK_TYPE_WINDOW, (GtkTypeInfo *)&filesel_info); } return file_selection_type; } static void efilesel_class_init(GtkEfileSelClass * class) { GtkObjectClass *object_class; object_class = (GtkObjectClass *) class; parent_class = gtk_type_class(GTK_TYPE_WINDOW); object_class->destroy = efilesel_destroy; } static void efilesel_init(GtkEfileSel *fsel) { GtkWidget *main_vbox; GtkWidget *action_area; GtkWidget *entry_vbox; GtkWidget *hbox; GtkWidget *tmp; char *dir_title[2]; char *file_title[2]; fsel->cmpl_state = cmpl_init_state(); /* The dialog-sized vertical box */ main_vbox = gtk_vbox_new(FALSE, 10); gtk_container_set_border_width(GTK_CONTAINER(fsel), 10); gtk_container_add(GTK_CONTAINER(fsel), main_vbox); gtk_widget_show(main_vbox); /* The horizontal box containing create, rename etc. buttons */ fsel->button_area = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(fsel->button_area), GTK_BUTTONBOX_START); gtk_button_box_set_spacing(GTK_BUTTON_BOX(fsel->button_area), 0); gtk_box_pack_start(GTK_BOX(main_vbox), fsel->button_area, FALSE, FALSE, 0); gtk_efilesel_show_fileop_buttons(fsel); /* hbox for pulldown menu */ tmp = gtk_hbox_new(TRUE, 5); gtk_box_pack_start(GTK_BOX(main_vbox), tmp, FALSE, FALSE, 0); /* Pulldown menu */ fsel->history_pulldown = gtk_option_menu_new(); gtk_box_pack_start(GTK_BOX(tmp), fsel->history_pulldown, FALSE, FALSE, 0); gtk_widget_show_all(tmp); /* The horizontal box containing the directory and file listboxes */ hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(main_vbox), hbox, TRUE, TRUE, 0); gtk_widget_show(hbox); /* The directories clist */ dir_title[0] = _("Directories"); dir_title[1] = NULL; fsel->dir_list = gtk_clist_new_with_titles(1, (char **) dir_title); gtk_widget_set_usize(fsel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT); gtk_signal_connect(GTK_OBJECT(fsel->dir_list), "select_row", (GtkSignalFunc) efilesel_dir_button, (gpointer) fsel); gtk_clist_column_titles_passive(GTK_CLIST(fsel->dir_list)); tmp = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(tmp), fsel->dir_list); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tmp), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_container_set_border_width(GTK_CONTAINER(tmp), 5); gtk_box_pack_start(GTK_BOX(hbox), tmp, TRUE, TRUE, 0); gtk_widget_show(fsel->dir_list); gtk_widget_show(tmp); /* The files clist */ file_title[0] = _("Files"); file_title[1] = NULL; fsel->file_list = gtk_clist_new_with_titles(1, (char **)file_title); gtk_clist_set_selection_mode(GTK_CLIST(fsel->file_list), GTK_SELECTION_MULTIPLE); gtk_widget_set_usize(fsel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT); gtk_signal_connect(GTK_OBJECT(fsel->file_list), "select_row", (GtkSignalFunc)efilesel_select_row_cb, (gpointer)fsel); gtk_signal_connect(GTK_OBJECT(fsel->file_list), "unselect_row", (GtkSignalFunc)efilesel_unselect_row_cb, (gpointer)fsel); gtk_clist_column_titles_passive(GTK_CLIST(fsel->file_list)); tmp = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(tmp), fsel->file_list); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tmp), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_container_set_border_width(GTK_CONTAINER(tmp), 5); gtk_box_pack_start(GTK_BOX(hbox), tmp, TRUE, TRUE, 0); gtk_widget_show(fsel->file_list); gtk_widget_show(tmp); /* for the detailed file info area */ fsel->fileinfo = gtk_table_new(6, 2, FALSE); gtk_box_pack_start(GTK_BOX(main_vbox), fsel->fileinfo, FALSE, FALSE, 0); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), gtk_hseparator_new(), 0, 2, 0, 1); tmp = gtk_label_new( _("Filename:")); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), tmp, 0, 1, 1, 2); gtk_misc_set_alignment(GTK_MISC(tmp), 0, 0.5); fsel->fname = gtk_label_new(""); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), fsel->fname, 1, 2, 1, 2); tmp = gtk_label_new( _("Last modified:")); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), tmp, 0, 1, 2, 3); gtk_misc_set_alignment(GTK_MISC(tmp), 0, 0.5); fsel->mtime = gtk_label_new(""); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), fsel->mtime, 1, 2, 2, 3); tmp = gtk_label_new( _("File size:")); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), tmp, 0, 1, 3, 4); gtk_misc_set_alignment(GTK_MISC(tmp), 0, 0.5); fsel->fsize = gtk_label_new(""); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), fsel->fsize, 1, 2, 3, 4); tmp = gtk_label_new( _("Owner:")); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), tmp, 0, 1, 4, 5); gtk_misc_set_alignment(GTK_MISC(tmp), 0, 0.5); fsel->owner = gtk_label_new(""); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), fsel->owner, 1, 2, 4, 5); gtk_table_attach_defaults(GTK_TABLE(fsel->fileinfo), gtk_hseparator_new(), 0, 2, 5, 6); gtk_widget_show_all(fsel->fileinfo); /* action area for packing buttons into. */ action_area = gtk_hbox_new(TRUE, 0); gtk_box_pack_end(GTK_BOX(main_vbox), action_area, FALSE, FALSE, 0); gtk_widget_show(action_area); /* the Reread button area */ hbox = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_START); gtk_button_box_set_spacing(GTK_BUTTON_BOX(hbox), 5); gtk_box_pack_start(GTK_BOX(action_area), hbox, FALSE, FALSE, 0); gtk_widget_show(hbox); /* The "Reread Dir" button */ (void)misc_button_new_w_label(_(" Reread Dir "), NULL, GTK_SIGNAL_FUNC(efilesel_reread_cb), fsel, hbox, PACK_END | CANCEL_DEFAULT | SHOW_BUTTON, 0); #if 0 /* File Details button */ tmp = gtk_button_new_with_label(_(" Details ")); gtk_signal_connect(GTK_OBJECT(tmp), "clicked", (GtkSignalFunc)efilesel_file_info_cb, (gpointer)fsel); GTK_WIDGET_SET_FLAGS(tmp, GTK_CAN_DEFAULT); gtk_box_pack_end(GTK_BOX(hbox), tmp, TRUE, TRUE, 0); gtk_widget_show(tmp); #endif /* The OK/Cancel button area */ tmp = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(tmp), GTK_BUTTONBOX_END); gtk_button_box_set_spacing(GTK_BUTTON_BOX(tmp), 5); gtk_box_pack_end(GTK_BOX(action_area), tmp, FALSE, FALSE, 0); /* The OK button */ fsel->ok_button = misc_button_new_w_label( _(" Ok "), GNOME_STOCK_BUTTON_OK, NULL, NULL, tmp, PACK_END | PACK_EXPAND | CANCEL_DEFAULT | GRAB_DEFAULT, 0); /* The Cancel button */ fsel->cancel_button = misc_button_new_w_label( _("Cancel"), GNOME_STOCK_BUTTON_CANCEL, NULL, NULL, tmp, PACK_END | PACK_EXPAND | CANCEL_DEFAULT, 0); gtk_widget_show_all(tmp); /* The selection entry widget */ entry_vbox = gtk_vbox_new(FALSE, 2); gtk_box_pack_end(GTK_BOX(main_vbox), entry_vbox, FALSE, FALSE, 0); gtk_widget_show(entry_vbox); fsel->selection_text = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(fsel->selection_text), 0.0, 0.5); gtk_box_pack_start(GTK_BOX(entry_vbox), fsel->selection_text, FALSE, FALSE, 0); gtk_widget_show(fsel->selection_text); fsel->selection_entry = gtk_entry_new(); gtk_signal_connect(GTK_OBJECT(fsel->selection_entry), "key_press_event", (GtkSignalFunc)efilesel_key_press, fsel); gtk_signal_connect_object(GTK_OBJECT(fsel->selection_entry), "focus_in_event", (GtkSignalFunc) gtk_widget_grab_default, GTK_OBJECT(fsel->ok_button)); gtk_signal_connect_object(GTK_OBJECT(fsel->selection_entry), "activate", (GtkSignalFunc) gtk_button_clicked, GTK_OBJECT(fsel->ok_button)); gtk_box_pack_start(GTK_BOX(entry_vbox), fsel->selection_entry, TRUE, TRUE, 0); gtk_widget_show(fsel->selection_entry); if (!efilesel_cmpl_state_okay(fsel->cmpl_state)) { char err_buf[256]; sprintf(err_buf, _("Directory unreadable: %s"), efile_cmpl_strerror(cmpl_errno)); gtk_label_set_text(GTK_LABEL(fsel->selection_text), err_buf); } else { efilesel_populate(fsel, "", FALSE); } gtk_widget_grab_focus(fsel->selection_entry); } /* efilesel_init */ /* * Creates a new Efile dialog. */ GtkWidget * gtk_efilesel_new(const char * title) { GtkEfileSel *fsel; fsel = gtk_type_new(GTK_TYPE_EFILESEL); gtk_window_set_title(GTK_WINDOW(fsel), title); /* * Every time we show this dialog, we need to clear out the fname list, * any selected files, and anything in the selection entry text. */ gtk_signal_connect(GTK_OBJECT(fsel), "show", (GtkSignalFunc)efilesel_reset_fname_list, (gpointer)fsel); return GTK_WIDGET(fsel); } void gtk_efilesel_show_fileop_buttons(GtkEfileSel * fsel) { g_return_if_fail(fsel != NULL); g_return_if_fail(GTK_IS_EFILESEL(fsel)); /* delete, create directory, and rename */ if (!fsel->fileop_c_dir) { fsel->fileop_c_dir = misc_button_new_w_label( _("Create Dir"), NULL, GTK_SIGNAL_FUNC(efilesel_create_dir), fsel, fsel->button_area, PACK_START | PACK_EXPAND | PACK_FILL, 0); } if (!fsel->fileop_del_file) { fsel->fileop_del_file = misc_button_new_w_label( _("Delete File"), NULL, GTK_SIGNAL_FUNC(efilesel_delete_file), fsel, fsel->button_area, PACK_START | PACK_EXPAND | PACK_FILL, 0); } if (!fsel->fileop_ren_file) { fsel->fileop_ren_file = misc_button_new_w_label( _("Rename File"), NULL, GTK_SIGNAL_FUNC(efilesel_rename_file), fsel, fsel->button_area, PACK_START | PACK_EXPAND | PACK_FILL, 0); } gtk_widget_show_all(fsel->button_area); gtk_widget_queue_resize(GTK_WIDGET(fsel)); } void gtk_efilesel_hide_fileop_buttons(GtkEfileSel * fsel) { g_return_if_fail(fsel != NULL); g_return_if_fail(GTK_IS_EFILESEL(fsel)); if (fsel->fileop_ren_file) { gtk_widget_destroy(fsel->fileop_ren_file); fsel->fileop_ren_file = NULL; } if (fsel->fileop_del_file) { gtk_widget_destroy(fsel->fileop_del_file); fsel->fileop_del_file = NULL; } if (fsel->fileop_c_dir) { gtk_widget_destroy(fsel->fileop_c_dir); fsel->fileop_c_dir = NULL; } } void gtk_efilesel_set_filename(GtkEfileSel *fsel, const char *filename) { char buf[MAXPATHLEN]; const char *name, *last_slash; g_return_if_fail(fsel != NULL); g_return_if_fail(GTK_IS_EFILESEL(fsel)); g_return_if_fail(filename != NULL); last_slash = strrchr(filename, '/'); if (!last_slash) { buf[0] = 0; name = filename; } else { int len = MIN(MAXPATHLEN - 1, last_slash - filename + 1); strncpy(buf, filename, len); buf[len] = 0; name = last_slash + 1; } efilesel_populate(fsel, buf, FALSE); /* add to fname_list */ efilesel_fname_list_add(fsel, (char *)name); /* update entry space with fname_list */ if (fsel->selection_entry) { char *buf = efilesel_fname_list_construct(fsel); gtk_entry_set_text(GTK_ENTRY(fsel->selection_entry), buf); g_free(buf); } } /* * Returns a single filename that was selected or entered. If there were * multiple selections, only return the first one. Otherwise, return whatever * is in the entry space (should only have one filename there). */ char * gtk_efilesel_get_filename(GtkEfileSel *fsel) { GtkCList *clist; char *txt; /* * I don't understand the rationale on returning "" instead of just * returning NULL, but I'll leave it here for now. */ static char nothing[2] = ""; g_return_val_if_fail(fsel != NULL, nothing); g_return_val_if_fail(GTK_IS_EFILESEL(fsel), nothing); clist = GTK_CLIST(fsel->file_list); if (clist->selection_mode == GTK_SELECTION_MULTIPLE && fsel->fname_list) { return cmpl_cmpl_fullname((char *)fsel->fname_list->data, fsel->cmpl_state); } g_return_val_if_fail(clist->selection_mode == GTK_SELECTION_SINGLE, nothing); txt = gtk_entry_get_text(GTK_ENTRY(fsel->selection_entry)); if (txt) { char *fname = cmpl_cmpl_fullname(txt, fsel->cmpl_state); return fname; } return nothing; } /* gtk_efilesel_get_filename */ GSList * gtk_efilesel_get_filename_list(GtkEfileSel *fsel) { return fsel->fname_list; } void gtk_efilesel_complete(GtkEfileSel *fsel, const char *pat) { g_return_if_fail(fsel != NULL); g_return_if_fail(GTK_IS_EFILESEL(fsel)); g_return_if_fail(pat != NULL); if (fsel->selection_entry) gtk_entry_set_text(GTK_ENTRY(fsel->selection_entry), pat); efilesel_populate(fsel, (char *)pat, TRUE); } static void efilesel_destroy(GtkObject * object) { GtkEfileSel *fsel; GList *list; HistoryCallbackArg *callback_arg; g_return_if_fail(object != NULL); g_return_if_fail(GTK_IS_EFILESEL(object)); fsel = GTK_EFILESEL(object); if (fsel->fileop_dialog) gtk_widget_destroy(fsel->fileop_dialog); efilesel_fname_list_destroy(fsel); if (fsel->history_list) { for (list = fsel->history_list; list; list = list->next) { callback_arg = list->data; g_free(callback_arg->directory); g_free(callback_arg); } g_list_free(fsel->history_list); fsel->history_list = NULL; } cmpl_free_state(fsel->cmpl_state); fsel->cmpl_state = NULL; if (GTK_OBJECT_CLASS(parent_class)->destroy) (*GTK_OBJECT_CLASS(parent_class)->destroy) (object); } /* Begin file operations callbacks */ static void efilesel_fileop_destroy(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); fs->fileop_dialog = NULL; } static void efilesel_mkdir_do(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; char *dirname; char *path; char *full_path; char *buf; CmplState *cmpl_state; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); dirname = gtk_entry_get_text(GTK_ENTRY(fs->fileop_entry)); cmpl_state = (CmplState *) fs->cmpl_state; path = cmpl_reference_position(cmpl_state); full_path = g_strconcat(path, "/", dirname, NULL); if ((mkdir(full_path, 0755) < 0)) { buf = g_strconcat( _("Error creating directory \""), dirname, "\": ", g_strerror(errno), NULL); do_dialog_error("Error", buf); g_free(buf); } g_free(full_path); gtk_widget_destroy(fs->fileop_dialog); efilesel_reread_cb(NULL, (gpointer)fs); } static void efilesel_create_dir(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; GtkWidget *label; GtkWidget *dialog; GtkWidget *vbox; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); if (fs->fileop_dialog) return; /* main dialog */ fs->fileop_dialog = dialog = gtk_dialog_new(); gtk_signal_connect(GTK_OBJECT(dialog), "destroy", (GtkSignalFunc) efilesel_fileop_destroy, (gpointer) fs); gtk_window_set_title(GTK_WINDOW(dialog), _("Create Directory")); gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); #ifdef GTK_HAVE_FEATURES_1_1_0 /* If file dialog is grabbed, grab option dialog */ /* When option dialog is closed, file dialog will be grabbed again */ if (GTK_WINDOW(fs)->modal) gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); #endif vbox = gtk_vbox_new(FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, FALSE, FALSE, 0); label = gtk_label_new(_("Directory name:")); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); /* The directory entry widget */ fs->fileop_entry = gtk_entry_new(); gtk_box_pack_start(GTK_BOX(vbox), fs->fileop_entry, TRUE, TRUE, 5); GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT); /* buttons */ (void)misc_button_new_w_label(_("Create"), NULL, GTK_SIGNAL_FUNC(efilesel_mkdir_do), fs, GTK_DIALOG(dialog)->action_area, PACK_START | PACK_EXPAND | PACK_FILL | CANCEL_DEFAULT | SHOW_BUTTON, 0); (void)misc_button_new_w_label(_("Cancel"), GNOME_STOCK_BUTTON_CANCEL, GTK_SIGNAL_FUNC(gtk_widget_destroy), dialog, GTK_DIALOG(dialog)->action_area, PACK_START | PACK_EXPAND | PACK_FILL | CANCEL_DEFAULT | GRAB_DEFAULT | SHOW_BUTTON | CONNECT_OBJ, 0); gtk_widget_show_all(dialog); } static void efilesel_unlink_do(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; CmplState *cmpl_state; char *path; char *full_path; char *buf; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); cmpl_state = (CmplState *) fs->cmpl_state; path = cmpl_reference_position(cmpl_state); full_path = g_strconcat(path, "/", fs->fileop_file, NULL); if ((unlink(full_path) < 0)) { buf = g_strconcat( _("Error deleting file \""), fs->fileop_file, "\": ", g_strerror(errno), NULL); do_dialog_error("Error", buf); g_free(buf); } g_free(full_path); gtk_widget_destroy(fs->fileop_dialog); efilesel_reread_cb(NULL, (gpointer)fs); } static void efilesel_delete_file(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; GtkWidget *label; GtkWidget *vbox; GtkWidget *dialog; char *filename; char *buf; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); if (fs->fileop_dialog) return; filename = gtk_entry_get_text(GTK_ENTRY(fs->selection_entry)); if (strlen(filename) < 1) return; fs->fileop_file = filename; /* main dialog */ fs->fileop_dialog = dialog = gtk_dialog_new(); gtk_signal_connect(GTK_OBJECT(dialog), "destroy", (GtkSignalFunc) efilesel_fileop_destroy, (gpointer) fs); gtk_window_set_title(GTK_WINDOW(dialog), _("Delete File")); gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); #ifdef GTK_HAVE_FEATURES_1_1_0 /* If file dialog is grabbed, grab option dialog */ /* When option dialog is closed, file dialog will be grabbed again */ if (GTK_WINDOW(fs)->modal) gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); #endif vbox = gtk_vbox_new(FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, FALSE, FALSE, 0); buf = g_strconcat("Really delete file \"", filename, "\" ?", NULL); label = gtk_label_new(buf); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); g_free(buf); /* buttons */ (void)misc_button_new_w_label(_("Delete"), NULL, GTK_SIGNAL_FUNC(efilesel_unlink_do), fs, GTK_DIALOG(dialog)->action_area, PACK_START | PACK_EXPAND | PACK_FILL | CANCEL_DEFAULT | SHOW_BUTTON, 0); (void)misc_button_new_w_label(_("Cancel"), GNOME_STOCK_BUTTON_CANCEL, GTK_SIGNAL_FUNC(gtk_widget_destroy), dialog, GTK_DIALOG(dialog)->action_area, PACK_START | PACK_EXPAND | PACK_FILL | CANCEL_DEFAULT | GRAB_DEFAULT | SHOW_BUTTON | CONNECT_OBJ, 0); gtk_widget_show_all(dialog); } static void efilesel_rename_do(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; char *buf; char *file; char *path; char *new_filename; char *old_filename; CmplState *cmpl_state; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); file = gtk_entry_get_text(GTK_ENTRY(fs->fileop_entry)); cmpl_state = (CmplState *) fs->cmpl_state; path = cmpl_reference_position(cmpl_state); new_filename = g_strconcat(path, "/", file, NULL); old_filename = g_strconcat(path, "/", fs->fileop_file, NULL); if ((rename(old_filename, new_filename)) < 0) { buf = g_strconcat( _("Error renaming file \""), file, "\": ", g_strerror(errno), NULL); do_dialog_error("Error", buf); g_free(buf); } g_free(new_filename); g_free(old_filename); gtk_widget_destroy(fs->fileop_dialog); efilesel_reread_cb(NULL, (gpointer)fs); } static void efilesel_rename_file(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; GtkWidget *label; GtkWidget *dialog; GtkWidget *vbox; char *buf; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); if (fs->fileop_dialog) return; fs->fileop_file = gtk_entry_get_text(GTK_ENTRY(fs->selection_entry)); if (strlen(fs->fileop_file) < 1) return; /* main dialog */ fs->fileop_dialog = dialog = gtk_dialog_new(); gtk_signal_connect(GTK_OBJECT(dialog), "destroy", (GtkSignalFunc) efilesel_fileop_destroy, (gpointer) fs); gtk_window_set_title(GTK_WINDOW(dialog), _("Rename File")); gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); #ifdef GTK_HAVE_FEATURES_1_1_0 /* If file dialog is grabbed, grab option dialog */ /* When option dialog closed, file dialog will be grabbed again */ if (GTK_WINDOW(fs)->modal) gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); #endif vbox = gtk_vbox_new(FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, FALSE, FALSE, 0); buf = g_strconcat(_("Rename file \""), fs->fileop_file, _("\" to:"), NULL); label = gtk_label_new(buf); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); g_free(buf); /* New filename entry */ fs->fileop_entry = gtk_entry_new(); gtk_box_pack_start(GTK_BOX(vbox), fs->fileop_entry, TRUE, TRUE, 5); GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT); gtk_entry_set_text(GTK_ENTRY(fs->fileop_entry), fs->fileop_file); gtk_editable_select_region(GTK_EDITABLE(fs->fileop_entry), 0, strlen(fs->fileop_file)); /* buttons */ (void)misc_button_new_w_label(_("Rename"), NULL, GTK_SIGNAL_FUNC(efilesel_rename_do), fs, GTK_DIALOG(dialog)->action_area, PACK_START | PACK_EXPAND | PACK_FILL | CANCEL_DEFAULT | SHOW_BUTTON, 0); (void)misc_button_new_w_label(_("Cancel"), GNOME_STOCK_BUTTON_CANCEL, GTK_SIGNAL_FUNC(gtk_widget_destroy), dialog, GTK_DIALOG(dialog)->action_area, PACK_START | PACK_EXPAND | PACK_FILL | CANCEL_DEFAULT | GRAB_DEFAULT | CONNECT_OBJ | SHOW_BUTTON, 0); gtk_widget_show_all(dialog); } static int efilesel_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { GtkEfileSel *fs; char *text; g_return_val_if_fail(widget != NULL, FALSE); g_return_val_if_fail(event != NULL, FALSE); if (event->keyval == GDK_Tab) { fs = GTK_EFILESEL(user_data); text = gtk_entry_get_text(GTK_ENTRY(fs->selection_entry)); text = g_strdup(text); efilesel_populate(fs, text, TRUE); g_free(text); gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event"); return TRUE; } return FALSE; } static void efilesel_history_callback(GtkWidget * widget, gpointer data) { GtkEfileSel *fs = data; HistoryCallbackArg *callback_arg; GList *list; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); list = fs->history_list; while (list) { callback_arg = list->data; if (callback_arg->menu_item == widget) { efilesel_populate(fs, callback_arg->directory, FALSE); break; } list = list->next; } } static void efilesel_update_history_menu(GtkEfileSel * fs, char * current_directory) { HistoryCallbackArg *callback_arg; GtkWidget *menu_item; GList *list; char *current_dir; int dir_len; int i; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); g_return_if_fail(current_directory != NULL); list = fs->history_list; if (fs->history_menu) { while (list) { callback_arg = list->data; g_free(callback_arg->directory); g_free(callback_arg); list = list->next; } g_list_free(fs->history_list); fs->history_list = NULL; gtk_widget_destroy(fs->history_menu); } fs->history_menu = gtk_menu_new(); current_dir = g_strdup(current_directory); dir_len = strlen(current_dir); for (i = dir_len; i >= 0; i--) { /* the i == dir_len is to catch the full path for the first * entry. */ if ((current_dir[i] == '/') || (i == dir_len)) { /* another small hack to catch the full path */ if (i != dir_len) current_dir[i + 1] = '\0'; menu_item = gtk_menu_item_new_with_label(current_dir); callback_arg = g_new(HistoryCallbackArg, 1); callback_arg->menu_item = menu_item; /* since the autocompletion gets confused if you don't * supply a trailing '/' on a dir entry, set the full * (current) path to "" which just refreshes the * fsel */ if (dir_len == i) { callback_arg->directory = g_strdup(""); } else { callback_arg->directory = g_strdup(current_dir); } fs->history_list = g_list_append(fs->history_list, callback_arg); gtk_signal_connect(GTK_OBJECT(menu_item), "activate", (GtkSignalFunc) efilesel_history_callback, (gpointer) fs); gtk_menu_append(GTK_MENU(fs->history_menu), menu_item); gtk_widget_show(menu_item); } } gtk_option_menu_set_menu(GTK_OPTION_MENU(fs->history_pulldown), fs->history_menu); g_free(current_dir); } /* * When the filelist clist gets an "unselect_row" signal (that is, an entry was * deselected), this gets called. Here, we get the filename from the clist * which was unselected, then remove the entry from 'fname_list'. Finally, we * update the file selection dialog's entry text. */ static void efilesel_unselect_row_cb(GtkWidget * widget, int row, int column, GdkEventButton * bevent, gpointer user_data) { char *buf; GtkEfileSel *fs; char *filename, *path, *fullpath; g_return_if_fail(GTK_IS_CLIST(widget)); fs = user_data; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); gtk_clist_get_text(GTK_CLIST(fs->file_list), row, 0, &filename); if (filename) { path = cmpl_reference_position((CmplState *)fs->cmpl_state); fullpath = g_strconcat(path, "/", filename, NULL); efilesel_fname_list_remove(fs, fullpath); g_free(fullpath); buf = efilesel_fname_list_construct(fs); gtk_entry_set_text(GTK_ENTRY(fs->selection_entry), buf); g_free(buf); } } /* efilesel_unselect_row_cb */ static void efilesel_select_row_cb(GtkWidget * widget, int row, int column, GdkEventButton * bevent, gpointer user_data) { char *buf; GtkEfileSel *fs; char *filename; g_return_if_fail(GTK_IS_CLIST(widget)); fs = user_data; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); gtk_clist_get_text(GTK_CLIST(fs->file_list), row, 0, &filename); /* * NOTE: this is different from the original GTK File Selection in that * the variable 'filename' does NOT get freed here. Instead, it gets * freed when 'fname_list' gets freed. 'fname_list' gets freed in the * objects destructor. */ if (filename) { efilesel_fname_list_add(fs, filename); if (bevent) { switch (bevent->type) { case GDK_2BUTTON_PRESS: gtk_button_clicked(GTK_BUTTON(fs->ok_button)); break; default: buf = efilesel_fname_list_construct(fs); gtk_entry_set_text( GTK_ENTRY(fs->selection_entry), buf); g_free(buf); break; } } else { buf = efilesel_fname_list_construct(fs); gtk_entry_set_text(GTK_ENTRY(fs->selection_entry), buf); g_free(buf); } } } /* efilesel_select_row_cb */ /* * Scans 'fname_list' and concatenates all filenames into one, newly allocated * buffer (which must be freed by the caller). The filenames are separated by * a single white space. */ static char * efilesel_fname_list_construct(GtkEfileSel *fs) { GSList *fnp; char *buf; int len = 1; for (fnp = fs->fname_list; fnp; fnp = fnp->next) len += (strlen(my_basename((char *)fnp->data)) + 1); buf = g_new(char, len); buf[0] = '\0'; for (fnp = fs->fname_list; fnp; fnp = fnp->next) { strcat(buf, my_basename((char *)fnp->data)); strcat(buf, " "); } buf[strlen(buf) - 1] = '\0'; /* remove trailing space */ return buf; } /* efilesel_fname_list_construct */ static void efilesel_dir_button(GtkWidget * widget, int row, int column, GdkEventButton * bevent, gpointer user_data) { GtkEfileSel *fs = NULL; char *filename, *temp = NULL; g_return_if_fail(GTK_IS_CLIST(widget)); fs = GTK_EFILESEL(user_data); g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); gtk_clist_get_text(GTK_CLIST(fs->dir_list), row, 0, &temp); filename = g_strdup(temp); if (filename) { if (bevent) { switch (bevent->type) { case GDK_2BUTTON_PRESS: efilesel_populate(fs, filename, FALSE); break; default: gtk_entry_set_text( GTK_ENTRY(fs->selection_entry), filename); break; } } else { gtk_entry_set_text(GTK_ENTRY(fs->selection_entry), filename); } g_free(filename); } } static void efilesel_populate(GtkEfileSel *fs, char *rel_path, int try_complete) { CmplState *cmpl_state; PossibleCmpl *poss; char *filename; int row; char *rem_path = rel_path; char *sel_text; char *text[2]; int did_recurse = FALSE; int possible_count = 0; int selection_index = -1; int file_list_width; int dir_list_width; g_return_if_fail(fs != NULL); g_return_if_fail(GTK_IS_EFILESEL(fs)); cmpl_state = (CmplState *) fs->cmpl_state; poss = cmpl_cmpl_matches(rel_path, &rem_path, cmpl_state); if (!efilesel_cmpl_state_okay(cmpl_state)) { /* Something went wrong. */ efilesel_abort(fs); return; } g_assert(cmpl_state->reference_dir); gtk_clist_freeze(GTK_CLIST(fs->dir_list)); gtk_clist_clear(GTK_CLIST(fs->dir_list)); gtk_clist_freeze(GTK_CLIST(fs->file_list)); gtk_clist_clear(GTK_CLIST(fs->file_list)); /* Set the dir_list to include ./ and ../ */ text[1] = NULL; text[0] = "./"; row = gtk_clist_append(GTK_CLIST(fs->dir_list), text); text[0] = "../"; row = gtk_clist_append(GTK_CLIST(fs->dir_list), text); /*reset the max widths of the lists */ dir_list_width = gdk_string_width(fs->dir_list->style->font, "../"); gtk_clist_set_column_width(GTK_CLIST(fs->dir_list), 0, dir_list_width); file_list_width = 1; gtk_clist_set_column_width(GTK_CLIST(fs->file_list), 0,file_list_width); while (poss) { if (cmpl_is_a_cmpl(poss)) { possible_count += 1; filename = cmpl_this_cmpl(poss); text[0] = filename; if (cmpl_is_directory(poss)) { if (strcmp(filename, "./") != 0 && strcmp(filename, "../") != 0) { int width = gdk_string_width(fs->dir_list->style->font, filename); row = gtk_clist_append(GTK_CLIST(fs->dir_list), text); if (width > dir_list_width) { dir_list_width = width; gtk_clist_set_column_width(GTK_CLIST(fs->dir_list), 0, width); } } } else { int width = gdk_string_width(fs->file_list->style->font, filename); row = gtk_clist_append(GTK_CLIST(fs->file_list), text); if (width > file_list_width) { file_list_width = width; gtk_clist_set_column_width(GTK_CLIST(fs->file_list), 0, width); } } gtk_events_flush(); } poss = cmpl_next_cmpl(cmpl_state); } gtk_clist_thaw(GTK_CLIST(fs->dir_list)); gtk_clist_thaw(GTK_CLIST(fs->file_list)); /* File lists are set. */ g_assert(cmpl_state->reference_dir); if (try_complete) { /* * User is trying to complete filenames, so advance the user's * input string to the updated_text, which is the common * leading substring of all possible completions, and if its a * directory attempt attempt completions in it. */ if (cmpl_updated_text(cmpl_state)[0]) { if (cmpl_updated_dir(cmpl_state)) { char *dir_name = g_strdup( cmpl_updated_text(cmpl_state)); did_recurse = TRUE; efilesel_populate(fs, dir_name, TRUE); g_free(dir_name); } else { if (fs->selection_entry) gtk_entry_set_text( GTK_ENTRY(fs->selection_entry), cmpl_updated_text(cmpl_state)); } } else { selection_index = cmpl_last_valid_char(cmpl_state) - (strlen(rel_path) - strlen(rem_path)); if (fs->selection_entry) gtk_entry_set_text( GTK_ENTRY(fs->selection_entry), rem_path); } } else { if (fs->selection_entry) gtk_entry_set_text(GTK_ENTRY(fs->selection_entry), ""); } if (!did_recurse) { if (fs->selection_entry) gtk_entry_set_position(GTK_ENTRY(fs->selection_entry), selection_index); if (fs->selection_entry) { sel_text = g_strconcat(_("Selection: "), cmpl_reference_position(cmpl_state), NULL); gtk_label_set_text(GTK_LABEL(fs->selection_text), sel_text); g_free(sel_text); } if (fs->history_pulldown) { efilesel_update_history_menu( fs, cmpl_reference_position(cmpl_state)); } } } static void efilesel_abort(GtkEfileSel * fs) { char err_buf[256]; sprintf(err_buf, _("Directory unreadable: %s"), efile_cmpl_strerror(cmpl_errno)); /* BEEP gdk_beep(); */ if (fs->selection_entry) gtk_label_set_text(GTK_LABEL(fs->selection_text), err_buf); } /**********************************************************************/ /* External Interface */ /**********************************************************************/ /* The four completion state selectors */ static char * cmpl_updated_text(CmplState * cmpl_state) { return cmpl_state->updated_text; } static int cmpl_updated_dir(CmplState * cmpl_state) { return cmpl_state->re_complete; } static char * cmpl_reference_position(CmplState * cmpl_state) { return cmpl_state->reference_dir->fullname; } static int cmpl_last_valid_char(CmplState * cmpl_state) { return cmpl_state->last_valid_char; } static char * cmpl_cmpl_fullname(char * text, CmplState * cmpl_state) { static char nothing[2] = ""; if (!efilesel_cmpl_state_okay(cmpl_state)) { return nothing; } else if (text[0] == '/') { strcpy(cmpl_state->updated_text, text); } else if (text[0] == '~') { CmplDir *dir; char *slash; dir = open_user_dir(text, cmpl_state); if (!dir) { /* spencer says just return ~something, so * for now just do it. */ strcpy(cmpl_state->updated_text, text); } else { strcpy(cmpl_state->updated_text, dir->fullname); slash = strchr(text, '/'); if (slash) strcat(cmpl_state->updated_text, slash); } } else { strcpy(cmpl_state->updated_text, cmpl_state->reference_dir->fullname); strcat(cmpl_state->updated_text, "/"); strcat(cmpl_state->updated_text, text); } return cmpl_state->updated_text; } /* The three completion selectors */ static char * cmpl_this_cmpl(PossibleCmpl * pc) { return pc->text; } static int cmpl_is_directory(PossibleCmpl * pc) { return pc->is_directory; } static int cmpl_is_a_cmpl(PossibleCmpl * pc) { return pc->is_a_cmpl; } /**********************************************************************/ /* Construction, deletion */ /**********************************************************************/ static CmplState * cmpl_init_state(void) { char getcwd_buf[2 * MAXPATHLEN]; CmplState *new_state; new_state = g_new(CmplState, 1); /* We don't use getcwd() on SUNOS, because, it does a popen("pwd") * and, if that wasn't bad enough, hangs in doing so. */ #if defined(sun) && !defined(__SVR4) if (!getwd(getcwd_buf)) #else if (!getcwd(getcwd_buf, MAXPATHLEN)) #endif { /* Oh joy, we can't get the current directory. Um..., we should * have a root directory, right? Right? (Probably not portable * to non-Unix) */ strcpy(getcwd_buf, "/"); } tryagain: new_state->reference_dir = NULL; new_state->cmpl_dir = NULL; new_state->active_cmpl_dir = NULL; new_state->directory_storage = NULL; new_state->directory_sent_storage = NULL; new_state->last_valid_char = 0; new_state->updated_text = g_new(char, MAXPATHLEN); new_state->updated_text_alloc = MAXPATHLEN; new_state->the_cmpl.text = g_new(char, MAXPATHLEN); new_state->the_cmpl.text_alloc = MAXPATHLEN; new_state->user_dir_name_buffer = NULL; new_state->user_directories = NULL; new_state->reference_dir = efilesel_open_dir(getcwd_buf, new_state); if (!new_state->reference_dir) { /* Directories changing from underneath us, grumble */ strcpy(getcwd_buf, "/"); goto tryagain; } return new_state; } static void cmpl_free_dir_list(GList * dp0) { GList *dp = dp0; while (dp) { free_dir(dp->data); dp = dp->next; } g_list_free(dp0); } static void cmpl_free_dir_sent_list(GList * dp0) { GList *dp = dp0; while (dp) { free_dir_sent(dp->data); dp = dp->next; } g_list_free(dp0); } static void cmpl_free_state(CmplState * cmpl_state) { cmpl_free_dir_list(cmpl_state->directory_storage); cmpl_free_dir_sent_list(cmpl_state->directory_sent_storage); if (cmpl_state->user_dir_name_buffer) g_free(cmpl_state->user_dir_name_buffer); if (cmpl_state->user_directories) g_free(cmpl_state->user_directories); if (cmpl_state->the_cmpl.text) g_free(cmpl_state->the_cmpl.text); if (cmpl_state->updated_text) g_free(cmpl_state->updated_text); g_free(cmpl_state); } static void free_dir(CmplDir * dir) { g_free(dir->fullname); g_free(dir); } static void free_dir_sent(CmplDirSent * sent) { g_free(sent->name_buffer); g_free(sent->entries); g_free(sent); } static void prune_memory_usage(CmplState * cmpl_state) { GList *cdsl = cmpl_state->directory_sent_storage; GList *cdl = cmpl_state->directory_storage; GList *cdl0 = cdl; int len = 0; for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1) cdsl = cdsl->next; if (cdsl) { cmpl_free_dir_sent_list(cdsl->next); cdsl->next = NULL; } cmpl_state->directory_storage = NULL; while (cdl) { if (cdl->data == cmpl_state->reference_dir) cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data); else free_dir(cdl->data); cdl = cdl->next; } g_list_free(cdl0); } /**********************************************************************/ /* The main entrances. */ /**********************************************************************/ static PossibleCmpl * cmpl_cmpl_matches(char *text_to_complete, char **remaining_text, CmplState *cmpl_state) { char *first_slash; PossibleCmpl *poss; prune_memory_usage(cmpl_state); g_assert(text_to_complete != NULL); cmpl_state->user_cmpl_index = -1; cmpl_state->last_cmpl_text = text_to_complete; cmpl_state->the_cmpl.text[0] = 0; cmpl_state->last_valid_char = 0; cmpl_state->updated_text_len = -1; cmpl_state->updated_text[0] = 0; cmpl_state->re_complete = FALSE; first_slash = strchr(text_to_complete, '/'); if (text_to_complete[0] == '~' && !first_slash) { /* Text starts with ~ and there is no slash, show all the * home directory completions. */ poss = efilesel_attempt_homedir_cmpl(text_to_complete, cmpl_state); efilesel_update_cmpl(poss, cmpl_state); return poss; } cmpl_state->reference_dir = open_ref_dir(text_to_complete, remaining_text, cmpl_state); if (!cmpl_state->reference_dir) return NULL; cmpl_state->cmpl_dir = efilesel_find_cmpl_dir(*remaining_text, remaining_text, cmpl_state); cmpl_state->last_valid_char = *remaining_text - text_to_complete; if (!cmpl_state->cmpl_dir) return NULL; cmpl_state->cmpl_dir->cmpl_index = -1; cmpl_state->cmpl_dir->cmpl_parent = NULL; cmpl_state->cmpl_dir->cmpl_text = *remaining_text; cmpl_state->active_cmpl_dir = cmpl_state->cmpl_dir; cmpl_state->reference_dir = cmpl_state->cmpl_dir; poss = efilesel_attempt_file_cmpl(cmpl_state); efilesel_update_cmpl(poss, cmpl_state); return poss; } static PossibleCmpl * cmpl_next_cmpl(CmplState * cmpl_state) { PossibleCmpl *poss = NULL; cmpl_state->the_cmpl.text[0] = 0; if (cmpl_state->user_cmpl_index >= 0) poss = efilesel_attempt_homedir_cmpl( cmpl_state->last_cmpl_text, cmpl_state); else poss = efilesel_attempt_file_cmpl(cmpl_state); efilesel_update_cmpl(poss, cmpl_state); return poss; } /**********************************************************************/ /* Directory Operations */ /**********************************************************************/ /* Open the directory where completion will begin from, if possible. */ static CmplDir * open_ref_dir(char *text_to_complete, char **remaining_text, CmplState *cmpl_state) { char *first_slash; CmplDir *new_dir; first_slash = strchr(text_to_complete, '/'); if (text_to_complete[0] == '/' || !cmpl_state->reference_dir) { new_dir = efilesel_open_dir("/", cmpl_state); if (new_dir) *remaining_text = text_to_complete + 1; } else if (text_to_complete[0] == '~') { new_dir = open_user_dir(text_to_complete, cmpl_state); if (new_dir) { if (first_slash) *remaining_text = first_slash + 1; else *remaining_text = text_to_complete + strlen(text_to_complete); } else { return NULL; } } else { *remaining_text = text_to_complete; new_dir = efilesel_open_dir(cmpl_state->reference_dir->fullname, cmpl_state); } if (new_dir) { new_dir->cmpl_index = -1; new_dir->cmpl_parent = NULL; } return new_dir; } /* open a directory by user name */ static CmplDir * open_user_dir(char *text_to_complete, CmplState *cmpl_state) { char *first_slash; int cmp_len; g_assert(text_to_complete && text_to_complete[0] == '~'); first_slash = strchr(text_to_complete, '/'); if (first_slash) cmp_len = first_slash - text_to_complete - 1; else cmp_len = strlen(text_to_complete + 1); if (!cmp_len) { /* ~/ */ #ifdef GTK_HAVE_FEATURES_1_1_0 char *homedir = g_get_home_dir(); #else char *homedir = getenv("HOME"); #endif if (homedir) return efilesel_open_dir(homedir, cmpl_state); else return NULL; } else { /* ~user/ */ char *copy = g_new(char, cmp_len + 1); struct passwd *pwd; strncpy(copy, text_to_complete + 1, cmp_len); copy[cmp_len] = 0; pwd = getpwnam(copy); g_free(copy); if (!pwd) { cmpl_errno = errno; return NULL; } return efilesel_open_dir(pwd->pw_dir, cmpl_state); } } /* open a directory relative the the current relative directory */ static CmplDir * open_relative_dir(char *dir_name, CmplDir *dir, CmplState *cmpl_state) { char path_buf[2 * MAXPATHLEN]; if (dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN) { cmpl_errno = CMPL_ERRNO_TOO_LONG; return NULL; } strcpy(path_buf, dir->fullname); if (dir->fullname_len > 1) { path_buf[dir->fullname_len] = '/'; strcpy(path_buf + dir->fullname_len + 1, dir_name); } else { strcpy(path_buf + dir->fullname_len, dir_name); } return efilesel_open_dir(path_buf, cmpl_state); } /* after the cache lookup fails, really open a new directory */ static CmplDirSent * open_new_dir(char * dir_name, struct stat *sbuf, gboolean stat_subdirs) { CmplDirSent *sent; DIR *directory; char *buffer_ptr; struct dirent *dirent_ptr; int buffer_size = 0; int entry_count = 0; int i; struct stat ent_sbuf; char path_buf[MAXPATHLEN * 2]; int path_buf_len; sent = g_new(CmplDirSent, 1); sent->mtime = sbuf->st_mtime; sent->inode = sbuf->st_ino; sent->device = sbuf->st_dev; path_buf_len = strlen(dir_name); if (path_buf_len > MAXPATHLEN) { cmpl_errno = CMPL_ERRNO_TOO_LONG; return NULL; } strcpy(path_buf, dir_name); directory = opendir(dir_name); if (!directory) { cmpl_errno = errno; return NULL; } while ((dirent_ptr = readdir(directory)) != NULL) { int entry_len = strlen(dirent_ptr->d_name); buffer_size += entry_len + 1; entry_count += 1; if (path_buf_len + entry_len + 2 >= MAXPATHLEN) { cmpl_errno = CMPL_ERRNO_TOO_LONG; closedir(directory); return NULL; } } sent->name_buffer = g_new(char, buffer_size); sent->entries = g_new(CmplDirEntry, entry_count); sent->entry_count = entry_count; buffer_ptr = sent->name_buffer; rewinddir(directory); for (i = 0; i < entry_count; i += 1) { dirent_ptr = readdir(directory); if (!dirent_ptr) { cmpl_errno = errno; closedir(directory); return NULL; } strcpy(buffer_ptr, dirent_ptr->d_name); sent->entries[i].entry_name = buffer_ptr; buffer_ptr += strlen(dirent_ptr->d_name); *buffer_ptr = 0; buffer_ptr += 1; path_buf[path_buf_len] = '/'; strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name); if (stat_subdirs) { if (stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode)) sent->entries[i].is_dir = 1; else /* stat may fail, and we don't mind, since it * could be a dangling symlink. */ sent->entries[i].is_dir = 0; } else sent->entries[i].is_dir = 1; } qsort(sent->entries, sent->entry_count, sizeof(CmplDirEntry), efilesel_compare_cmpl_dir); closedir(directory); return sent; } static gboolean efilesel_check_dir(char *dir_name, struct stat *result, gboolean *stat_subdirs) { /* A list of directories that we know only contain other directories. * Trying to stat every file in these directories would be very * expensive. */ static struct { char *name; gboolean present; struct stat statbuf; } no_stat_dirs[] = { { "/afs", FALSE, { 0 } }, { "/net", FALSE, { 0 } } }; static const int n_no_stat_dirs = sizeof(no_stat_dirs) / sizeof(no_stat_dirs[0]); static gboolean initialized = FALSE; int i; if (!initialized) { initialized = TRUE; for (i = 0; i < n_no_stat_dirs; i++) { if (stat(no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0) no_stat_dirs[i].present = TRUE; } } if (stat(dir_name, result) < 0) { cmpl_errno = errno; return FALSE; } *stat_subdirs = TRUE; for (i = 0; i < n_no_stat_dirs; i++) { if (no_stat_dirs[i].present && (no_stat_dirs[i].statbuf.st_dev == result->st_dev) && (no_stat_dirs[i].statbuf.st_ino == result->st_ino)) { *stat_subdirs = FALSE; break; } } return TRUE; } /* open a directory by absolute pathname */ static CmplDir * efilesel_open_dir(char * dir_name, CmplState * cmpl_state) { struct stat sbuf; gboolean stat_subdirs; CmplDirSent *sent; GList *cdsl; if (!efilesel_check_dir(dir_name, &sbuf, &stat_subdirs)) return NULL; cdsl = cmpl_state->directory_sent_storage; while (cdsl) { sent = cdsl->data; if (sent->inode == sbuf.st_ino && sent->mtime == sbuf.st_mtime && sent->device == sbuf.st_dev) return efilesel_attach_dir(sent, dir_name, cmpl_state); cdsl = cdsl->next; } sent = open_new_dir(dir_name, &sbuf, stat_subdirs); if (sent) { cmpl_state->directory_sent_storage = g_list_prepend(cmpl_state->directory_sent_storage, sent); return efilesel_attach_dir(sent, dir_name, cmpl_state); } return NULL; } static CmplDir * efilesel_attach_dir(CmplDirSent *sent, char *dir_name, CmplState *cmpl_state) { CmplDir *new_dir; new_dir = g_new(CmplDir, 1); cmpl_state->directory_storage = g_list_prepend(cmpl_state->directory_storage, new_dir); new_dir->sent = sent; new_dir->fullname = g_strdup(dir_name); new_dir->fullname_len = strlen(dir_name); return new_dir; } static int efilesel_correct_dir_fullname(CmplDir * cmpl_dir) { int length = strlen(cmpl_dir->fullname); struct stat sbuf; if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0) { if (length == 2) { strcpy(cmpl_dir->fullname, "/"); cmpl_dir->fullname_len = 1; return TRUE; } else { cmpl_dir->fullname[length - 2] = 0; } } else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0) cmpl_dir->fullname[length - 2] = 0; else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0) { if (length == 3) { strcpy(cmpl_dir->fullname, "/"); cmpl_dir->fullname_len = 1; return TRUE; } if (stat(cmpl_dir->fullname, &sbuf) < 0) { cmpl_errno = errno; return FALSE; } cmpl_dir->fullname[length - 2] = 0; if (!efilesel_correct_parent(cmpl_dir, &sbuf)) return FALSE; } else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0) { if (length == 4) { strcpy(cmpl_dir->fullname, "/"); cmpl_dir->fullname_len = 1; return TRUE; } if (stat(cmpl_dir->fullname, &sbuf) < 0) { cmpl_errno = errno; return FALSE; } cmpl_dir->fullname[length - 3] = 0; if (!efilesel_correct_parent(cmpl_dir, &sbuf)) return FALSE; } cmpl_dir->fullname_len = strlen(cmpl_dir->fullname); return TRUE; } static int efilesel_correct_parent(CmplDir * cmpl_dir, struct stat *sbuf) { struct stat parbuf; char *last_slash; char *new_name; char c = 0; last_slash = strrchr(cmpl_dir->fullname, '/'); g_assert(last_slash); if (last_slash != cmpl_dir->fullname) { /* last_slash[0] = 0; */ } else { c = last_slash[1]; last_slash[1] = 0; } if (stat(cmpl_dir->fullname, &parbuf) < 0) { cmpl_errno = errno; return FALSE; } if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev) /* it wasn't a link */ return TRUE; if (c) last_slash[1] = c; /* else last_slash[0] = '/'; */ /* it was a link, have to figure it out the hard way */ new_name = efilesel_find_parent_dir_fullname(cmpl_dir->fullname); if (!new_name) return FALSE; g_free(cmpl_dir->fullname); cmpl_dir->fullname = new_name; return TRUE; } static char * efilesel_find_parent_dir_fullname(char * dirname) { char buffer[MAXPATHLEN]; char buffer2[MAXPATHLEN]; #if defined(sun) && !defined(__SVR4) if (!getwd(buffer)) #else if (!getcwd(buffer, MAXPATHLEN)) #endif { cmpl_errno = errno; return NULL; } if (chdir(dirname) != 0 || chdir("..") != 0) { cmpl_errno = errno; return NULL; } #if defined(sun) && !defined(__SVR4) if (!getwd(buffer2)) #else if (!getcwd(buffer2, MAXPATHLEN)) #endif { chdir(buffer); cmpl_errno = errno; return NULL; } if (chdir(buffer) != 0) { cmpl_errno = errno; return NULL; } return g_strdup(buffer2); } /**********************************************************************/ /* Cmpl Operations */ /**********************************************************************/ static PossibleCmpl * efilesel_attempt_homedir_cmpl(char *text_to_complete, CmplState *cmpl_state) { int index, length; if (!cmpl_state->user_dir_name_buffer && !efilesel_get_pwdb(cmpl_state)) return NULL; length = strlen(text_to_complete) - 1; cmpl_state->user_cmpl_index += 1; while (cmpl_state->user_cmpl_index < cmpl_state->user_directories_len) { index = efilesel_first_diff_index(text_to_complete + 1, cmpl_state->user_directories [cmpl_state->user_cmpl_index].login); switch (index) { case PATTERN_MATCH: break; default: if (cmpl_state->last_valid_char < (index + 1)) cmpl_state->last_valid_char = index + 1; cmpl_state->user_cmpl_index += 1; continue; } cmpl_state->the_cmpl.is_a_cmpl = 1; cmpl_state->the_cmpl.is_directory = 1; efilesel_append_cmpl_text("~", cmpl_state); efilesel_append_cmpl_text(cmpl_state-> user_directories[cmpl_state->user_cmpl_index].login, cmpl_state); return efilesel_append_cmpl_text("/", cmpl_state); } if (text_to_complete[1] || cmpl_state->user_cmpl_index > cmpl_state->user_directories_len) { cmpl_state->user_cmpl_index = -1; return NULL; } else { cmpl_state->user_cmpl_index += 1; cmpl_state->the_cmpl.is_a_cmpl = 1; cmpl_state->the_cmpl.is_directory = 1; return efilesel_append_cmpl_text("~/", cmpl_state); } } /* returns the index (>= 0) of the first differing character, * PATTERN_MATCH if the completion matches */ static int efilesel_first_diff_index(char * pat, char * text) { int diff = 0; while (*pat && *text && *text == *pat) { pat += 1; text += 1; diff += 1; } if (*pat) return diff; return PATTERN_MATCH; } static PossibleCmpl * efilesel_append_cmpl_text(char * text, CmplState * cmpl_state) { int len, i = 1; if (!cmpl_state->the_cmpl.text) return NULL; len = strlen(text) + strlen(cmpl_state->the_cmpl.text) + 1; if (cmpl_state->the_cmpl.text_alloc > len) { strcat(cmpl_state->the_cmpl.text, text); return &cmpl_state->the_cmpl; } while (i < len) { i <<= 1; } cmpl_state->the_cmpl.text_alloc = i; cmpl_state->the_cmpl.text = (char *)g_realloc(cmpl_state->the_cmpl.text, i); if (!cmpl_state->the_cmpl.text) return NULL; else { strcat(cmpl_state->the_cmpl.text, text); return &cmpl_state->the_cmpl; } } static CmplDir * efilesel_find_cmpl_dir(char *text_to_complete, char **remaining_text, CmplState *cmpl_state) { char *first_slash = strchr(text_to_complete, '/'); CmplDir *dir = cmpl_state->reference_dir; CmplDir *next; *remaining_text = text_to_complete; while (first_slash) { int len = first_slash - *remaining_text; int found = 0; char *found_name = NULL; /* Quiet gcc */ int i; char *pat_buf = g_new(char, len + 1); strncpy(pat_buf, *remaining_text, len); pat_buf[len] = 0; for (i = 0; i < dir->sent->entry_count; i += 1) { if (dir->sent->entries[i].is_dir && fnmatch(pat_buf, dir->sent->entries[i].entry_name, FNMATCH_FLAGS) != FNM_NOMATCH) { if (found) { g_free(pat_buf); return dir; } else { found = 1; found_name = dir->sent->entries[i].entry_name; } } } if (!found) { /* Perhaps we are trying to open an automount directory * */ found_name = pat_buf; } next = open_relative_dir(found_name, dir, cmpl_state); if (!next) { g_free(pat_buf); return NULL; } next->cmpl_parent = dir; dir = next; if (!efilesel_correct_dir_fullname(dir)) { g_free(pat_buf); return NULL; } *remaining_text = first_slash + 1; first_slash = strchr(*remaining_text, '/'); g_free(pat_buf); } return dir; } static void efilesel_update_cmpl(PossibleCmpl * poss, CmplState * cmpl_state) { int cmpl_len; if (!poss || !cmpl_is_a_cmpl(poss)) return; cmpl_len = strlen(cmpl_this_cmpl(poss)); if (cmpl_state->updated_text_alloc < cmpl_len + 1) { cmpl_state->updated_text = (char *) g_realloc(cmpl_state->updated_text, cmpl_state->updated_text_alloc); cmpl_state->updated_text_alloc = 2 * cmpl_len; } if (cmpl_state->updated_text_len < 0) { strcpy(cmpl_state->updated_text, cmpl_this_cmpl(poss)); cmpl_state->updated_text_len = cmpl_len; cmpl_state->re_complete = cmpl_is_directory(poss); } else if (cmpl_state->updated_text_len == 0) { cmpl_state->re_complete = FALSE; } else { int first_diff = efilesel_first_diff_index( cmpl_state->updated_text, cmpl_this_cmpl(poss)); cmpl_state->re_complete = FALSE; if (first_diff == PATTERN_MATCH) return; if (first_diff > cmpl_state->updated_text_len) strcpy(cmpl_state->updated_text, cmpl_this_cmpl(poss)); cmpl_state->updated_text_len = first_diff; cmpl_state->updated_text[first_diff] = 0; } } static PossibleCmpl * efilesel_attempt_file_cmpl(CmplState * cmpl_state) { char *pat_buf, *first_slash; CmplDir *dir = cmpl_state->active_cmpl_dir; dir->cmpl_index += 1; if (dir->cmpl_index == dir->sent->entry_count) { if (dir->cmpl_parent == NULL) { cmpl_state->active_cmpl_dir = NULL; return NULL; } else { cmpl_state->active_cmpl_dir = dir->cmpl_parent; return efilesel_attempt_file_cmpl(cmpl_state); } } g_assert(dir->cmpl_text); first_slash = strchr(dir->cmpl_text, '/'); if (first_slash) { int len = first_slash - dir->cmpl_text; pat_buf = g_new(char, len + 1); strncpy(pat_buf, dir->cmpl_text, len); pat_buf[len] = 0; } else { int len = strlen(dir->cmpl_text); pat_buf = g_new(char, len + 2); strcpy(pat_buf, dir->cmpl_text); strcpy(pat_buf + len, "*"); } if (first_slash) { if (dir->sent->entries[dir->cmpl_index].is_dir) { if (fnmatch( pat_buf, dir->sent->entries[dir->cmpl_index].entry_name, FNMATCH_FLAGS) != FNM_NOMATCH) { CmplDir *new_dir; new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name, dir, cmpl_state); if (!new_dir) { g_free(pat_buf); return NULL; } new_dir->cmpl_parent = dir; new_dir->cmpl_index = -1; new_dir->cmpl_text = first_slash + 1; cmpl_state->active_cmpl_dir = new_dir; g_free(pat_buf); return efilesel_attempt_file_cmpl(cmpl_state); } else { g_free(pat_buf); return efilesel_attempt_file_cmpl(cmpl_state); } } else { g_free(pat_buf); return efilesel_attempt_file_cmpl(cmpl_state); } } else { if (dir->cmpl_parent != NULL) { efilesel_append_cmpl_text(dir->fullname + strlen(cmpl_state->cmpl_dir->fullname) + 1, cmpl_state); efilesel_append_cmpl_text("/", cmpl_state); } efilesel_append_cmpl_text( dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state); cmpl_state->the_cmpl.is_a_cmpl = (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name, FNMATCH_FLAGS) != FNM_NOMATCH); cmpl_state->the_cmpl.is_directory = dir->sent->entries[dir->cmpl_index].is_dir; if (dir->sent->entries[dir->cmpl_index].is_dir) efilesel_append_cmpl_text("/", cmpl_state); g_free(pat_buf); return &cmpl_state->the_cmpl; } } static int efilesel_get_pwdb(CmplState * cmpl_state) { struct passwd *pwd_ptr; char *buf_ptr; int len = 0, i, count = 0; if (cmpl_state->user_dir_name_buffer) return TRUE; setpwent(); while ((pwd_ptr = getpwent()) != NULL) { len += strlen(pwd_ptr->pw_name); len += strlen(pwd_ptr->pw_dir); len += 2; count += 1; } setpwent(); cmpl_state->user_dir_name_buffer = g_new(char, len); cmpl_state->user_directories = g_new(CmplUserDir, count); cmpl_state->user_directories_len = count; buf_ptr = cmpl_state->user_dir_name_buffer; for (i = 0; i < count; i += 1) { pwd_ptr = getpwent(); if (!pwd_ptr) { cmpl_errno = errno; goto error; } strcpy(buf_ptr, pwd_ptr->pw_name); cmpl_state->user_directories[i].login = buf_ptr; buf_ptr += strlen(buf_ptr); buf_ptr += 1; strcpy(buf_ptr, pwd_ptr->pw_dir); cmpl_state->user_directories[i].homedir = buf_ptr; buf_ptr += strlen(buf_ptr); buf_ptr += 1; } qsort(cmpl_state->user_directories, cmpl_state->user_directories_len, sizeof(CmplUserDir), efilesel_compare_user_dir); endpwent(); return TRUE; error: if (cmpl_state->user_dir_name_buffer) g_free(cmpl_state->user_dir_name_buffer); if (cmpl_state->user_directories) g_free(cmpl_state->user_directories); cmpl_state->user_dir_name_buffer = NULL; cmpl_state->user_directories = NULL; return FALSE; } static int efilesel_compare_user_dir(const void *a, const void *b) { return strcmp((((CmplUserDir *) a))->login, (((CmplUserDir *) b))->login); } static int efilesel_compare_cmpl_dir(const void *a, const void *b) { return strcmp((((CmplDirEntry *) a))->entry_name, (((CmplDirEntry *) b))->entry_name); } static int efilesel_cmpl_state_okay(CmplState * cmpl_state) { return cmpl_state && cmpl_state->reference_dir; } static char * efile_cmpl_strerror(int err) { if (err == CMPL_ERRNO_TOO_LONG) return "Name too long"; else return g_strerror(err); } #if 0 static void efilesel_file_info_cb(GtkWidget *wgt, gpointer cbdata) { GtkEfileSel *fs = (GtkEfileSel *)cbdata; if (GTK_WIDGET_VISIBLE(fs->fileinfo)) { gtk_widget_hide(fs->fileinfo); } else { if (!fs->fname_list || !fs->fname_list->data) { efilesel_details_update(fs, NULL); } else { GSList *last = g_slist_last(fs->fname_list); efilesel_details_update(fs, (char *)last->data); } gtk_widget_show_all(fs->fileinfo); } } /* efilesel_file_info_cb */ #endif /* * callback for the "Reread Dir" button. */ static void efilesel_reread_cb(GtkWidget *wgt, gpointer cbdata) { GtkEfileSel *fs = (GtkEfileSel *)cbdata; efilesel_reset_fname_list(NULL, cbdata); efilesel_populate(fs, "", FALSE); } /* efilesel_reread_cb */ /* * Appends a new filename to the fname_list. Skips duplicates. */ static void efilesel_fname_list_add(GtkEfileSel *fs, char *fname) { GSList *fnp; char *path, *fullpath; if (!fname || fname == "") /* don't add crap */ return; path = cmpl_reference_position((CmplState *)fs->cmpl_state); fullpath = g_strconcat(path, "/", fname, NULL); for (fnp = fs->fname_list; fnp; fnp = fnp->next) { if (strcmp(fullpath, (char *)fnp->data) == 0) { /* duplicate */ g_free(fullpath); return; } } fs->fname_list = g_slist_append(fs->fname_list, (gpointer)g_strdup(fullpath)); efilesel_details_update(fs, fullpath); g_free(fullpath); } /* efilesel_fname_list_add */ /* * removes filename from the fname_list linked list. note that 'fname' must * be a full pathname, since all filenames stored in 'fname_list' are full * pathnames. */ static void efilesel_fname_list_remove(GtkEfileSel *fs, const char *fname) { GSList *fnp; for (fnp = fs->fname_list; fnp; fnp = fnp->next) { if (strcmp(fname, (char *)fnp->data) == 0) break; } if (fnp) { fs->fname_list = g_slist_remove_link(fs->fname_list, fnp); g_free(fnp->data); if (!fs->fname_list || !fs->fname_list->data) { efilesel_details_update(fs, NULL); } else { fnp = g_slist_last(fs->fname_list); efilesel_details_update(fs, (char *)fnp->data); } } else { g_warning("efilesel_unselect_row_cb: '%s' was not found in " "fname_list", fname); } } /* efilesel_fname_list_remove */ /* * destroys 'fname_list' */ static void efilesel_fname_list_destroy(GtkEfileSel *fsel) { GSList *fnp; if (fsel->fname_list) { for (fnp = fsel->fname_list; fnp; fnp = fnp->next) g_free((char *)fnp->data); g_slist_free(fsel->fname_list); fsel->fname_list = NULL; } } /* efilesel_fname_list_destroy */ #ifndef GTK_HAVE_FEATURES_1_1_0 static void gtk_clist_unselect_all(GtkCList *clist) { GList *list; gint i; g_return_if_fail(clist != NULL); g_return_if_fail(GTK_IS_CLIST(clist)); if (gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB(clist)) return; #if 0 switch (clist->selection_mode) { case GTK_SELECTION_BROWSE: if (clist->focus_row >= 0) { gtk_signal_emit(GTK_OBJECT(clist), clist_signals[SELECT_ROW], clist->focus_row, -1, NULL); return; } break; case GTK_SELECTION_EXTENDED: g_list_free(clist->undo_selection); g_list_free(clist->undo_unselection); clist->undo_selection = NULL; clist->undo_unselection = NULL; clist->anchor = -1; clist->drag_pos = -1; clist->undo_anchor = clist->focus_row; break; default: break; } #endif list = clist->selection; while (list) { i = GPOINTER_TO_INT(list->data); list = list->next; gtk_signal_emit_by_name(GTK_OBJECT(clist), "unselect_row", i, -1, NULL); } } /* gtk_clist_unselect_all */ #endif /* * 'fullpath' must be full, because we need to stat() the file */ static void efilesel_details_update(GtkEfileSel *fs, char *fullpath) { struct stat sb; char *str; char buf[32]; struct passwd *pwp; if (fullpath) { gtk_label_set_text(GTK_LABEL(fs->fname), my_basename(fullpath)); gtk_misc_set_alignment(GTK_MISC(fs->fname), 0, 0.5); if (stat((const char *)fullpath, &sb) == 0) { str = ctime(&(sb.st_mtime)); if (str) { if (str[strlen(str) - 1] == '\n') str[strlen(str) - 1] = '\0'; gtk_label_set_text(GTK_LABEL(fs->mtime), str); } else { gtk_label_set_text( GTK_LABEL(fs->mtime), ""); } gtk_misc_set_alignment(GTK_MISC(fs->mtime), 0, 0.5); g_snprintf(buf, 32, "%ld bytes", (long)sb.st_size); gtk_label_set_text(GTK_LABEL(fs->fsize), buf); gtk_misc_set_alignment(GTK_MISC(fs->fsize), 0, 0.5); pwp = getpwuid(sb.st_uid); if (pwp) { str = g_new(char, strlen(pwp->pw_name) + 16); sprintf(str, "%s (uid %ld)", pwp->pw_name, (long)sb.st_uid); } else { str = g_new(char, 32); sprintf(str, " (uid %ld)", (long)sb.st_uid); } gtk_label_set_text(GTK_LABEL(fs->owner), str); gtk_misc_set_alignment(GTK_MISC(fs->owner), 0, 0.5); g_free(str); /* note: pwp doesn't need to be freed? */ } else { g_snprintf(buf, 32, "Error (%d) in stat()", errno); gtk_label_set_text(GTK_LABEL(fs->mtime), buf); gtk_misc_set_alignment(GTK_MISC(fs->mtime), 0, 0.5); gtk_label_set_text(GTK_LABEL(fs->fsize), buf); gtk_misc_set_alignment(GTK_MISC(fs->fsize), 0, 0.5); gtk_label_set_text(GTK_LABEL(fs->owner), buf); gtk_misc_set_alignment(GTK_MISC(fs->owner), 0, 0.5); } } else { gtk_label_set_text(GTK_LABEL(fs->fname), ""); gtk_label_set_text(GTK_LABEL(fs->mtime), ""); gtk_label_set_text(GTK_LABEL(fs->fsize), ""); gtk_label_set_text(GTK_LABEL(fs->owner), ""); } } /* efilesel_details_update */ /* * clears the filename entry text, unselects any selects files, and clears out * the 'fname_list' */ static void efilesel_reset_fname_list(GtkWidget *wgt, gpointer cbdata) { GtkEfileSel *fs = (GtkEfileSel *)cbdata; gtk_entry_set_text(GTK_ENTRY(fs->selection_entry), ""); gtk_label_set_text(GTK_LABEL(fs->fname), ""); gtk_label_set_text(GTK_LABEL(fs->mtime), ""); gtk_label_set_text(GTK_LABEL(fs->fsize), ""); gtk_label_set_text(GTK_LABEL(fs->owner), ""); gtk_clist_unselect_all(GTK_CLIST(fs->file_list)); efilesel_fname_list_destroy(fs); } /* efilesel_reset_fname_list */ void gtk_efilesel_set_mode(GtkEfileSel *fsel, GtkSelectionMode mode) { g_return_if_fail(fsel != NULL); g_return_if_fail(GTK_IS_EFILESEL(fsel)); g_return_if_fail(mode == GTK_SELECTION_SINGLE || mode == GTK_SELECTION_MULTIPLE); gtk_clist_set_selection_mode(GTK_CLIST(fsel->file_list), mode); } /* gtk_efilesel_set_mode */ void gtk_efilesel_show_file_details(GtkEfileSel *fsel) { g_return_if_fail(fsel != NULL); g_return_if_fail(GTK_IS_EFILESEL(fsel)); g_return_if_fail(fsel->fileinfo != NULL); gtk_widget_show_all(fsel->fileinfo); gtk_widget_queue_resize(GTK_WIDGET(fsel)); } /* gtk_efilesel_show_file_details */ void gtk_efilesel_hide_file_details(GtkEfileSel *fsel) { g_return_if_fail(fsel != NULL); g_return_if_fail(GTK_IS_EFILESEL(fsel)); g_return_if_fail(fsel->fileinfo != NULL); gtk_widget_hide(fsel->fileinfo); gtk_widget_queue_resize(GTK_WIDGET(fsel)); } /* gtk_efilesel_hide_file_details */ #endif /* USE_ENHANCED_FSEL */