X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkfilesel.c;h=ad0516c1096b709e3fbc4254e918b0dae0e76aca;hb=fa966c6aa755e27c6d470402bf06e86cc5d2fdc8;hp=d2e38ac5deba0407e6a8e8510c5c785cc2c30860;hpb=33da844e70731005b5bb3670f81ca59a832b2910;p=~andy%2Fgtk diff --git a/gtk/gtkfilesel.c b/gtk/gtkfilesel.c index d2e38ac5d..ad0516c10 100644 --- a/gtk/gtkfilesel.c +++ b/gtk/gtkfilesel.c @@ -2,33 +2,29 @@ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public + * modify it under the terms of the GNU Lesser 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. + * Lesser General Public License for more details. * - * You should have received a copy of the GNU Library General Public + * You should have received a copy of the GNU Lesser 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 + * Modified by the GTK+ Team and others 1997-2000. 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/. */ -#include "config.h" - -#include /* To get stat->_stat redefinition - * for mingw32 - */ +#include #include #include @@ -36,9 +32,6 @@ #ifdef HAVE_SYS_PARAM_H #include #endif -#ifdef HAVE_DIRENT_H -#include -#endif #include #ifdef HAVE_UNISTD_H #include @@ -49,31 +42,49 @@ #include #endif -#include "fnmatch.h" +#include /* Include early to get G_OS_WIN32 etc */ + +#if defined(G_PLATFORM_WIN32) +#include +#define STRICT +#include +#undef STRICT +#endif /* G_PLATFORM_WIN32 */ +#ifdef G_OS_WIN32 +#include /* For gethostname */ +#endif #include "gdk/gdkkeysyms.h" +#include "gtkalias.h" #include "gtkbutton.h" +#include "gtkcellrenderertext.h" #include "gtkentry.h" #include "gtkfilesel.h" #include "gtkhbox.h" #include "gtkhbbox.h" +#include "gtkintl.h" #include "gtklabel.h" -#include "gtklist.h" -#include "gtklistitem.h" +#include "gtkliststore.h" #include "gtkmain.h" +#include "gtkprivate.h" #include "gtkscrolledwindow.h" -#include "gtksignal.h" +#include "gtkstock.h" +#include "gtktreeselection.h" +#include "gtktreeview.h" #include "gtkvbox.h" #include "gtkmenu.h" #include "gtkmenuitem.h" -#include "gtkoptionmenu.h" -#include "gtkclist.h" #include "gtkdialog.h" -#include "gtkintl.h" +#include "gtkmessagedialog.h" +#include "gtkdnd.h" +#include "gtkeventbox.h" -#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN) -#define STRICT -#include +#undef GTK_DISABLE_DEPRECATED +#include "gtkoptionmenu.h" +#define GTK_DISABLE_DEPRECATED + +#define WANT_HPANED 1 +#include "gtkhpaned.h" #ifdef G_OS_WIN32 #include @@ -82,16 +93,29 @@ #ifndef S_ISDIR #define S_ISDIR(mode) ((mode)&_S_IFDIR) #endif - #endif /* G_OS_WIN32 */ -#endif /* G_OS_WIN32 || G_WITH_CYGWIN */ +#ifdef G_WITH_CYGWIN +#include /* For cygwin_conv_to_posix_path */ +#endif #define DIR_LIST_WIDTH 180 #define DIR_LIST_HEIGHT 180 #define FILE_LIST_WIDTH 180 #define FILE_LIST_HEIGHT 180 +/* The Hurd doesn't define either PATH_MAX or MAXPATHLEN, so we put this + * in here, since the rest of the code in the file does require some + * fixed maximum. + */ +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# else +# define MAXPATHLEN 2048 +# endif +#endif + /* I've put this here so it doesn't get confused with the * file completion interface */ typedef struct _HistoryCallbackArg HistoryCallbackArg; @@ -120,11 +144,8 @@ typedef struct _PossibleCompletion PossibleCompletion; * match by 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) +#define CMPL_ERRNO_DID_NOT_CONVERT ((1<<16)-2) /* This structure contains all the useful information about a directory * for the purposes of filename completion. These structures are cached @@ -132,13 +153,13 @@ typedef struct _PossibleCompletion PossibleCompletion; */ struct _CompletionDirSent { +#ifndef G_PLATFORM_WIN32 ino_t inode; time_t mtime; dev_t device; +#endif gint entry_count; - gchar *name_buffer; /* memory segment containing names of all entries */ - struct _CompletionDirEntry *entries; }; @@ -164,8 +185,9 @@ struct _CompletionDir */ struct _CompletionDirEntry { - gint is_dir; + gboolean is_dir; gchar *entry_name; + gchar *sort_key; }; struct _CompletionUserDir @@ -181,7 +203,7 @@ struct _PossibleCompletion */ gchar *text; gint is_a_completion; - gint is_directory; + gboolean is_directory; /* Private fields */ @@ -194,7 +216,7 @@ struct _CompletionState gchar *updated_text; gint updated_text_len; gint updated_text_alloc; - gint re_complete; + gboolean re_complete; gchar *user_dir_name_buffer; gint user_directories_len; @@ -216,6 +238,20 @@ struct _CompletionState struct _CompletionUserDir *user_directories; }; +enum { + PROP_0, + PROP_SHOW_FILEOPS, + PROP_FILENAME, + PROP_SELECT_MULTIPLE +}; + +enum { + DIR_COLUMN +}; + +enum { + FILE_COLUMN +}; /* File completion functions which would be external, were they used * outside of this file. @@ -224,7 +260,7 @@ struct _CompletionState static CompletionState* cmpl_init_state (void); static void cmpl_free_state (CompletionState *cmpl_state); static gint cmpl_state_okay (CompletionState* cmpl_state); -static gchar* cmpl_strerror (gint); +static const gchar* cmpl_strerror (gint); static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete, gchar **remaining_text, @@ -242,7 +278,7 @@ static gint cmpl_is_a_completion (PossibleCompletion*); /* True if the completion is a directory */ -static gint cmpl_is_directory (PossibleCompletion*); +static gboolean cmpl_is_directory (PossibleCompletion*); /* Obtains the next completion, or NULL */ @@ -260,7 +296,7 @@ static gchar* cmpl_updated_text (CompletionState* cmpl_state) /* After updating, to see if the completion was a directory, call * this. If it was, you should consider re-calling completion_matches. */ -static gint cmpl_updated_dir (CompletionState* cmpl_state); +static gboolean cmpl_updated_dir (CompletionState* cmpl_state); /* Current location: if using file completion, return the current * directory, from which file completion begins. More specifically, @@ -269,22 +305,27 @@ static gint cmpl_updated_dir (CompletionState* cmpl_state) */ static gchar* cmpl_reference_position (CompletionState* cmpl_state); +#if 0 +/* This doesn't work currently and would require changes + * to fnmatch.c to get working. + */ /* backing up: if cmpl_completion_matches returns NULL, you may query * the index of the last completable character into cmpl_updated_text. */ static gint cmpl_last_valid_char (CompletionState* cmpl_state); +#endif /* When the user selects a non-directory, call cmpl_completion_fullname * to get the full name of the selected file. */ -static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state); +static gchar* cmpl_completion_fullname (const gchar*, CompletionState* cmpl_state); /* Directory operations. */ static CompletionDir* open_ref_dir (gchar* text_to_complete, gchar** remaining_text, CompletionState* cmpl_state); -#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN) +#ifndef G_PLATFORM_WIN32 static gboolean check_dir (gchar *dir_name, struct stat *result, gboolean *stat_subdirs); @@ -292,7 +333,7 @@ static gboolean check_dir (gchar *dir_name, static CompletionDir* open_dir (gchar* dir_name, CompletionState* cmpl_state); #ifdef HAVE_PWD_H -static CompletionDir* open_user_dir (gchar* text_to_complete, +static CompletionDir* open_user_dir (const gchar* text_to_complete, CompletionState *cmpl_state); #endif static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir, @@ -303,7 +344,9 @@ static CompletionDirSent* open_new_dir (gchar* dir_name, static gint correct_dir_fullname (CompletionDir* cmpl_dir); static gint correct_parent (CompletionDir* cmpl_dir, struct stat *sbuf); +#ifndef G_PLATFORM_WIN32 static gchar* find_parent_dir_fullname (gchar* dirname); +#endif static CompletionDir* attach_dir (CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state); @@ -312,8 +355,10 @@ static void free_dir (CompletionDir *dir); static void prune_memory_usage(CompletionState *cmpl_state); /* Completion operations */ +#ifdef HAVE_PWD_H static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete, CompletionState *cmpl_state); +#endif static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state); static CompletionDir* find_completion_dir(gchar* text_to_complete, gchar** remaining_text, @@ -330,37 +375,98 @@ static void update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state); static void gtk_file_selection_class_init (GtkFileSelectionClass *klass); +static void gtk_file_selection_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_file_selection_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); static void gtk_file_selection_init (GtkFileSelection *filesel); +static void gtk_file_selection_finalize (GObject *object); static void gtk_file_selection_destroy (GtkObject *object); +static void gtk_file_selection_map (GtkWidget *widget); static gint gtk_file_selection_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data); - -static void gtk_file_selection_file_button (GtkWidget *widget, - gint row, - gint column, - GdkEventButton *bevent, - gpointer user_data); - -static void gtk_file_selection_dir_button (GtkWidget *widget, - gint row, - gint column, - GdkEventButton *bevent, - gpointer data); +static gint gtk_file_selection_insert_text (GtkWidget *widget, + const gchar *new_text, + gint new_text_length, + gint *position, + gpointer user_data); +static void gtk_file_selection_update_fileops (GtkFileSelection *filesel); + +static void gtk_file_selection_file_activate (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data); +static void gtk_file_selection_file_changed (GtkTreeSelection *selection, + gpointer user_data); +static void gtk_file_selection_dir_activate (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data); static void gtk_file_selection_populate (GtkFileSelection *fs, gchar *rel_path, - gint try_complete); + gboolean try_complete, + gboolean reset_entry); static void gtk_file_selection_abort (GtkFileSelection *fs); static void gtk_file_selection_update_history_menu (GtkFileSelection *fs, gchar *current_dir); -static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data); +static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data); static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data); static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data); +static void free_selected_names (GPtrArray *names); + +#ifndef G_PLATFORM_WIN32 + +#define compare_utf8_filenames(a, b) strcmp(a, b) +#define compare_sys_filenames(a, b) strcmp(a, b) + +#else + +static gint +compare_utf8_filenames (const gchar *a, + const gchar *b) +{ + gchar *a_folded, *b_folded; + gint retval; + + a_folded = g_utf8_strdown (a, -1); + b_folded = g_utf8_strdown (b, -1); + + retval = strcmp (a_folded, b_folded); + + g_free (a_folded); + g_free (b_folded); + + return retval; +} + +static gint +compare_sys_filenames (const gchar *a, + const gchar *b) +{ + gchar *a_utf8, *b_utf8; + gint retval; + + a_utf8 = g_filename_to_utf8 (a, -1, NULL, NULL, NULL); + b_utf8 = g_filename_to_utf8 (b, -1, NULL, NULL, NULL); + + retval = compare_utf8_filenames (a_utf8, b_utf8); + + g_free (a_utf8); + g_free (b_utf8); + + return retval; +} +#endif static GtkWindowClass *parent_class = NULL; @@ -371,87 +477,61 @@ static gint cmpl_errno; /* * Take the path currently in the file selection * entry field and translate as necessary from - * a WIN32 style to CYGWIN32 style path. For + * a Win32 style to Cygwin style path. For * instance translate: * x:\somepath\file.jpg * to: - * //x/somepath/file.jpg + * /cygdrive/x/somepath/file.jpg * * Replace the path in the selection text field. * Return a boolean value concerning whether a * translation had to be made. */ -int -translate_win32_path(GtkFileSelection *filesel) +static int +translate_win32_path (GtkFileSelection *filesel) { int updated = 0; - gchar *path; + const gchar *path; + gchar newPath[MAX_PATH]; /* * Retrieve the current path */ path = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry)); - /* - * Translate only if this looks like a DOS-ish - * path... First handle any drive letters. - */ - if (isalpha(path[0]) && (path[1] == ':')) { - /* - * This part kind of stinks... It isn't possible - * to know if there is enough space in the current - * string for the extra character required in this - * conversion. Assume that there isn't enough space - * and use the set function on the text field to - * set the newly created string. - */ - gchar *newPath; - - newPath = g_malloc(strlen(path) + 2); - sprintf(newPath, "//%c/%s", path[0], (path + 3)); - gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), newPath); - - path = newPath; - updated = 1; - } - - /* - * Now, replace backslashes with forward slashes - * if necessary. - */ - if (strchr(path, '\\')) { - int index; - for (index = 0; path[index] != '\0'; index++) - if (path[index] == '\\') - path[index] = '/'; + cygwin_conv_to_posix_path (path, newPath); + updated = (strcmp (path, newPath) != 0); - updated = 1; - } + if (updated) + gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), newPath); return updated; } #endif -GtkType +GType gtk_file_selection_get_type (void) { - static GtkType file_selection_type = 0; + static GType file_selection_type = 0; if (!file_selection_type) { - static const GtkTypeInfo filesel_info = + static const GTypeInfo filesel_info = { - "GtkFileSelection", - sizeof (GtkFileSelection), sizeof (GtkFileSelectionClass), - (GtkClassInitFunc) gtk_file_selection_class_init, - (GtkObjectInitFunc) gtk_file_selection_init, - /* reserved_1 */ NULL, - /* reserved_2 */ NULL, - (GtkClassInitFunc) NULL, + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_file_selection_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkFileSelection), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_file_selection_init, }; - file_selection_type = gtk_type_unique (GTK_TYPE_WINDOW, &filesel_info); + file_selection_type = + g_type_register_static (GTK_TYPE_DIALOG, "GtkFileSelection", + &filesel_info, 0); } return file_selection_type; @@ -460,45 +540,152 @@ gtk_file_selection_get_type (void) static void gtk_file_selection_class_init (GtkFileSelectionClass *class) { + GObjectClass *gobject_class; GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + gobject_class = (GObjectClass*) class; object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = gtk_file_selection_finalize; + gobject_class->set_property = gtk_file_selection_set_property; + gobject_class->get_property = gtk_file_selection_get_property; + + g_object_class_install_property (gobject_class, + PROP_FILENAME, + g_param_spec_string ("filename", + P_("Filename"), + P_("The currently selected filename"), + NULL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + g_object_class_install_property (gobject_class, + PROP_SHOW_FILEOPS, + g_param_spec_boolean ("show_fileops", + P_("Show file operations"), + P_("Whether buttons for creating/manipulating files should be displayed"), + FALSE, + G_PARAM_READABLE | + G_PARAM_WRITABLE)); + g_object_class_install_property (gobject_class, + PROP_SELECT_MULTIPLE, + g_param_spec_boolean ("select_multiple", + P_("Select multiple"), + P_("Whether to allow multiple files to be selected"), + FALSE, + G_PARAM_READABLE | + G_PARAM_WRITABLE)); + object_class->destroy = gtk_file_selection_destroy; + widget_class->map = gtk_file_selection_map; +} - parent_class = gtk_type_class (GTK_TYPE_WINDOW); +static void gtk_file_selection_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkFileSelection *filesel; - object_class->destroy = gtk_file_selection_destroy; + filesel = GTK_FILE_SELECTION (object); + + switch (prop_id) + { + case PROP_FILENAME: + gtk_file_selection_set_filename (filesel, + g_value_get_string (value)); + break; + case PROP_SHOW_FILEOPS: + if (g_value_get_boolean (value)) + gtk_file_selection_show_fileop_buttons (filesel); + else + gtk_file_selection_hide_fileop_buttons (filesel); + break; + case PROP_SELECT_MULTIPLE: + gtk_file_selection_set_select_multiple (filesel, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void gtk_file_selection_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkFileSelection *filesel; + + filesel = GTK_FILE_SELECTION (object); + + switch (prop_id) + { + case PROP_FILENAME: + g_value_set_string (value, + gtk_file_selection_get_filename(filesel)); + break; + + case PROP_SHOW_FILEOPS: + /* This is a little bit hacky, but doing otherwise would require + * adding a field to the object. + */ + g_value_set_boolean (value, (filesel->fileop_c_dir && + filesel->fileop_del_file && + filesel->fileop_ren_file)); + break; + case PROP_SELECT_MULTIPLE: + g_value_set_boolean (value, gtk_file_selection_get_select_multiple (filesel)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } +static gboolean +grab_default (GtkWidget *widget) +{ + gtk_widget_grab_default (widget); + return FALSE; +} + static void gtk_file_selection_init (GtkFileSelection *filesel) { GtkWidget *entry_vbox; GtkWidget *label; - GtkWidget *list_hbox; + GtkWidget *list_hbox, *list_container; GtkWidget *confirm_area; GtkWidget *pulldown_hbox; GtkWidget *scrolled_win; + GtkWidget *eventbox; + GtkWidget *spacer; + GtkDialog *dialog; - char *dir_title [2]; - char *file_title [2]; + GtkListStore *model; + GtkTreeViewColumn *column; + gtk_widget_push_composite_child (); + + dialog = GTK_DIALOG (filesel); + filesel->cmpl_state = cmpl_init_state (); /* The dialog-sized vertical box */ - filesel->main_vbox = gtk_vbox_new (FALSE, 10); + filesel->main_vbox = dialog->vbox; gtk_container_set_border_width (GTK_CONTAINER (filesel), 10); - gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox); - gtk_widget_show (filesel->main_vbox); /* The horizontal box containing create, rename etc. buttons */ filesel->button_area = gtk_hbutton_box_new (); - gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START); - gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX (filesel->button_area), GTK_BUTTONBOX_START); + gtk_box_set_spacing (GTK_BOX (filesel->button_area), 0); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area, FALSE, FALSE, 0); gtk_widget_show (filesel->button_area); - gtk_file_selection_show_fileop_buttons(filesel); + gtk_file_selection_show_fileop_buttons (filesel); /* hbox for pulldown menu */ pulldown_hbox = gtk_hbox_new (TRUE, 5); @@ -512,45 +699,98 @@ gtk_file_selection_init (GtkFileSelection *filesel) FALSE, FALSE, 0); /* The horizontal box containing the directory and file listboxes */ + + spacer = gtk_hbox_new (FALSE, 0); + gtk_widget_set_size_request (spacer, -1, 5); + gtk_box_pack_start (GTK_BOX (filesel->main_vbox), spacer, FALSE, FALSE, 0); + gtk_widget_show (spacer); + list_hbox = gtk_hbox_new (FALSE, 5); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0); gtk_widget_show (list_hbox); + if (WANT_HPANED) + list_container = g_object_new (GTK_TYPE_HPANED, + "visible", TRUE, + "parent", list_hbox, + "border_width", 0, + NULL); + else + list_container = list_hbox; + + spacer = gtk_hbox_new (FALSE, 0); + gtk_widget_set_size_request (spacer, -1, 5); + gtk_box_pack_start (GTK_BOX (filesel->main_vbox), spacer, FALSE, FALSE, 0); + gtk_widget_show (spacer); + + /* The directories list */ + + model = gtk_list_store_new (1, G_TYPE_STRING); + filesel->dir_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + g_object_unref (model); + + column = gtk_tree_view_column_new_with_attributes (_("Folders"), + gtk_cell_renderer_text_new (), + "text", DIR_COLUMN, + NULL); + label = gtk_label_new_with_mnemonic (_("Fol_ders")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), filesel->dir_list); + gtk_widget_show (label); + gtk_tree_view_column_set_widget (column, label); + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column (GTK_TREE_VIEW (filesel->dir_list), column); - /* The directories clist */ - dir_title[0] = _("Directories"); - dir_title[1] = NULL; - filesel->dir_list = gtk_clist_new_with_titles (1, (gchar**) dir_title); - gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT); - gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row", - (GtkSignalFunc) gtk_file_selection_dir_button, - (gpointer) filesel); - gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list)); + gtk_widget_set_size_request (filesel->dir_list, + DIR_LIST_WIDTH, DIR_LIST_HEIGHT); + g_signal_connect (filesel->dir_list, "row_activated", + G_CALLBACK (gtk_file_selection_dir_activate), filesel); + + /* gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list)); */ scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); - gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5); - gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 0); + if (GTK_IS_PANED (list_container)) + gtk_paned_pack1 (GTK_PANED (list_container), scrolled_win, TRUE, TRUE); + else + gtk_container_add (GTK_CONTAINER (list_container), scrolled_win); gtk_widget_show (filesel->dir_list); gtk_widget_show (scrolled_win); - /* The files clist */ - file_title[0] = _("Files"); - file_title[1] = NULL; - filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title); - gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT); - gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row", - (GtkSignalFunc) gtk_file_selection_file_button, - (gpointer) filesel); - gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list)); + /* The files list */ + model = gtk_list_store_new (1, G_TYPE_STRING); + filesel->file_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + g_object_unref (model); + + column = gtk_tree_view_column_new_with_attributes (_("Files"), + gtk_cell_renderer_text_new (), + "text", FILE_COLUMN, + NULL); + label = gtk_label_new_with_mnemonic (_("_Files")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), filesel->file_list); + gtk_widget_show (label); + gtk_tree_view_column_set_widget (column, label); + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_tree_view_append_column (GTK_TREE_VIEW (filesel->file_list), column); + + gtk_widget_set_size_request (filesel->file_list, + FILE_LIST_WIDTH, FILE_LIST_HEIGHT); + g_signal_connect (filesel->file_list, "row_activated", + G_CALLBACK (gtk_file_selection_file_activate), filesel); + g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list)), "changed", + G_CALLBACK (gtk_file_selection_file_changed), filesel); + + /* gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list)); */ scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); - gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5); - gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 0); + gtk_container_add (GTK_CONTAINER (list_container), scrolled_win); gtk_widget_show (filesel->file_list); gtk_widget_show (scrolled_win); @@ -561,61 +801,294 @@ gtk_file_selection_init (GtkFileSelection *filesel) gtk_widget_show (filesel->action_area); /* The OK/Cancel button area */ - confirm_area = gtk_hbutton_box_new (); - gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END); - gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5); - gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0); - gtk_widget_show (confirm_area); + confirm_area = dialog->action_area; + /* The Cancel button */ + filesel->cancel_button = gtk_dialog_add_button (dialog, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); /* The OK button */ - filesel->ok_button = gtk_button_new_with_label (_("OK")); - GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT); - gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0); + filesel->ok_button = gtk_dialog_add_button (dialog, + GTK_STOCK_OK, + GTK_RESPONSE_OK); + gtk_widget_grab_default (filesel->ok_button); - gtk_widget_show (filesel->ok_button); - - /* The Cancel button */ - filesel->cancel_button = gtk_button_new_with_label (_("Cancel")); - GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT); - gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0); - gtk_widget_show (filesel->cancel_button); /* The selection entry widget */ entry_vbox = gtk_vbox_new (FALSE, 2); - gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 2); gtk_widget_show (entry_vbox); - + + eventbox = gtk_event_box_new (); filesel->selection_text = label = gtk_label_new (""); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); - gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (eventbox), label); + gtk_box_pack_start (GTK_BOX (entry_vbox), eventbox, FALSE, FALSE, 0); gtk_widget_show (label); + gtk_widget_show (eventbox); filesel->selection_entry = gtk_entry_new (); - gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event", - (GtkSignalFunc) gtk_file_selection_key_press, filesel); - gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event", - (GtkSignalFunc) gtk_widget_grab_default, - GTK_OBJECT (filesel->ok_button)); - gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate", - (GtkSignalFunc) gtk_button_clicked, - GTK_OBJECT (filesel->ok_button)); + g_signal_connect (filesel->selection_entry, "key_press_event", + G_CALLBACK (gtk_file_selection_key_press), filesel); + g_signal_connect (filesel->selection_entry, "insert_text", + G_CALLBACK (gtk_file_selection_insert_text), NULL); + g_signal_connect_swapped (filesel->selection_entry, "changed", + G_CALLBACK (gtk_file_selection_update_fileops), filesel); + g_signal_connect_swapped (filesel->selection_entry, "focus_in_event", + G_CALLBACK (grab_default), + filesel->ok_button); + g_signal_connect_swapped (filesel->selection_entry, "activate", + G_CALLBACK (gtk_button_clicked), + filesel->ok_button); + gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0); gtk_widget_show (filesel->selection_entry); + gtk_label_set_mnemonic_widget (GTK_LABEL (filesel->selection_text), + filesel->selection_entry); + if (!cmpl_state_okay (filesel->cmpl_state)) { gchar err_buf[256]; - sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno)); + g_snprintf (err_buf, sizeof (err_buf), _("Folder unreadable: %s"), cmpl_strerror (cmpl_errno)); gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf); } else { - gtk_file_selection_populate (filesel, "", FALSE); + gtk_file_selection_populate (filesel, "", FALSE, TRUE); } gtk_widget_grab_focus (filesel->selection_entry); + + gtk_widget_pop_composite_child (); +} + +static gchar * +uri_list_extract_first_uri (const gchar* uri_list) +{ + const gchar *p, *q; + + g_return_val_if_fail (uri_list != NULL, NULL); + + p = uri_list; + /* We don't actually try to validate the URI according to RFC + * 2396, or even check for allowed characters - we just ignore + * comments and trim whitespace off the ends. We also + * allow LF delimination as well as the specified CRLF. + * + * We do allow comments like specified in RFC 2483. + */ + while (p) + { + if (*p != '#') + { + while (g_ascii_isspace(*p)) + p++; + + q = p; + while (*q && (*q != '\n') && (*q != '\r')) + q++; + + if (q > p) + { + q--; + while (q > p && g_ascii_isspace (*q)) + q--; + + if (q > p) + return g_strndup (p, q - p + 1); + } + } + p = strchr (p, '\n'); + if (p) + p++; + } + return NULL; +} + +static void +dnd_really_drop (GtkWidget *dialog, gint response_id, GtkFileSelection *fs) +{ + gchar *filename; + + if (response_id == GTK_RESPONSE_YES) + { + filename = g_object_get_data (G_OBJECT (dialog), "gtk-fs-dnd-filename"); + + gtk_file_selection_set_filename (fs, filename); + } + + gtk_widget_destroy (dialog); +} + + +static void +filenames_dropped (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + char *uri = NULL; + char *filename = NULL; + char *hostname; + char this_hostname[257]; + int res; + GError *error = NULL; + + if (!selection_data->data) + return; + + uri = uri_list_extract_first_uri ((char *)selection_data->data); + + if (!uri) + return; + + filename = g_filename_from_uri (uri, &hostname, &error); + g_free (uri); + + if (!filename) + { + g_warning ("Error getting dropped filename: %s\n", + error->message); + g_error_free (error); + return; + } + + res = gethostname (this_hostname, 256); + this_hostname[256] = 0; + + if ((hostname == NULL) || + (res == 0 && strcmp (hostname, this_hostname) == 0) || + (strcmp (hostname, "localhost") == 0)) + gtk_file_selection_set_filename (GTK_FILE_SELECTION (widget), + filename); + else + { + GtkWidget *dialog; + gchar *filename_utf8; + + /* Conversion back to UTF-8 should always succeed for the result + * of g_filename_from_uri() + */ + filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); + g_assert (filename_utf8); + + dialog = gtk_message_dialog_new (GTK_WINDOW (widget), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + _("The file \"%s\" resides on another machine (called %s) and may not be available to this program.\n" + "Are you sure that you want to select it?"), filename_utf8, hostname); + g_free (filename_utf8); + + g_object_set_data_full (G_OBJECT (dialog), "gtk-fs-dnd-filename", g_strdup (filename), g_free); + + g_signal_connect_data (dialog, "response", + (GCallback) dnd_really_drop, + widget, NULL, 0); + + gtk_widget_show (dialog); + } + + g_free (hostname); + g_free (filename); +} + +enum +{ + TARGET_URILIST, + TARGET_UTF8_STRING, + TARGET_STRING, + TARGET_TEXT, + TARGET_COMPOUND_TEXT +}; + + +static void +filenames_drag_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time, + GtkFileSelection *filesel) +{ + const gchar *file; + gchar *uri_list; + char hostname[256]; + int res; + GError *error; + + file = gtk_file_selection_get_filename (filesel); + + if (file) + { + if (info == TARGET_URILIST) + { + res = gethostname (hostname, 256); + + error = NULL; + uri_list = g_filename_to_uri (file, (!res)?hostname:NULL, &error); + if (!uri_list) + { + g_warning ("Error getting filename: %s\n", + error->message); + g_error_free (error); + return; + } + + gtk_selection_data_set (selection_data, + selection_data->target, 8, + (void *)uri_list, strlen((char *)uri_list)); + g_free (uri_list); + } + else + { + gchar *filename_utf8 = g_filename_to_utf8 (file, -1, NULL, NULL, NULL); + g_assert (filename_utf8); + gtk_selection_data_set_text (selection_data, filename_utf8, -1); + g_free (filename_utf8); + } + } +} + +static void +file_selection_setup_dnd (GtkFileSelection *filesel) +{ + GtkWidget *eventbox; + static const GtkTargetEntry drop_types[] = { + { "text/uri-list", 0, TARGET_URILIST} + }; + static gint n_drop_types = sizeof(drop_types)/sizeof(drop_types[0]); + static const GtkTargetEntry drag_types[] = { + { "text/uri-list", 0, TARGET_URILIST}, + { "UTF8_STRING", 0, TARGET_UTF8_STRING }, + { "STRING", 0, 0 }, + { "TEXT", 0, 0 }, + { "COMPOUND_TEXT", 0, 0 } + }; + static gint n_drag_types = sizeof(drag_types)/sizeof(drag_types[0]); + + gtk_drag_dest_set (GTK_WIDGET (filesel), + GTK_DEST_DEFAULT_ALL, + drop_types, n_drop_types, + GDK_ACTION_COPY); + + g_signal_connect (filesel, "drag_data_received", + G_CALLBACK (filenames_dropped), NULL); + + eventbox = gtk_widget_get_parent (filesel->selection_text); + gtk_drag_source_set (eventbox, + GDK_BUTTON1_MASK, + drag_types, n_drag_types, + GDK_ACTION_COPY); + + g_signal_connect (eventbox, "drag_data_get", + G_CALLBACK (filenames_drag_get), filesel); } GtkWidget* @@ -623,25 +1096,27 @@ gtk_file_selection_new (const gchar *title) { GtkFileSelection *filesel; - filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION); + filesel = g_object_new (GTK_TYPE_FILE_SELECTION, NULL); gtk_window_set_title (GTK_WINDOW (filesel), title); + gtk_dialog_set_has_separator (GTK_DIALOG (filesel), FALSE); + file_selection_setup_dnd (filesel); + return GTK_WIDGET (filesel); } void gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel) { - g_return_if_fail (filesel != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); /* delete, create directory, and rename */ if (!filesel->fileop_c_dir) { - filesel->fileop_c_dir = gtk_button_new_with_label (_("Create Dir")); - gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked", - (GtkSignalFunc) gtk_file_selection_create_dir, - (gpointer) filesel); + filesel->fileop_c_dir = gtk_button_new_with_mnemonic (_("_New Folder")); + g_signal_connect (filesel->fileop_c_dir, "clicked", + G_CALLBACK (gtk_file_selection_create_dir), + filesel); gtk_box_pack_start (GTK_BOX (filesel->button_area), filesel->fileop_c_dir, TRUE, TRUE, 0); gtk_widget_show (filesel->fileop_c_dir); @@ -649,10 +1124,10 @@ gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel) if (!filesel->fileop_del_file) { - filesel->fileop_del_file = gtk_button_new_with_label (_("Delete File")); - gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked", - (GtkSignalFunc) gtk_file_selection_delete_file, - (gpointer) filesel); + filesel->fileop_del_file = gtk_button_new_with_mnemonic (_("De_lete File")); + g_signal_connect (filesel->fileop_del_file, "clicked", + G_CALLBACK (gtk_file_selection_delete_file), + filesel); gtk_box_pack_start (GTK_BOX (filesel->button_area), filesel->fileop_del_file, TRUE, TRUE, 0); gtk_widget_show (filesel->fileop_del_file); @@ -660,25 +1135,26 @@ gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel) if (!filesel->fileop_ren_file) { - filesel->fileop_ren_file = gtk_button_new_with_label (_("Rename File")); - gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked", - (GtkSignalFunc) gtk_file_selection_rename_file, - (gpointer) filesel); + filesel->fileop_ren_file = gtk_button_new_with_mnemonic (_("_Rename File")); + g_signal_connect (filesel->fileop_ren_file, "clicked", + G_CALLBACK (gtk_file_selection_rename_file), + filesel); gtk_box_pack_start (GTK_BOX (filesel->button_area), filesel->fileop_ren_file, TRUE, TRUE, 0); gtk_widget_show (filesel->fileop_ren_file); } - - gtk_widget_queue_resize(GTK_WIDGET(filesel)); + + gtk_file_selection_update_fileops (filesel); + + g_object_notify (G_OBJECT (filesel), "show_fileops"); } void gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel) { - g_return_if_fail (filesel != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); - if (filesel->fileop_ren_file) + if (filesel->fileop_ren_file) { gtk_widget_destroy (filesel->fileop_ren_file); filesel->fileop_ren_file = NULL; @@ -695,62 +1171,103 @@ gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel) gtk_widget_destroy (filesel->fileop_c_dir); filesel->fileop_c_dir = NULL; } + g_object_notify (G_OBJECT (filesel), "show_fileops"); } +/** + * gtk_file_selection_set_filename: + * @filesel: a #GtkFileSelection. + * @filename: a string to set as the default file name. + * + * Sets a default path for the file requestor. If @filename includes a + * directory path, then the requestor will open with that path as its + * current working directory. + * + * This has the consequence that in order to open the requestor with a + * working directory and an empty filename, @filename must have a trailing + * directory separator. + * + * The encoding of @filename is the on-disk encoding, which + * may not be UTF-8. See g_filename_from_utf8(). + **/ void gtk_file_selection_set_filename (GtkFileSelection *filesel, const gchar *filename) { - char buf[MAXPATHLEN]; + gchar *buf; const char *name, *last_slash; + char *filename_utf8; - g_return_if_fail (filesel != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); g_return_if_fail (filename != NULL); - last_slash = strrchr (filename, G_DIR_SEPARATOR); + filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL); + g_return_if_fail (filename_utf8 != NULL); + + last_slash = strrchr (filename_utf8, G_DIR_SEPARATOR); if (!last_slash) { - buf[0] = 0; - name = filename; + buf = g_strdup (""); + name = filename_utf8; } else { - gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1); - - strncpy (buf, filename, len); - buf[len] = 0; - + buf = g_strdup (filename_utf8); + buf[last_slash - filename_utf8 + 1] = 0; name = last_slash + 1; } - gtk_file_selection_populate (filesel, buf, FALSE); + gtk_file_selection_populate (filesel, buf, FALSE, TRUE); if (filesel->selection_entry) gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name); + g_free (buf); + g_object_notify (G_OBJECT (filesel), "filename"); + + g_free (filename_utf8); } -gchar* +/** + * gtk_file_selection_get_filename: + * @filesel: a #GtkFileSelection + * + * This function returns the selected filename in the on-disk encoding + * (see g_filename_from_utf8()), which may or may not be the same as that + * used by GTK+ (UTF-8). To convert to UTF-8, call g_filename_to_utf8(). + * The returned string points to a statically allocated buffer and + * should be copied if you plan to keep it around. + * + * If no file is selected then the selected directory path is returned. + * + * Return value: currently-selected filename in the on-disk encoding. + **/ +G_CONST_RETURN gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel) { - static char nothing[2] = ""; - char *text; - char *filename; + static const gchar nothing[2] = ""; + static gchar something[MAXPATHLEN*2]; + char *sys_filename; + const char *text; - g_return_val_if_fail (filesel != NULL, nothing); g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing); #ifdef G_WITH_CYGWIN - translate_win32_path(filesel); + translate_win32_path (filesel); #endif text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry)); if (text) { - filename = cmpl_completion_fullname (text, filesel->cmpl_state); - return filename; + gchar *fullname = cmpl_completion_fullname (text, filesel->cmpl_state); + sys_filename = g_filename_from_utf8 (fullname, -1, NULL, NULL, NULL); + g_free (fullname); + if (!sys_filename) + return nothing; + strncpy (something, sys_filename, sizeof (something)); + g_free (sys_filename); + return something; } return nothing; @@ -760,13 +1277,12 @@ void gtk_file_selection_complete (GtkFileSelection *filesel, const gchar *pattern) { - g_return_if_fail (filesel != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); g_return_if_fail (pattern != NULL); if (filesel->selection_entry) gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern); - gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE); + gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE, TRUE); } static void @@ -775,14 +1291,16 @@ gtk_file_selection_destroy (GtkObject *object) GtkFileSelection *filesel; GList *list; HistoryCallbackArg *callback_arg; - - g_return_if_fail (object != NULL); + g_return_if_fail (GTK_IS_FILE_SELECTION (object)); - + filesel = GTK_FILE_SELECTION (object); if (filesel->fileop_dialog) - gtk_widget_destroy (filesel->fileop_dialog); + { + gtk_widget_destroy (filesel->fileop_dialog); + filesel->fileop_dialog = NULL; + } if (filesel->history_list) { @@ -797,92 +1315,117 @@ gtk_file_selection_destroy (GtkObject *object) g_list_free (filesel->history_list); filesel->history_list = NULL; } + + if (filesel->cmpl_state) + { + cmpl_free_state (filesel->cmpl_state); + filesel->cmpl_state = NULL; + } + + if (filesel->selected_names) + { + free_selected_names (filesel->selected_names); + filesel->selected_names = NULL; + } + + if (filesel->last_selected) + { + g_free (filesel->last_selected); + filesel->last_selected = NULL; + } + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +gtk_file_selection_map (GtkWidget *widget) +{ + GtkFileSelection *filesel = GTK_FILE_SELECTION (widget); + + /* Refresh the contents */ + gtk_file_selection_populate (filesel, "", FALSE, FALSE); - cmpl_free_state (filesel->cmpl_state); - filesel->cmpl_state = NULL; + GTK_WIDGET_CLASS (parent_class)->map (widget); +} + +static void +gtk_file_selection_finalize (GObject *object) +{ + GtkFileSelection *filesel = GTK_FILE_SELECTION (object); - if (GTK_OBJECT_CLASS (parent_class)->destroy) - (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); + g_free (filesel->fileop_file); + + G_OBJECT_CLASS (parent_class)->finalize (object); } /* Begin file operations callbacks */ static void -gtk_file_selection_fileop_error (GtkFileSelection *fs, gchar *error_message) +gtk_file_selection_fileop_error (GtkFileSelection *fs, + gchar *error_message) { - GtkWidget *label; - GtkWidget *vbox; - GtkWidget *button; GtkWidget *dialog; - + g_return_if_fail (error_message != NULL); - - /* main dialog */ - dialog = gtk_dialog_new (); - /* - gtk_signal_connect (GTK_OBJECT (dialog), "destroy", - (GtkSignalFunc) gtk_file_selection_fileop_destroy, - (gpointer) fs); - */ - gtk_window_set_title (GTK_WINDOW (dialog), _("Error")); - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); - - /* If file dialog is grabbed, make this dialog modal too */ - /* When error dialog is closed, file dialog will be grabbed again */ - if (GTK_WINDOW(fs)->modal) - gtk_window_set_modal (GTK_WINDOW(dialog), TRUE); - 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); - gtk_widget_show(vbox); - - label = gtk_label_new(error_message); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); - gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); - gtk_widget_show(label); + /* main dialog */ + dialog = gtk_message_dialog_new (GTK_WINDOW (fs), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", error_message); /* yes, we free it */ g_free (error_message); - - /* close button */ - button = gtk_button_new_with_label (_("Close")); - gtk_signal_connect_object (GTK_OBJECT (button), "clicked", - (GtkSignalFunc) gtk_widget_destroy, - (gpointer) dialog); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), - button, TRUE, TRUE, 0); - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); - gtk_widget_grab_default(button); - gtk_widget_show (button); + + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + g_signal_connect_swapped (dialog, "response", + G_CALLBACK (gtk_widget_destroy), + dialog); gtk_widget_show (dialog); } static void -gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data) +gtk_file_selection_fileop_destroy (GtkWidget *widget, + gpointer data) { GtkFileSelection *fs = data; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); fs->fileop_dialog = NULL; } - -static void -gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data) +static gboolean +entry_is_empty (GtkEntry *entry) +{ + const gchar *text = gtk_entry_get_text (entry); + + return *text == '\0'; +} + +static void +gtk_file_selection_fileop_entry_changed (GtkEntry *entry, + GtkWidget *button) +{ + gtk_widget_set_sensitive (button, !entry_is_empty (entry)); +} + +static void +gtk_file_selection_create_dir_confirmed (GtkWidget *widget, + gpointer data) { GtkFileSelection *fs = data; - gchar *dirname; + const gchar *dirname; gchar *path; gchar *full_path; + gchar *sys_full_path; gchar *buf; + GError *error = NULL; CompletionState *cmpl_state; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry)); @@ -890,20 +1433,37 @@ gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data) path = cmpl_reference_position (cmpl_state); full_path = g_strconcat (path, G_DIR_SEPARATOR_S, dirname, NULL); - if ( (mkdir (full_path, 0755) < 0) ) + sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error); + if (error) { - buf = g_strconcat ("Error creating directory \"", dirname, "\": ", - g_strerror(errno), NULL); + if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE)) + buf = g_strdup_printf (_("The folder name \"%s\" contains symbols that are not allowed in filenames"), dirname); + else + buf = g_strdup_printf (_("Error creating folder \"%s\": %s\n%s"), dirname, error->message, + _("You probably used symbols not allowed in filenames.")); + gtk_file_selection_fileop_error (fs, buf); + g_error_free (error); + goto out; + } + + if (mkdir (sys_full_path, 0777) < 0) + { + buf = g_strdup_printf (_("Error creating folder \"%s\": %s\n"), dirname, + g_strerror (errno)); gtk_file_selection_fileop_error (fs, buf); } + + out: g_free (full_path); + g_free (sys_full_path); gtk_widget_destroy (fs->fileop_dialog); - gtk_file_selection_populate (fs, "", FALSE); + gtk_file_selection_populate (fs, "", FALSE, FALSE); } static void -gtk_file_selection_create_dir (GtkWidget *widget, gpointer data) +gtk_file_selection_create_dir (GtkWidget *widget, + gpointer data) { GtkFileSelection *fs = data; GtkWidget *label; @@ -911,183 +1471,194 @@ gtk_file_selection_create_dir (GtkWidget *widget, gpointer data) GtkWidget *vbox; GtkWidget *button; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); if (fs->fileop_dialog) - return; + return; /* main dialog */ - fs->fileop_dialog = dialog = gtk_dialog_new (); - gtk_signal_connect (GTK_OBJECT (dialog), "destroy", - (GtkSignalFunc) gtk_file_selection_fileop_destroy, - (gpointer) fs); - gtk_window_set_title (GTK_WINDOW (dialog), _("Create Directory")); + dialog = gtk_dialog_new (); + fs->fileop_dialog = dialog; + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_file_selection_fileop_destroy), + fs); + gtk_window_set_title (GTK_WINDOW (dialog), _("New Folder")); gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (fs)); /* 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); + if (GTK_WINDOW (fs)->modal) + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); - 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, + 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); - gtk_widget_show(vbox); + gtk_widget_show( vbox); - 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); - gtk_widget_show(label); + label = gtk_label_new_with_mnemonic (_("_Folder name:")); + gtk_misc_set_alignment(GTK_MISC (label), 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5); + gtk_widget_show (label); /* The directory entry widget */ fs->fileop_entry = gtk_entry_new (); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), fs->fileop_entry); gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, TRUE, TRUE, 5); - GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT); + GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT); + gtk_entry_set_activates_default (GTK_ENTRY (fs->fileop_entry), TRUE); gtk_widget_show (fs->fileop_entry); /* buttons */ - button = gtk_button_new_with_label (_("Create")); - gtk_signal_connect (GTK_OBJECT (button), "clicked", - (GtkSignalFunc) gtk_file_selection_create_dir_confirmed, - (gpointer) fs); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), - button, TRUE, TRUE, 0); - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); - gtk_widget_show(button); - - button = gtk_button_new_with_label (_("Cancel")); - gtk_signal_connect_object (GTK_OBJECT (button), "clicked", - (GtkSignalFunc) gtk_widget_destroy, - (gpointer) dialog); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), - button, TRUE, TRUE, 0); - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); - gtk_widget_grab_default(button); - gtk_widget_show (button); - + button = gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gtk_widget_destroy), + dialog); + + gtk_widget_grab_focus (fs->fileop_entry); + + button = gtk_dialog_add_button (GTK_DIALOG (dialog), + _("C_reate"), GTK_RESPONSE_OK); + gtk_widget_set_sensitive (button, FALSE); + g_signal_connect (button, "clicked", + G_CALLBACK (gtk_file_selection_create_dir_confirmed), + fs); + g_signal_connect (fs->fileop_entry, "changed", + G_CALLBACK (gtk_file_selection_fileop_entry_changed), + button); + + gtk_widget_grab_default (button); + gtk_widget_show (dialog); } static void -gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data) +gtk_file_selection_delete_file_response (GtkDialog *dialog, + gint response_id, + gpointer data) { GtkFileSelection *fs = data; CompletionState *cmpl_state; gchar *path; gchar *full_path; + gchar *sys_full_path; + GError *error = NULL; gchar *buf; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); + if (response_id != GTK_RESPONSE_OK) + { + gtk_widget_destroy (GTK_WIDGET (dialog)); + return; + } + cmpl_state = (CompletionState*) fs->cmpl_state; path = cmpl_reference_position (cmpl_state); full_path = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL); - if ( (unlink (full_path) < 0) ) + sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error); + if (error) { - buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ", - g_strerror(errno), NULL); + if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE)) + buf = g_strdup_printf (_("The filename \"%s\" contains symbols that are not allowed in filenames"), + fs->fileop_file); + else + buf = g_strdup_printf (_("Error deleting file \"%s\": %s\n%s"), + fs->fileop_file, error->message, + _("It probably contains symbols not allowed in filenames.")); + gtk_file_selection_fileop_error (fs, buf); + g_error_free (error); + goto out; } + + if (unlink (sys_full_path) < 0) + { + buf = g_strdup_printf (_("Error deleting file \"%s\": %s"), + fs->fileop_file, g_strerror (errno)); + gtk_file_selection_fileop_error (fs, buf); + } + + out: g_free (full_path); + g_free (sys_full_path); gtk_widget_destroy (fs->fileop_dialog); - gtk_file_selection_populate (fs, "", FALSE); + gtk_file_selection_populate (fs, "", FALSE, TRUE); } static void -gtk_file_selection_delete_file (GtkWidget *widget, gpointer data) +gtk_file_selection_delete_file (GtkWidget *widget, + gpointer data) { GtkFileSelection *fs = data; - GtkWidget *label; - GtkWidget *vbox; - GtkWidget *button; GtkWidget *dialog; - gchar *filename; - gchar *buf; + const gchar *filename; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); if (fs->fileop_dialog) - return; + return; #ifdef G_WITH_CYGWIN - translate_win32_path(fs); + translate_win32_path (fs); #endif filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)); - if (strlen(filename) < 1) - return; + if (strlen (filename) < 1) + return; - fs->fileop_file = filename; + g_free (fs->fileop_file); + fs->fileop_file = g_strdup (filename); /* main dialog */ - fs->fileop_dialog = dialog = gtk_dialog_new (); - gtk_signal_connect (GTK_OBJECT (dialog), "destroy", - (GtkSignalFunc) gtk_file_selection_fileop_destroy, - (gpointer) fs); + fs->fileop_dialog = dialog = + gtk_message_dialog_new (GTK_WINDOW (fs), + GTK_WINDOW (fs)->modal ? GTK_DIALOG_MODAL : 0, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Really delete file \"%s\"?"), filename); + + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_file_selection_fileop_destroy), + fs); gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File")); gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); - - /* 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); - - 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); - gtk_widget_show(vbox); - - 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); - gtk_widget_show(label); - g_free(buf); /* buttons */ - button = gtk_button_new_with_label (_("Delete")); - gtk_signal_connect (GTK_OBJECT (button), "clicked", - (GtkSignalFunc) gtk_file_selection_delete_file_confirmed, - (gpointer) fs); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), - button, TRUE, TRUE, 0); - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); - gtk_widget_show(button); - - button = gtk_button_new_with_label (_("Cancel")); - gtk_signal_connect_object (GTK_OBJECT (button), "clicked", - (GtkSignalFunc) gtk_widget_destroy, - (gpointer) dialog); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), - button, TRUE, TRUE, 0); - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); - gtk_widget_grab_default(button); - gtk_widget_show (button); + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_DELETE, GTK_RESPONSE_OK, + NULL); - gtk_widget_show (dialog); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_file_selection_delete_file_response), + fs); + + gtk_widget_show (dialog); } static void -gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data) +gtk_file_selection_rename_file_confirmed (GtkWidget *widget, + gpointer data) { GtkFileSelection *fs = data; gchar *buf; - gchar *file; + const gchar *file; gchar *path; gchar *new_filename; gchar *old_filename; + gchar *sys_new_filename; + gchar *sys_old_filename; CompletionState *cmpl_state; + GError *error = NULL; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry)); @@ -1097,21 +1668,60 @@ gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data) new_filename = g_strconcat (path, G_DIR_SEPARATOR_S, file, NULL); old_filename = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL); - if ( (rename (old_filename, new_filename)) < 0) + sys_new_filename = g_filename_from_utf8 (new_filename, -1, NULL, NULL, &error); + if (error) + { + if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE)) + buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), new_filename); + else + buf = g_strdup_printf (_("Error renaming file to \"%s\": %s\n%s"), + new_filename, error->message, + _("You probably used symbols not allowed in filenames.")); + gtk_file_selection_fileop_error (fs, buf); + g_error_free (error); + goto out1; + } + + sys_old_filename = g_filename_from_utf8 (old_filename, -1, NULL, NULL, &error); + if (error) + { + if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE)) + buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), old_filename); + else + buf = g_strdup_printf (_("Error renaming file \"%s\": %s\n%s"), + old_filename, error->message, + _("It probably contains symbols not allowed in filenames.")); + gtk_file_selection_fileop_error (fs, buf); + g_error_free (error); + goto out2; + } + + if (rename (sys_old_filename, sys_new_filename) < 0) { - buf = g_strconcat ("Error renaming file \"", file, "\": ", - g_strerror(errno), NULL); + buf = g_strdup_printf (_("Error renaming file \"%s\" to \"%s\": %s"), + sys_old_filename, sys_new_filename, + g_strerror (errno)); gtk_file_selection_fileop_error (fs, buf); + goto out2; } + + gtk_file_selection_populate (fs, "", FALSE, FALSE); + gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), file); + + out2: + g_free (sys_old_filename); + + out1: g_free (new_filename); g_free (old_filename); + g_free (sys_new_filename); gtk_widget_destroy (fs->fileop_dialog); - gtk_file_selection_populate (fs, "", FALSE); } static void -gtk_file_selection_rename_file (GtkWidget *widget, gpointer data) +gtk_file_selection_rename_file (GtkWidget *widget, + gpointer data) { GtkFileSelection *fs = data; GtkWidget *label; @@ -1120,47 +1730,49 @@ gtk_file_selection_rename_file (GtkWidget *widget, gpointer data) GtkWidget *button; gchar *buf; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (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; + g_free (fs->fileop_file); + fs->fileop_file = g_strdup (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) gtk_file_selection_fileop_destroy, - (gpointer) fs); + g_signal_connect (dialog, "destroy", + G_CALLBACK (gtk_file_selection_fileop_destroy), + fs); gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File")); gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (fs)); /* 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); + if (GTK_WINDOW (fs)->modal) + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); - 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); + 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); gtk_widget_show(vbox); - 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); - gtk_widget_show(label); - g_free(buf); + buf = g_strdup_printf (_("Rename file \"%s\" to:"), fs->fileop_file); + 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); + gtk_widget_show (label); + 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_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT); + gtk_entry_set_activates_default (GTK_ENTRY (fs->fileop_entry), TRUE); gtk_widget_show (fs->fileop_entry); gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file); @@ -1168,28 +1780,67 @@ gtk_file_selection_rename_file (GtkWidget *widget, gpointer data) 0, strlen (fs->fileop_file)); /* buttons */ - button = gtk_button_new_with_label (_("Rename")); - gtk_signal_connect (GTK_OBJECT (button), "clicked", - (GtkSignalFunc) gtk_file_selection_rename_file_confirmed, - (gpointer) fs); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), - button, TRUE, TRUE, 0); - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); - gtk_widget_show(button); - - button = gtk_button_new_with_label (_("Cancel")); - gtk_signal_connect_object (GTK_OBJECT (button), "clicked", - (GtkSignalFunc) gtk_widget_destroy, - (gpointer) dialog); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), - button, TRUE, TRUE, 0); - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); - gtk_widget_grab_default(button); - gtk_widget_show (button); - + button = gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gtk_widget_destroy), + dialog); + + gtk_widget_grab_focus (fs->fileop_entry); + + button = gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Rename"), GTK_RESPONSE_OK); + g_signal_connect (button, "clicked", + G_CALLBACK (gtk_file_selection_rename_file_confirmed), + fs); + g_signal_connect (fs->fileop_entry, "changed", + G_CALLBACK (gtk_file_selection_fileop_entry_changed), + button); + + gtk_widget_grab_default (button); + gtk_widget_show (dialog); } +static gint +gtk_file_selection_insert_text (GtkWidget *widget, + const gchar *new_text, + gint new_text_length, + gint *position, + gpointer user_data) +{ + gchar *filename; + + filename = g_filename_from_utf8 (new_text, new_text_length, NULL, NULL, NULL); + + if (!filename) + { + gdk_display_beep (gtk_widget_get_display (widget)); + g_signal_stop_emission_by_name (widget, "insert_text"); + return FALSE; + } + + g_free (filename); + + return TRUE; +} + +static void +gtk_file_selection_update_fileops (GtkFileSelection *fs) +{ + gboolean sensitive; + + if (!fs->selection_entry) + return; + + sensitive = !entry_is_empty (GTK_ENTRY (fs->selection_entry)); + + if (fs->fileop_del_file) + gtk_widget_set_sensitive (fs->fileop_del_file, sensitive); + + if (fs->fileop_ren_file) + gtk_widget_set_sensitive (fs->fileop_ren_file, sensitive); +} static gint gtk_file_selection_key_press (GtkWidget *widget, @@ -1202,37 +1853,33 @@ gtk_file_selection_key_press (GtkWidget *widget, g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (event != NULL, FALSE); - if (event->keyval == GDK_Tab) + if ((event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab) && + (event->state & gtk_accelerator_get_default_mod_mask ()) == 0) { fs = GTK_FILE_SELECTION (user_data); #ifdef G_WITH_CYGWIN - translate_win32_path(fs); + translate_win32_path (fs); #endif - text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)); - - text = g_strdup (text); + text = g_strdup (gtk_entry_get_text (GTK_ENTRY (fs->selection_entry))); - gtk_file_selection_populate (fs, text, TRUE); + gtk_file_selection_populate (fs, text, TRUE, TRUE); g_free (text); - gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event"); - return TRUE; } return FALSE; } - static void -gtk_file_selection_history_callback (GtkWidget *widget, gpointer data) +gtk_file_selection_history_callback (GtkWidget *widget, + gpointer data) { GtkFileSelection *fs = data; HistoryCallbackArg *callback_arg; GList *list; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); list = fs->history_list; @@ -1242,7 +1889,7 @@ gtk_file_selection_history_callback (GtkWidget *widget, gpointer data) if (callback_arg->menu_item == widget) { - gtk_file_selection_populate (fs, callback_arg->directory, FALSE); + gtk_file_selection_populate (fs, callback_arg->directory, FALSE, FALSE); break; } @@ -1252,7 +1899,7 @@ gtk_file_selection_history_callback (GtkWidget *widget, gpointer data) static void gtk_file_selection_update_history_menu (GtkFileSelection *fs, - gchar *current_directory) + gchar *current_directory) { HistoryCallbackArg *callback_arg; GtkWidget *menu_item; @@ -1261,7 +1908,6 @@ gtk_file_selection_update_history_menu (GtkFileSelection *fs, gint dir_len; gint i; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); g_return_if_fail (current_directory != NULL); @@ -1281,7 +1927,7 @@ gtk_file_selection_update_history_menu (GtkFileSelection *fs, gtk_widget_destroy (fs->history_menu); } - fs->history_menu = gtk_menu_new(); + fs->history_menu = gtk_menu_new (); current_dir = g_strdup (current_directory); @@ -1297,7 +1943,7 @@ gtk_file_selection_update_history_menu (GtkFileSelection *fs, if (i != dir_len) current_dir[i + 1] = '\0'; #ifdef G_WITH_CYGWIN - if (!strcmp(current_dir, "//")) + if (!strcmp (current_dir, "//")) continue; #endif menu_item = gtk_menu_item_new_with_label (current_dir); @@ -1308,18 +1954,21 @@ gtk_file_selection_update_history_menu (GtkFileSelection *fs, /* 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 filesel */ - if (dir_len == i) { - callback_arg->directory = g_strdup (""); - } else { - callback_arg->directory = g_strdup (current_dir); - } + 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) gtk_file_selection_history_callback, - (gpointer) fs); - gtk_menu_append (GTK_MENU (fs->history_menu), menu_item); + g_signal_connect (menu_item, "activate", + G_CALLBACK (gtk_file_selection_history_callback), + fs); + gtk_menu_shell_append (GTK_MENU_SHELL (fs->history_menu), menu_item); gtk_widget_show (menu_item); } } @@ -1329,162 +1978,141 @@ gtk_file_selection_update_history_menu (GtkFileSelection *fs, g_free (current_dir); } -static void -gtk_file_selection_file_button (GtkWidget *widget, - gint row, - gint column, - GdkEventButton *bevent, - gpointer user_data) +static gchar * +get_real_filename (gchar *filename, + gboolean free_old) { - GtkFileSelection *fs = NULL; - gchar *filename, *temp = NULL; - - g_return_if_fail (GTK_IS_CLIST (widget)); - - fs = user_data; - g_return_if_fail (fs != NULL); - g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); - - gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp); - filename = g_strdup (temp); - #ifdef G_WITH_CYGWIN /* Check to see if the selection was a drive selector */ - if (isalpha(filename[0]) && (filename[1] == ':')) { - /* It is... map it to a CYGWIN32 drive */ - char temp_filename[10]; + if (isalpha (filename[0]) && (filename[1] == ':')) + { + gchar temp_filename[MAX_PATH]; + int len; - sprintf(temp_filename, "//%c/", tolower(filename[0])); - g_free(filename); - filename = g_strdup(temp_filename); - } -#endif /* G_WITH_CYGWIN */ + cygwin_conv_to_posix_path (filename, temp_filename); - if (filename) - { - if (bevent) - switch (bevent->type) - { - case GDK_2BUTTON_PRESS: - gtk_button_clicked (GTK_BUTTON (fs->ok_button)); - break; - - default: - gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); - break; - } - else - gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); + /* we need trailing '/'. */ + len = strlen (temp_filename); + if (len > 0 && temp_filename[len-1] != '/') + { + temp_filename[len] = '/'; + temp_filename[len+1] = '\0'; + } + + if (free_old) + g_free (filename); - g_free (filename); + return g_strdup (temp_filename); } +#endif /* G_WITH_CYGWIN */ + return filename; } static void -gtk_file_selection_dir_button (GtkWidget *widget, - gint row, - gint column, - GdkEventButton *bevent, - gpointer user_data) -{ - GtkFileSelection *fs = NULL; - gchar *filename, *temp = NULL; - - g_return_if_fail (GTK_IS_CLIST (widget)); - - fs = GTK_FILE_SELECTION (user_data); - g_return_if_fail (fs != NULL); - g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); +gtk_file_selection_file_activate (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + GtkFileSelection *fs = GTK_FILE_SELECTION (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + GtkTreeIter iter; + gchar *filename; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, FILE_COLUMN, &filename, -1); + filename = get_real_filename (filename, TRUE); + gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); + gtk_button_clicked (GTK_BUTTON (fs->ok_button)); - gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp); - filename = g_strdup (temp); + g_free (filename); +} - if (filename) - { - if (bevent) - switch (bevent->type) - { - case GDK_2BUTTON_PRESS: - gtk_file_selection_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); +static void +gtk_file_selection_dir_activate (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + GtkFileSelection *fs = GTK_FILE_SELECTION (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + GtkTreeIter iter; + gchar *filename; - g_free (filename); - } + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, DIR_COLUMN, &filename, -1); + filename = get_real_filename (filename, TRUE); + gtk_file_selection_populate (fs, filename, FALSE, FALSE); + g_free (filename); } -#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN) +#ifdef G_PLATFORM_WIN32 static void -win32_gtk_add_drives_to_dir_list(GtkWidget *the_dir_list) +win32_gtk_add_drives_to_dir_list (GtkListStore *model) { - gchar *text[2], *textPtr; + gchar *textPtr; gchar buffer[128]; - char volumeNameBuf[128]; char formatBuffer[128]; - gint row; + GtkTreeIter iter; - text[1] = NULL; - - /* Get the Drives string */ - GetLogicalDriveStrings(sizeof(buffer), buffer); + /* Get the drives string */ + GetLogicalDriveStrings (sizeof (buffer), buffer); /* Add the drives as necessary */ textPtr = buffer; - while (*textPtr != '\0') { - /* Get the volume information for this drive */ - if ((tolower(textPtr[0]) != 'a') && (tolower(textPtr[0]) != 'b')) - { - /* Ignore floppies (?) */ - DWORD maxComponentLength, flags; - - GetVolumeInformation(textPtr, - volumeNameBuf, sizeof(volumeNameBuf), - NULL, &maxComponentLength, - &flags, NULL, 0); - /* Build the actual displayable string */ - - sprintf(formatBuffer, "%c:\\", toupper(textPtr[0])); -#if 0 /* HB: removed to allow drive change AND directory update with one click */ - if (strlen(volumeNameBuf) > 0) - sprintf(formatBuffer, "%s (%s)", formatBuffer, volumeNameBuf); -#endif - /* Add to the list */ - text[0] = formatBuffer; - row = gtk_clist_append (GTK_CLIST (the_dir_list), text); - } - textPtr += (strlen(textPtr) + 1); - } + while (*textPtr != '\0') + { + /* Ignore floppies (?) */ + if (GetDriveType (textPtr) != DRIVE_REMOVABLE) + { + /* Build the actual displayable string */ + g_snprintf (formatBuffer, sizeof (formatBuffer), "%c:\\", toupper (textPtr[0])); + + /* Add to the list */ + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, DIR_COLUMN, formatBuffer, -1); + } + textPtr += (strlen (textPtr) + 1); + } } #endif +static gchar * +escape_underscores (const gchar *str) +{ + GString *result = g_string_new (NULL); + while (*str) + { + if (*str == '_') + g_string_append_c (result, '_'); + + g_string_append_c (result, *str); + str++; + } + + return g_string_free (result, FALSE); +} + static void gtk_file_selection_populate (GtkFileSelection *fs, gchar *rel_path, - gint try_complete) + gboolean try_complete, + gboolean reset_entry) { CompletionState *cmpl_state; PossibleCompletion* poss; + GtkTreeIter iter; + GtkListStore *dir_model; + GtkListStore *file_model; gchar* filename; - gint row; gchar* rem_path = rel_path; gchar* sel_text; - gchar* text[2]; gint did_recurse = FALSE; gint possible_count = 0; - gint selection_index = -1; - gint file_list_width; - gint dir_list_width; - g_return_if_fail (fs != NULL); g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); - + cmpl_state = (CompletionState*) fs->cmpl_state; poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state); @@ -1497,24 +2125,17 @@ gtk_file_selection_populate (GtkFileSelection *fs, 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)); + dir_model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (fs->dir_list))); + file_model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (fs->file_list))); - /* Set the dir_list to include ./ and ../ */ - text[1] = NULL; - text[0] = "." G_DIR_SEPARATOR_S; - row = gtk_clist_append (GTK_CLIST (fs->dir_list), text); + gtk_list_store_clear (dir_model); + gtk_list_store_clear (file_model); - text[0] = ".." G_DIR_SEPARATOR_S; - 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,".." G_DIR_SEPARATOR_S); - 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); + /* Set the dir list to include ./ and ../ */ + gtk_list_store_append (dir_model, &iter); + gtk_list_store_set (dir_model, &iter, DIR_COLUMN, "." G_DIR_SEPARATOR_S, -1); + gtk_list_store_append (dir_model, &iter); + gtk_list_store_set (dir_model, &iter, DIR_COLUMN, ".." G_DIR_SEPARATOR_S, -1); while (poss) { @@ -1524,129 +2145,380 @@ gtk_file_selection_populate (GtkFileSelection *fs, filename = cmpl_this_completion (poss); - text[0] = filename; - if (cmpl_is_directory (poss)) { if (strcmp (filename, "." G_DIR_SEPARATOR_S) != 0 && strcmp (filename, ".." G_DIR_SEPARATOR_S) != 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); - } - } + gtk_list_store_append (dir_model, &iter); + gtk_list_store_set (dir_model, &iter, DIR_COLUMN, filename, -1); + } } 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_list_store_append (file_model, &iter); + gtk_list_store_set (file_model, &iter, DIR_COLUMN, filename, -1); } } poss = cmpl_next_completion (cmpl_state); } -#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN) - /* For Windows, add drives as potential selections */ - win32_gtk_add_drives_to_dir_list (fs->dir_list); -#endif +#ifdef G_PLATFORM_WIN32 + /* For Windows, add drives as potential selections */ + win32_gtk_add_drives_to_dir_list (dir_model); +#endif + + /* 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)) + { + gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state)); + + did_recurse = TRUE; + + gtk_file_selection_populate (fs, dir_name, TRUE, 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 + { + if (fs->selection_entry) + gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path); + } + } + else if (reset_entry) + { + if (fs->selection_entry) + gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), ""); + } + + if (!did_recurse) + { + if (fs->selection_entry && try_complete) + gtk_editable_set_position (GTK_EDITABLE (fs->selection_entry), -1); + + if (fs->selection_entry) + { + char *escaped = escape_underscores (cmpl_reference_position (cmpl_state)); + sel_text = g_strconcat (_("_Selection: "), escaped, NULL); + g_free (escaped); + + gtk_label_set_text_with_mnemonic (GTK_LABEL (fs->selection_text), sel_text); + g_free (sel_text); + } + + if (fs->history_pulldown) + { + gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state)); + } + + } +} + +static void +gtk_file_selection_abort (GtkFileSelection *fs) +{ + gchar err_buf[256]; + + g_snprintf (err_buf, sizeof (err_buf), _("Folder unreadable: %s"), cmpl_strerror (cmpl_errno)); + + /* BEEP gdk_beep(); */ + + if (fs->selection_entry) + gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf); +} + +/** + * gtk_file_selection_set_select_multiple: + * @filesel: a #GtkFileSelection + * @select_multiple: whether or not the user is allowed to select multiple + * files in the file list. + * + * Sets whether the user is allowed to select multiple files in the file list. + * Use gtk_file_selection_get_selections () to get the list of selected files. + **/ +void +gtk_file_selection_set_select_multiple (GtkFileSelection *filesel, + gboolean select_multiple) +{ + GtkTreeSelection *sel; + GtkSelectionMode mode; + + g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list)); + + mode = select_multiple ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE; + + if (mode != gtk_tree_selection_get_mode (sel)) + { + gtk_tree_selection_set_mode (sel, mode); + + g_object_notify (G_OBJECT (filesel), "select-multiple"); + } +} + +/** + * gtk_file_selection_get_select_multiple: + * @filesel: a #GtkFileSelection + * + * Determines whether or not the user is allowed to select multiple files in + * the file list. See gtk_file_selection_set_select_multiple(). + * + * Return value: %TRUE if the user is allowed to select multiple files in the + * file list + **/ +gboolean +gtk_file_selection_get_select_multiple (GtkFileSelection *filesel) +{ + GtkTreeSelection *sel; + + g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), FALSE); + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list)); + return (gtk_tree_selection_get_mode (sel) == GTK_SELECTION_MULTIPLE); +} + +static void +multiple_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GPtrArray *names = data; + gchar *filename; + + gtk_tree_model_get (model, iter, FILE_COLUMN, &filename, -1); + + g_ptr_array_add (names, filename); +} + +static void +free_selected_names (GPtrArray *names) +{ + gint i; + + for (i = 0; i < names->len; i++) + g_free (g_ptr_array_index (names, i)); + + g_ptr_array_free (names, TRUE); +} + +static void +gtk_file_selection_file_changed (GtkTreeSelection *selection, + gpointer user_data) +{ + GtkFileSelection *fs = GTK_FILE_SELECTION (user_data); + GPtrArray *new_names; + gchar *filename; + const gchar *entry; + gint index = -1; + + new_names = g_ptr_array_sized_new (8); + + gtk_tree_selection_selected_foreach (selection, + multiple_changed_foreach, + new_names); + + /* nothing selected */ + if (new_names->len == 0) + { + g_ptr_array_free (new_names, TRUE); + + if (fs->selected_names != NULL) + { + free_selected_names (fs->selected_names); + fs->selected_names = NULL; + } + + goto maybe_clear_entry; + } + + if (new_names->len != 1) + { + GPtrArray *old_names = fs->selected_names; + + if (old_names != NULL) + { + /* A common case is selecting a range of files from top to bottom, + * so quickly check for that to avoid looping over the entire list + */ + if (compare_utf8_filenames (g_ptr_array_index (old_names, old_names->len - 1), + g_ptr_array_index (new_names, new_names->len - 1)) != 0) + index = new_names->len - 1; + else + { + gint i = 0, j = 0, cmp; + + /* do a quick diff, stopping at the first file not in the + * old list + */ + while (i < old_names->len && j < new_names->len) + { + cmp = compare_utf8_filenames (g_ptr_array_index (old_names, i), + g_ptr_array_index (new_names, j)); + if (cmp < 0) + { + i++; + } + else if (cmp == 0) + { + i++; + j++; + } + else if (cmp > 0) + { + index = j; + break; + } + } + + /* we ran off the end of the old list */ + if (index == -1 && i < new_names->len) + index = j; + } + } + else + { + /* A phantom anchor still exists at the point where the last item + * was selected, which is used for subsequent range selections. + * So search up from there. + */ + if (fs->last_selected && + compare_utf8_filenames (fs->last_selected, + g_ptr_array_index (new_names, 0)) == 0) + index = new_names->len - 1; + else + index = 0; + } + } + else + index = 0; + + if (fs->selected_names != NULL) + free_selected_names (fs->selected_names); - gtk_clist_thaw (GTK_CLIST (fs->dir_list)); - gtk_clist_thaw (GTK_CLIST (fs->file_list)); + fs->selected_names = new_names; - /* File lists are set. */ + if (index != -1) + { + if (fs->last_selected != NULL) + g_free (fs->last_selected); - g_assert (cmpl_state->reference_dir); + fs->last_selected = g_strdup (g_ptr_array_index (new_names, index)); + filename = get_real_filename (fs->last_selected, FALSE); - if (try_complete) - { + gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); - /* 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 (filename != fs->last_selected) + g_free (filename); + + return; + } + +maybe_clear_entry: - if (cmpl_updated_text (cmpl_state)[0]) - { + entry = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)); + if ((entry != NULL) && (fs->last_selected != NULL) && + (compare_utf8_filenames (entry, fs->last_selected) == 0)) + gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), ""); +} - if (cmpl_updated_dir (cmpl_state)) - { - gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state)); +/** + * gtk_file_selection_get_selections: + * @filesel: a #GtkFileSelection + * + * Retrieves the list of file selections the user has made in the dialog box. + * This function is intended for use when the user can select multiple files + * in the file list. The first file in the list is equivalent to what + * gtk_file_selection_get_filename() would return. + * + * The filenames are in the encoding of g_filename_from_utf8(), which may or + * may not be the same as that used by GTK+ (UTF-8). To convert to UTF-8, call + * g_filename_to_utf8() on each string. + * + * Return value: a newly-allocated %NULL-terminated array of strings. Use + * g_strfreev() to free it. + **/ +gchar ** +gtk_file_selection_get_selections (GtkFileSelection *filesel) +{ + GPtrArray *names; + gchar **selections; + gchar *filename, *dirname; + gchar *current, *buf; + gint i, count; + gboolean unselected_entry; - did_recurse = TRUE; + g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), NULL); - gtk_file_selection_populate (fs, dir_name, TRUE); + filename = g_strdup (gtk_file_selection_get_filename (filesel)); - 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 (strlen (filename) == 0) { - if (fs->selection_entry) - gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), ""); + g_free (filename); + return NULL; } - if (!did_recurse) + names = filesel->selected_names; + + if (names != NULL) + selections = g_new (gchar *, names->len + 2); + else + selections = g_new (gchar *, 2); + + count = 0; + unselected_entry = TRUE; + + if (names != NULL) { - if (fs->selection_entry) - gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index); + dirname = g_path_get_dirname (filename); - if (fs->selection_entry) + for (i = 0; i < names->len; i++) { - sel_text = g_strconcat (_("Selection: "), - cmpl_reference_position (cmpl_state), - NULL); + buf = g_filename_from_utf8 (g_ptr_array_index (names, i), -1, + NULL, NULL, NULL); + current = g_build_filename (dirname, buf, NULL); + g_free (buf); - gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text); - g_free (sel_text); - } + selections[count++] = current; - if (fs->history_pulldown) - { - gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state)); + if (unselected_entry && compare_sys_filenames (current, filename) == 0) + unselected_entry = FALSE; } - - } -} -static void -gtk_file_selection_abort (GtkFileSelection *fs) -{ - gchar err_buf[256]; + g_free (dirname); + } - sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno)); + if (unselected_entry) + selections[count++] = filename; + else + g_free (filename); - /* BEEP gdk_beep(); */ + selections[count] = NULL; - if (fs->selection_entry) - gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf); + return selections; } /**********************************************************************/ @@ -1656,41 +2528,45 @@ gtk_file_selection_abort (GtkFileSelection *fs) /* The four completion state selectors */ static gchar* -cmpl_updated_text (CompletionState* cmpl_state) +cmpl_updated_text (CompletionState *cmpl_state) { return cmpl_state->updated_text; } -static gint -cmpl_updated_dir (CompletionState* cmpl_state) +static gboolean +cmpl_updated_dir (CompletionState *cmpl_state) { return cmpl_state->re_complete; } static gchar* -cmpl_reference_position (CompletionState* cmpl_state) +cmpl_reference_position (CompletionState *cmpl_state) { return cmpl_state->reference_dir->fullname; } +#if 0 +/* This doesn't work currently and would require changes + * to fnmatch.c to get working. + */ static gint -cmpl_last_valid_char (CompletionState* cmpl_state) +cmpl_last_valid_char (CompletionState *cmpl_state) { return cmpl_state->last_valid_char; } +#endif static gchar* -cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state) +cmpl_completion_fullname (const gchar *text, + CompletionState *cmpl_state) { - static char nothing[2] = ""; - if (!cmpl_state_okay (cmpl_state)) { - return nothing; + return g_strdup (""); } else if (g_path_is_absolute (text)) { - strcpy (cmpl_state->updated_text, text); + return g_strdup (text); } #ifdef HAVE_PWD_H else if (text[0] == '~') @@ -1700,33 +2576,19 @@ cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state) 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 + if (dir) { - - strcpy (cmpl_state->updated_text, dir->fullname); - slash = strchr (text, G_DIR_SEPARATOR); - - if (slash) - strcat (cmpl_state->updated_text, slash); + + /* slash may be NULL, that works too */ + return g_strconcat (dir->fullname, slash, NULL); } } #endif - else - { - strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname); - if (cmpl_state->updated_text[strlen (cmpl_state->updated_text) - 1] != G_DIR_SEPARATOR) - strcat (cmpl_state->updated_text, G_DIR_SEPARATOR_S); - strcat (cmpl_state->updated_text, text); - } - - return cmpl_state->updated_text; + + return g_build_filename (cmpl_state->reference_dir->fullname, + text, + NULL); } /* The three completion selectors @@ -1737,7 +2599,7 @@ cmpl_this_completion (PossibleCompletion* pc) return pc->text; } -static gint +static gboolean cmpl_is_directory (PossibleCompletion* pc) { return pc->is_directory; @@ -1753,15 +2615,55 @@ cmpl_is_a_completion (PossibleCompletion* pc) /* Construction, deletion */ /**********************************************************************/ +/* Get the nearest parent of the current directory for which + * we can convert the filename into UTF-8. With paranoia. + * Returns "." when all goes wrong. + */ +static gchar * +get_current_dir_utf8 (void) +{ + gchar *dir = g_get_current_dir (); + gchar *dir_utf8 = NULL; + + while (TRUE) + { + gchar *last_slash; + + dir_utf8 = g_filename_to_utf8 (dir, -1, NULL, NULL, NULL); + if (dir_utf8) + break; + + last_slash = strrchr (dir, G_DIR_SEPARATOR); + if (!last_slash) /* g_get_current_dir() wasn't absolute! */ + break; + + if (last_slash + 1 == g_path_skip_root (dir)) /* Parent directory is a root directory */ + { + if (last_slash[1] == '\0') /* Root misencoded! */ + break; + else + last_slash[1] = '\0'; + } + else + last_slash[0] = '\0'; + + g_assert (last_slash); + } + + g_free (dir); + + return dir_utf8 ? dir_utf8 : g_strdup ("."); +} + static CompletionState* cmpl_init_state (void) { - gchar *getcwd_buf; + gchar *utf8_cwd; CompletionState *new_state; new_state = g_new (CompletionState, 1); - getcwd_buf = g_get_current_dir (); + utf8_cwd = get_current_dir_utf8 (); tryagain: @@ -1778,48 +2680,52 @@ tryagain: new_state->user_dir_name_buffer = NULL; new_state->user_directories = NULL; - new_state->reference_dir = open_dir (getcwd_buf, new_state); + new_state->reference_dir = open_dir (utf8_cwd, new_state); if (!new_state->reference_dir) { /* Directories changing from underneath us, grumble */ - strcpy (getcwd_buf, G_DIR_SEPARATOR_S); + strcpy (utf8_cwd, G_DIR_SEPARATOR_S); goto tryagain; } - g_free (getcwd_buf); + g_free (utf8_cwd); return new_state; } static void -cmpl_free_dir_list(GList* dp0) +cmpl_free_dir_list (GList* dp0) { GList *dp = dp0; - while (dp) { - free_dir (dp->data); - dp = dp->next; - } + while (dp) + { + free_dir (dp->data); + dp = dp->next; + } - g_list_free(dp0); + g_list_free (dp0); } static void -cmpl_free_dir_sent_list(GList* dp0) +cmpl_free_dir_sent_list (GList* dp0) { GList *dp = dp0; - while (dp) { - free_dir_sent (dp->data); - dp = dp->next; - } + while (dp) + { + free_dir_sent (dp->data); + dp = dp->next; + } - g_list_free(dp0); + g_list_free (dp0); } static void cmpl_free_state (CompletionState* cmpl_state) { + g_return_if_fail (cmpl_state != NULL); + cmpl_free_dir_list (cmpl_state->directory_storage); cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage); @@ -1836,46 +2742,54 @@ cmpl_free_state (CompletionState* cmpl_state) } static void -free_dir(CompletionDir* dir) +free_dir (CompletionDir* dir) { - g_free(dir->fullname); - g_free(dir); + g_free (dir->cmpl_text); + g_free (dir->fullname); + g_free (dir); } static void -free_dir_sent(CompletionDirSent* sent) +free_dir_sent (CompletionDirSent* sent) { - g_free(sent->name_buffer); - g_free(sent->entries); - g_free(sent); + gint i; + for (i = 0; i < sent->entry_count; i++) + { + g_free (sent->entries[i].entry_name); + g_free (sent->entries[i].sort_key); + } + g_free (sent->entries); + g_free (sent); } static void -prune_memory_usage(CompletionState *cmpl_state) +prune_memory_usage (CompletionState *cmpl_state) { GList* cdsl = cmpl_state->directory_sent_storage; GList* cdl = cmpl_state->directory_storage; GList* cdl0 = cdl; gint len = 0; - for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1) + for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1) cdsl = cdsl->next; - if (cdsl) { - cmpl_free_dir_sent_list(cdsl->next); - cdsl->next = NULL; - } + 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; - } + 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); + g_list_free (cdl0); } /**********************************************************************/ @@ -1883,14 +2797,16 @@ prune_memory_usage(CompletionState *cmpl_state) /**********************************************************************/ static PossibleCompletion* -cmpl_completion_matches (gchar* text_to_complete, - gchar** remaining_text, - CompletionState* cmpl_state) +cmpl_completion_matches (gchar *text_to_complete, + gchar **remaining_text, + CompletionState *cmpl_state) { +#ifdef HAVE_PWD_H gchar* first_slash; +#endif PossibleCompletion *poss; - prune_memory_usage(cmpl_state); + prune_memory_usage (cmpl_state); g_assert (text_to_complete != NULL); @@ -1912,7 +2828,7 @@ cmpl_completion_matches (gchar* text_to_complete, */ poss = attempt_homedir_completion (text_to_complete, cmpl_state); - update_cmpl(poss, cmpl_state); + update_cmpl (poss, cmpl_state); return poss; } @@ -1920,7 +2836,7 @@ cmpl_completion_matches (gchar* text_to_complete, cmpl_state->reference_dir = open_ref_dir (text_to_complete, remaining_text, cmpl_state); - if(!cmpl_state->reference_dir) + if (!cmpl_state->reference_dir) return NULL; cmpl_state->completion_dir = @@ -1928,20 +2844,20 @@ cmpl_completion_matches (gchar* text_to_complete, cmpl_state->last_valid_char = *remaining_text - text_to_complete; - if(!cmpl_state->completion_dir) + if (!cmpl_state->completion_dir) return NULL; cmpl_state->completion_dir->cmpl_index = -1; cmpl_state->completion_dir->cmpl_parent = NULL; - cmpl_state->completion_dir->cmpl_text = *remaining_text; + cmpl_state->completion_dir->cmpl_text = g_strdup (*remaining_text); cmpl_state->active_completion_dir = cmpl_state->completion_dir; cmpl_state->reference_dir = cmpl_state->completion_dir; - poss = attempt_file_completion(cmpl_state); + poss = attempt_file_completion (cmpl_state); - update_cmpl(poss, cmpl_state); + update_cmpl (poss, cmpl_state); return poss; } @@ -1954,15 +2870,15 @@ cmpl_next_completion (CompletionState* cmpl_state) cmpl_state->the_completion.text[0] = 0; #ifdef HAVE_PWD_H - if(cmpl_state->user_completion_index >= 0) - poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state); + if (cmpl_state->user_completion_index >= 0) + poss = attempt_homedir_completion (cmpl_state->last_completion_text, cmpl_state); else - poss = attempt_file_completion(cmpl_state); + poss = attempt_file_completion (cmpl_state); #else - poss = attempt_file_completion(cmpl_state); + poss = attempt_file_completion (cmpl_state); #endif - update_cmpl(poss, cmpl_state); + update_cmpl (poss, cmpl_state); return poss; } @@ -1973,22 +2889,22 @@ cmpl_next_completion (CompletionState* cmpl_state) /* Open the directory where completion will begin from, if possible. */ static CompletionDir* -open_ref_dir(gchar* text_to_complete, - gchar** remaining_text, - CompletionState* cmpl_state) +open_ref_dir (gchar *text_to_complete, + gchar **remaining_text, + CompletionState *cmpl_state) { gchar* first_slash; CompletionDir *new_dir; - first_slash = strchr(text_to_complete, G_DIR_SEPARATOR); + first_slash = strchr (text_to_complete, G_DIR_SEPARATOR); #ifdef G_WITH_CYGWIN if (text_to_complete[0] == '/' && text_to_complete[1] == '/') { char root_dir[5]; - sprintf(root_dir, "//%c", text_to_complete[2]); + g_snprintf (root_dir, sizeof (root_dir), "//%c", text_to_complete[2]); - new_dir = open_dir(root_dir, cmpl_state); + new_dir = open_dir (root_dir, cmpl_state); if (new_dir) { *remaining_text = text_to_complete + 4; @@ -1998,31 +2914,17 @@ open_ref_dir(gchar* text_to_complete, if (FALSE) ; #endif - else if (g_path_is_absolute (text_to_complete) || !cmpl_state->reference_dir) - { - char *root; - int rootlen; - - rootlen = g_path_skip_root (text_to_complete) - text_to_complete; - root = g_malloc (rootlen + 1); - memcpy (root, text_to_complete, rootlen); - root[rootlen] = '\0'; - new_dir = open_dir (root, cmpl_state); - if (new_dir) - *remaining_text = g_path_skip_root (text_to_complete); - g_free (root); - } #ifdef HAVE_PWD_H else if (text_to_complete[0] == '~') { - new_dir = open_user_dir(text_to_complete, cmpl_state); + new_dir = open_user_dir (text_to_complete, cmpl_state); - if(new_dir) + if (new_dir) { - if(first_slash) + if (first_slash) *remaining_text = first_slash + 1; else - *remaining_text = text_to_complete + strlen(text_to_complete); + *remaining_text = text_to_complete + strlen (text_to_complete); } else { @@ -2030,14 +2932,52 @@ open_ref_dir(gchar* text_to_complete, } } #endif + else if (g_path_is_absolute (text_to_complete) || !cmpl_state->reference_dir) + { + gchar *tmp = g_strdup (text_to_complete); + gchar *p; + + p = tmp; + while (*p && *p != '*' && *p != '?') + p++; + + *p = '\0'; + p = strrchr (tmp, G_DIR_SEPARATOR); + if (p) + { + if (p + 1 == g_path_skip_root (tmp)) + p++; + + *p = '\0'; + new_dir = open_dir (tmp, cmpl_state); + + if (new_dir) + *remaining_text = text_to_complete + + ((p == g_path_skip_root (tmp)) ? (p - tmp) : (p + 1 - tmp)); + } + else + { + /* If no possible candidates, use the cwd */ + gchar *utf8_curdir = get_current_dir_utf8 (); + + new_dir = open_dir (utf8_curdir, cmpl_state); + + if (new_dir) + *remaining_text = text_to_complete; + + g_free (utf8_curdir); + } + + g_free (tmp); + } else { *remaining_text = text_to_complete; - new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state); + new_dir = open_dir (cmpl_state->reference_dir->fullname, cmpl_state); } - if(new_dir) + if (new_dir) { new_dir->cmpl_index = -1; new_dir->cmpl_parent = NULL; @@ -2050,195 +2990,204 @@ open_ref_dir(gchar* text_to_complete, /* open a directory by user name */ static CompletionDir* -open_user_dir(gchar* text_to_complete, - CompletionState *cmpl_state) +open_user_dir (const gchar *text_to_complete, + CompletionState *cmpl_state) { + CompletionDir *result; gchar *first_slash; gint cmp_len; - g_assert(text_to_complete && text_to_complete[0] == '~'); + g_assert (text_to_complete && text_to_complete[0] == '~'); - first_slash = strchr(text_to_complete, G_DIR_SEPARATOR); + first_slash = strchr (text_to_complete, G_DIR_SEPARATOR); if (first_slash) cmp_len = first_slash - text_to_complete - 1; else - cmp_len = strlen(text_to_complete + 1); + cmp_len = strlen (text_to_complete + 1); - if(!cmp_len) + if (!cmp_len) { /* ~/ */ - gchar *homedir = g_get_home_dir (); + const gchar *homedir = g_get_home_dir (); + gchar *utf8_homedir = g_filename_to_utf8 (homedir, -1, NULL, NULL, NULL); - if (homedir) - return open_dir(homedir, cmpl_state); + if (utf8_homedir) + result = open_dir (utf8_homedir, cmpl_state); else - return NULL; + result = NULL; + + g_free (utf8_homedir); } else { /* ~user/ */ - char* copy = g_new(char, cmp_len + 1); + gchar* copy = g_new (char, cmp_len + 1); + gchar *utf8_dir; struct passwd *pwd; - strncpy(copy, text_to_complete + 1, cmp_len); + + strncpy (copy, text_to_complete + 1, cmp_len); copy[cmp_len] = 0; - pwd = getpwnam(copy); - g_free(copy); + pwd = getpwnam (copy); + g_free (copy); if (!pwd) { cmpl_errno = errno; return NULL; } - - return open_dir(pwd->pw_dir, cmpl_state); + utf8_dir = g_filename_to_utf8 (pwd->pw_dir, -1, NULL, NULL, NULL); + result = open_dir (utf8_dir, cmpl_state); + g_free (utf8_dir); } + return result; } #endif /* open a directory relative the the current relative directory */ static CompletionDir* -open_relative_dir(gchar* dir_name, - CompletionDir* dir, - CompletionState *cmpl_state) +open_relative_dir (gchar *dir_name, + CompletionDir *dir, + CompletionState *cmpl_state) { - gchar path_buf[2*MAXPATHLEN]; + CompletionDir *result; + GString *path; - if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN) - { - cmpl_errno = CMPL_ERRNO_TOO_LONG; - return NULL; - } + path = g_string_sized_new (dir->fullname_len + strlen (dir_name) + 10); + g_string_assign (path, dir->fullname); - strcpy(path_buf, dir->fullname); + if (dir->fullname_len > 1 + && path->str[dir->fullname_len - 1] != G_DIR_SEPARATOR) + g_string_append_c (path, G_DIR_SEPARATOR); + g_string_append (path, dir_name); - if(dir->fullname_len > 1) - { - if (path_buf[dir->fullname_len - 1] != G_DIR_SEPARATOR) - { - path_buf[dir->fullname_len] = G_DIR_SEPARATOR; - strcpy (path_buf + dir->fullname_len + 1, dir_name); - } - else - strcpy (path_buf + dir->fullname_len, dir_name); - } - else - { - strcpy(path_buf + dir->fullname_len, dir_name); - } + result = open_dir (path->str, cmpl_state); - return open_dir(path_buf, cmpl_state); + g_string_free (path, TRUE); + + return result; } /* after the cache lookup fails, really open a new directory */ static CompletionDirSent* -open_new_dir(gchar* dir_name, struct stat* sbuf, gboolean stat_subdirs) +open_new_dir (gchar *dir_name, + struct stat *sbuf, + gboolean stat_subdirs) { - CompletionDirSent* sent; - DIR* directory; - gchar *buffer_ptr; - struct dirent *dirent_ptr; - gint buffer_size = 0; + CompletionDirSent *sent; + GDir *directory; + const char *dirent; + GError *error = NULL; gint entry_count = 0; + gint n_entries = 0; gint i; struct stat ent_sbuf; - char path_buf[MAXPATHLEN*2]; - gint path_buf_len; + GString *path; + gchar *sys_dir_name; - sent = g_new(CompletionDirSent, 1); + sent = g_new (CompletionDirSent, 1); +#ifndef G_PLATFORM_WIN32 sent->mtime = sbuf->st_mtime; sent->inode = sbuf->st_ino; sent->device = sbuf->st_dev; +#endif + path = g_string_sized_new (2*MAXPATHLEN + 10); - path_buf_len = strlen(dir_name); - - if (path_buf_len > MAXPATHLEN) + sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL); + if (!sys_dir_name) { - cmpl_errno = CMPL_ERRNO_TOO_LONG; + cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT; return NULL; } - - strcpy(path_buf, dir_name); - - directory = opendir(dir_name); - - if(!directory) + + directory = g_dir_open (sys_dir_name, 0, &error); + if (!directory) { - cmpl_errno = errno; + cmpl_errno = error->code; /* ??? */ + g_free (sys_dir_name); 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; - } - } + while ((dirent = g_dir_read_name (directory)) != NULL) + entry_count++; + entry_count += 2; /* For ".",".." */ - sent->name_buffer = g_new(gchar, buffer_size); - sent->entries = g_new(CompletionDirEntry, entry_count); + sent->entries = g_new (CompletionDirEntry, entry_count); sent->entry_count = entry_count; - buffer_ptr = sent->name_buffer; + g_dir_rewind (directory); - rewinddir(directory); - - for(i = 0; i < entry_count; i += 1) + for (i = 0; i < entry_count; i += 1) { - dirent_ptr = readdir(directory); + GError *error = NULL; - if(!dirent_ptr) + if (i == 0) + dirent = "."; + else if (i == 1) + dirent = ".."; + else { - cmpl_errno = errno; - closedir(directory); - return NULL; + dirent = g_dir_read_name (directory); + if (!dirent) /* Directory changed */ + break; } - 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; - - if (path_buf[path_buf_len-1] != G_DIR_SEPARATOR) + sent->entries[n_entries].entry_name = g_filename_to_utf8 (dirent, -1, NULL, NULL, &error); + if (sent->entries[n_entries].entry_name == NULL + || !g_utf8_validate (sent->entries[n_entries].entry_name, -1, NULL)) { - path_buf[path_buf_len] = G_DIR_SEPARATOR; - strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name); + gchar *escaped_str = g_strescape (dirent, NULL); + g_message (_("The filename \"%s\" couldn't be converted to UTF-8. " + "(try setting the environment variable G_FILENAME_ENCODING): %s"), + escaped_str, + error->message ? error->message : _("Invalid UTF-8")); + g_free (escaped_str); + g_clear_error (&error); + continue; } - else - strcpy(path_buf + path_buf_len, dirent_ptr->d_name); + g_clear_error (&error); + + sent->entries[n_entries].sort_key = g_utf8_collate_key (sent->entries[n_entries].entry_name, -1); + + g_string_assign (path, sys_dir_name); + if (path->str[path->len-1] != G_DIR_SEPARATOR) + { + g_string_append_c (path, G_DIR_SEPARATOR); + } + g_string_append (path, dirent); if (stat_subdirs) { - if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode)) - sent->entries[i].is_dir = 1; + /* Here we know path->str is a "system charset" string */ + if (stat (path->str, &ent_sbuf) >= 0 && S_ISDIR (ent_sbuf.st_mode)) + sent->entries[n_entries].is_dir = TRUE; else /* stat may fail, and we don't mind, since it could be a * dangling symlink. */ - sent->entries[i].is_dir = 0; + sent->entries[n_entries].is_dir = FALSE; } else - sent->entries[i].is_dir = 1; - } + sent->entries[n_entries].is_dir = 1; - qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir); + n_entries++; + } + sent->entry_count = n_entries; + + g_free (sys_dir_name); + g_string_free (path, TRUE); + qsort (sent->entries, sent->entry_count, sizeof (CompletionDirEntry), compare_cmpl_dir); - closedir(directory); + g_dir_close (directory); return sent; } -#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN) +#ifndef G_PLATFORM_WIN32 static gboolean -check_dir(gchar *dir_name, struct stat *result, gboolean *stat_subdirs) +check_dir (gchar *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 @@ -2254,9 +3203,9 @@ check_dir(gchar *dir_name, struct stat *result, gboolean *stat_subdirs) { "/net", FALSE, { 0 } } }; - static const gint n_no_stat_dirs = sizeof(no_stat_dirs) / sizeof(no_stat_dirs[0]); + static const gint n_no_stat_dirs = G_N_ELEMENTS (no_stat_dirs); static gboolean initialized = FALSE; - + gchar *sys_dir_name; gint i; if (!initialized) @@ -2269,14 +3218,23 @@ check_dir(gchar *dir_name, struct stat *result, gboolean *stat_subdirs) } } - if(stat(dir_name, result) < 0) + sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL); + if (!sys_dir_name) + { + cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT; + return FALSE; + } + + if (stat (sys_dir_name, result) < 0) { + g_free (sys_dir_name); cmpl_errno = errno; return FALSE; } + g_free (sys_dir_name); *stat_subdirs = TRUE; - for (i=0; ist_dev) && @@ -2294,14 +3252,17 @@ check_dir(gchar *dir_name, struct stat *result, gboolean *stat_subdirs) /* open a directory by absolute pathname */ static CompletionDir* -open_dir(gchar* dir_name, CompletionState* cmpl_state) +open_dir (gchar *dir_name, + CompletionState *cmpl_state) { +#ifndef G_PLATFORM_WIN32 struct stat sbuf; gboolean stat_subdirs; - CompletionDirSent *sent; GList* cdsl; +#endif + CompletionDirSent *sent; -#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN) +#ifndef G_PLATFORM_WIN32 if (!check_dir (dir_name, &sbuf, &stat_subdirs)) return NULL; @@ -2311,56 +3272,61 @@ open_dir(gchar* dir_name, CompletionState* cmpl_state) { sent = cdsl->data; - if(sent->inode == sbuf.st_ino && - sent->mtime == sbuf.st_mtime && - sent->device == sbuf.st_dev) - return attach_dir(sent, dir_name, cmpl_state); + if (sent->inode == sbuf.st_ino && + sent->mtime == sbuf.st_mtime && + sent->device == sbuf.st_dev) + return attach_dir (sent, dir_name, cmpl_state); cdsl = cdsl->next; } + + sent = open_new_dir (dir_name, &sbuf, stat_subdirs); #else - stat_subdirs = TRUE; + sent = open_new_dir (dir_name, NULL, TRUE); #endif - 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); + if (sent) + { + cmpl_state->directory_sent_storage = + g_list_prepend (cmpl_state->directory_sent_storage, sent); - return attach_dir(sent, dir_name, cmpl_state); - } + return attach_dir (sent, dir_name, cmpl_state); + } return NULL; } static CompletionDir* -attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state) +attach_dir (CompletionDirSent *sent, + gchar *dir_name, + CompletionState *cmpl_state) { CompletionDir* new_dir; - new_dir = g_new(CompletionDir, 1); + new_dir = g_new (CompletionDir, 1); cmpl_state->directory_storage = - g_list_prepend(cmpl_state->directory_storage, new_dir); + 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); + new_dir->fullname = g_strdup (dir_name); + new_dir->fullname_len = strlen (dir_name); + new_dir->cmpl_text = NULL; return new_dir; } static gint -correct_dir_fullname(CompletionDir* cmpl_dir) +correct_dir_fullname (CompletionDir* cmpl_dir) { - gint length = strlen(cmpl_dir->fullname); - gchar *first_slash = strchr(cmpl_dir->fullname, G_DIR_SEPARATOR); + gint length = strlen (cmpl_dir->fullname); + gchar *first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR); + gchar *sys_filename; struct stat sbuf; /* Does it end with /. (\.) ? */ if (length >= 2 && - strcmp(cmpl_dir->fullname + length - 2, G_DIR_SEPARATOR_S ".") == 0) + strcmp (cmpl_dir->fullname + length - 2, G_DIR_SEPARATOR_S ".") == 0) { /* Is it just the root directory (on a drive) ? */ if (cmpl_dir->fullname + length - 2 == first_slash) @@ -2377,77 +3343,99 @@ correct_dir_fullname(CompletionDir* cmpl_dir) /* Ends with /./ (\.\)? */ else if (length >= 3 && - strcmp(cmpl_dir->fullname + length - 3, - G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) == 0) + strcmp (cmpl_dir->fullname + length - 3, + G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) == 0) cmpl_dir->fullname[length - 2] = 0; /* Ends with /.. (\..) ? */ else if (length >= 3 && - strcmp(cmpl_dir->fullname + length - 3, - G_DIR_SEPARATOR_S "..") == 0) + strcmp (cmpl_dir->fullname + length - 3, + G_DIR_SEPARATOR_S "..") == 0) { /* Is it just /.. (X:\..)? */ - if(cmpl_dir->fullname + length - 3 == first_slash) + if (cmpl_dir->fullname + length - 3 == first_slash) { cmpl_dir->fullname[length - 2] = 0; cmpl_dir->fullname_len = length - 2; return TRUE; } - if(stat(cmpl_dir->fullname, &sbuf) < 0) + sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL); + if (!sys_filename) + { + cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT; + return FALSE; + } + + if (stat (sys_filename, &sbuf) < 0) { + g_free (sys_filename); cmpl_errno = errno; return FALSE; } + g_free (sys_filename); cmpl_dir->fullname[length - 3] = 0; - if(!correct_parent(cmpl_dir, &sbuf)) + if (!correct_parent (cmpl_dir, &sbuf)) return FALSE; } /* Ends with /../ (\..\)? */ else if (length >= 4 && - strcmp(cmpl_dir->fullname + length - 4, - G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) == 0) + strcmp (cmpl_dir->fullname + length - 4, + G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) == 0) { /* Is it just /../ (X:\..\)? */ - if(cmpl_dir->fullname + length - 4 == first_slash) + if (cmpl_dir->fullname + length - 4 == first_slash) { cmpl_dir->fullname[length - 3] = 0; cmpl_dir->fullname_len = length - 3; return TRUE; } - if(stat(cmpl_dir->fullname, &sbuf) < 0) + sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL); + if (!sys_filename) + { + cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT; + return FALSE; + } + + if (stat (sys_filename, &sbuf) < 0) { + g_free (sys_filename); cmpl_errno = errno; return FALSE; } + g_free (sys_filename); cmpl_dir->fullname[length - 4] = 0; - if(!correct_parent(cmpl_dir, &sbuf)) + if (!correct_parent (cmpl_dir, &sbuf)) return FALSE; } - cmpl_dir->fullname_len = strlen(cmpl_dir->fullname); + cmpl_dir->fullname_len = strlen (cmpl_dir->fullname); return TRUE; } static gint -correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf) +correct_parent (CompletionDir *cmpl_dir, + struct stat *sbuf) { struct stat parbuf; gchar *last_slash; gchar *first_slash; +#ifndef G_PLATFORM_WIN32 gchar *new_name; +#endif + gchar *sys_filename; gchar c = 0; - last_slash = strrchr(cmpl_dir->fullname, G_DIR_SEPARATOR); - g_assert(last_slash); - first_slash = strchr(cmpl_dir->fullname, G_DIR_SEPARATOR); + last_slash = strrchr (cmpl_dir->fullname, G_DIR_SEPARATOR); + g_assert (last_slash); + first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR); /* Clever (?) way to check for top-level directory that works also on * Win32, where there is a drive letter and colon prefixed... @@ -2462,32 +3450,43 @@ correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf) last_slash[1] = 0; } - if (stat(cmpl_dir->fullname, &parbuf) < 0) + sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL); + if (!sys_filename) + { + cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT; + if (!c) + last_slash[0] = G_DIR_SEPARATOR; + return FALSE; + } + + if (stat (sys_filename, &parbuf) < 0) { + g_free (sys_filename); cmpl_errno = errno; if (!c) last_slash[0] = G_DIR_SEPARATOR; return FALSE; } + g_free (sys_filename); -#ifndef G_OS_WIN32 /* No inode numbers on Win32 */ +#ifndef G_PLATFORM_WIN32 /* No inode numbers on Win32 */ if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev) /* it wasn't a link */ return TRUE; - if(c) + if (c) last_slash[1] = c; else last_slash[0] = G_DIR_SEPARATOR; /* it was a link, have to figure it out the hard way */ - new_name = find_parent_dir_fullname(cmpl_dir->fullname); + new_name = find_parent_dir_fullname (cmpl_dir->fullname); if (!new_name) return FALSE; - g_free(cmpl_dir->fullname); + g_free (cmpl_dir->fullname); cmpl_dir->fullname = new_name; #endif @@ -2495,31 +3494,47 @@ correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf) return TRUE; } -#ifndef G_OS_WIN32 +#ifndef G_PLATFORM_WIN32 static gchar* -find_parent_dir_fullname(gchar* dirname) +find_parent_dir_fullname (gchar* dirname) { - gchar *orig_dir; + gchar *sys_orig_dir; gchar *result; + gchar *sys_cwd; + gchar *sys_dirname; - orig_dir = g_get_current_dir (); - - if(chdir(dirname) != 0 || chdir("..") != 0) + sys_orig_dir = g_get_current_dir (); + sys_dirname = g_filename_from_utf8 (dirname, -1, NULL, NULL, NULL); + if (!sys_dirname) + { + g_free (sys_orig_dir); + cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT; + return NULL; + } + + if (chdir (sys_dirname) != 0 || chdir ("..") != 0) { cmpl_errno = errno; + chdir (sys_orig_dir); + g_free (sys_dirname); + g_free (sys_orig_dir); return NULL; } + g_free (sys_dirname); - result = g_get_current_dir (); + sys_cwd = g_get_current_dir (); + result = g_filename_to_utf8 (sys_cwd, -1, NULL, NULL, NULL); + g_free (sys_cwd); - if(chdir(orig_dir) != 0) + if (chdir (sys_orig_dir) != 0) { cmpl_errno = errno; + g_free (sys_orig_dir); return NULL; } - g_free (orig_dir); + g_free (sys_orig_dir); return result; } @@ -2532,49 +3547,49 @@ find_parent_dir_fullname(gchar* dirname) #ifdef HAVE_PWD_H static PossibleCompletion* -attempt_homedir_completion(gchar* text_to_complete, - CompletionState *cmpl_state) +attempt_homedir_completion (gchar *text_to_complete, + CompletionState *cmpl_state) { gint index, length; if (!cmpl_state->user_dir_name_buffer && - !get_pwdb(cmpl_state)) + !get_pwdb (cmpl_state)) return NULL; - length = strlen(text_to_complete) - 1; + length = strlen (text_to_complete) - 1; cmpl_state->user_completion_index += 1; - while(cmpl_state->user_completion_index < cmpl_state->user_directories_len) + while (cmpl_state->user_completion_index < cmpl_state->user_directories_len) { - index = first_diff_index(text_to_complete + 1, - cmpl_state->user_directories - [cmpl_state->user_completion_index].login); + index = first_diff_index (text_to_complete + 1, + cmpl_state->user_directories + [cmpl_state->user_completion_index].login); - switch(index) + switch (index) { case PATTERN_MATCH: break; default: - if(cmpl_state->last_valid_char < (index + 1)) + if (cmpl_state->last_valid_char < (index + 1)) cmpl_state->last_valid_char = index + 1; cmpl_state->user_completion_index += 1; continue; } cmpl_state->the_completion.is_a_completion = 1; - cmpl_state->the_completion.is_directory = 1; + cmpl_state->the_completion.is_directory = TRUE; - append_completion_text("~", cmpl_state); + append_completion_text ("~", cmpl_state); - append_completion_text(cmpl_state-> + append_completion_text (cmpl_state-> user_directories[cmpl_state->user_completion_index].login, - cmpl_state); + cmpl_state); - return append_completion_text(G_DIR_SEPARATOR_S, cmpl_state); + return append_completion_text (G_DIR_SEPARATOR_S, cmpl_state); } - if(text_to_complete[1] || - cmpl_state->user_completion_index > cmpl_state->user_directories_len) + if (text_to_complete[1] + || cmpl_state->user_completion_index > cmpl_state->user_directories_len) { cmpl_state->user_completion_index = -1; return NULL; @@ -2583,15 +3598,20 @@ attempt_homedir_completion(gchar* text_to_complete, { cmpl_state->user_completion_index += 1; cmpl_state->the_completion.is_a_completion = 1; - cmpl_state->the_completion.is_directory = 1; + cmpl_state->the_completion.is_directory = TRUE; - return append_completion_text("~" G_DIR_SEPARATOR_S, cmpl_state); + return append_completion_text ("~" G_DIR_SEPARATOR_S, cmpl_state); } } #endif -#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN) +#ifdef G_PLATFORM_WIN32 +/* FIXME: determine whether we should casefold all Unicode letters + * here, too (and in in first_diff_index() walk through the strings with + * g_utf8_next_char()), or if this folding isn't actually needed at + * all. + */ #define FOLD(c) (tolower(c)) #else #define FOLD(c) (c) @@ -2600,65 +3620,68 @@ attempt_homedir_completion(gchar* text_to_complete, /* returns the index (>= 0) of the first differing character, * PATTERN_MATCH if the completion matches */ static gint -first_diff_index(gchar* pat, gchar* text) +first_diff_index (gchar *pat, + gchar *text) { gint diff = 0; - while(*pat && *text && FOLD(*text) == FOLD(*pat)) + while (*pat && *text && FOLD (*text) == FOLD (*pat)) { pat += 1; text += 1; diff += 1; } - if(*pat) + if (*pat) return diff; return PATTERN_MATCH; } static PossibleCompletion* -append_completion_text(gchar* text, CompletionState* cmpl_state) +append_completion_text (gchar *text, + CompletionState *cmpl_state) { gint len, i = 1; - if(!cmpl_state->the_completion.text) + if (!cmpl_state->the_completion.text) return NULL; - len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1; + len = strlen (text) + strlen (cmpl_state->the_completion.text) + 1; - if(cmpl_state->the_completion.text_alloc > len) + if (cmpl_state->the_completion.text_alloc > len) { - strcat(cmpl_state->the_completion.text, text); + strcat (cmpl_state->the_completion.text, text); return &cmpl_state->the_completion; } - while(i < len) { i <<= 1; } + while (i < len) + i <<= 1; cmpl_state->the_completion.text_alloc = i; - cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i); + cmpl_state->the_completion.text = (gchar*) g_realloc (cmpl_state->the_completion.text, i); - if(!cmpl_state->the_completion.text) + if (!cmpl_state->the_completion.text) return NULL; else { - strcat(cmpl_state->the_completion.text, text); + strcat (cmpl_state->the_completion.text, text); return &cmpl_state->the_completion; } } static CompletionDir* -find_completion_dir(gchar* text_to_complete, - gchar** remaining_text, - CompletionState* cmpl_state) +find_completion_dir (gchar *text_to_complete, + gchar **remaining_text, + CompletionState *cmpl_state) { - gchar* first_slash = strchr(text_to_complete, G_DIR_SEPARATOR); + gchar* first_slash = strchr (text_to_complete, G_DIR_SEPARATOR); CompletionDir* dir = cmpl_state->reference_dir; CompletionDir* next; *remaining_text = text_to_complete; - while(first_slash) + while (first_slash) { gint len = first_slash - *remaining_text; gint found = 0; @@ -2666,16 +3689,15 @@ find_completion_dir(gchar* text_to_complete, gint i; gchar* pat_buf = g_new (gchar, len + 1); - strncpy(pat_buf, *remaining_text, len); + strncpy (pat_buf, *remaining_text, len); pat_buf[len] = 0; - for(i = 0; i < dir->sent->entry_count; i += 1) + 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 (dir->sent->entries[i].is_dir && + _gtk_fnmatch (pat_buf, dir->sent->entries[i].entry_name, TRUE)) { - if(found) + if (found) { g_free (pat_buf); return dir; @@ -2694,26 +3716,26 @@ find_completion_dir(gchar* text_to_complete, found_name = pat_buf; } - next = open_relative_dir(found_name, dir, cmpl_state); + next = open_relative_dir (found_name, dir, cmpl_state); - if(!next) + if (!next) { g_free (pat_buf); return NULL; - } +} next->cmpl_parent = dir; dir = next; - if(!correct_dir_fullname(dir)) + if (!correct_dir_fullname (dir)) { - g_free(pat_buf); + g_free (pat_buf); return NULL; } *remaining_text = first_slash + 1; - first_slash = strchr(*remaining_text, G_DIR_SEPARATOR); + first_slash = strchr (*remaining_text, G_DIR_SEPARATOR); g_free (pat_buf); } @@ -2722,46 +3744,47 @@ find_completion_dir(gchar* text_to_complete, } static void -update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state) +update_cmpl (PossibleCompletion *poss, + CompletionState *cmpl_state) { gint cmpl_len; - if(!poss || !cmpl_is_a_completion(poss)) + if (!poss || !cmpl_is_a_completion (poss)) return; - cmpl_len = strlen(cmpl_this_completion(poss)); + cmpl_len = strlen (cmpl_this_completion (poss)); - if(cmpl_state->updated_text_alloc < cmpl_len + 1) + if (cmpl_state->updated_text_alloc < cmpl_len + 1) { - cmpl_state->updated_text = - (gchar*)g_realloc(cmpl_state->updated_text, - cmpl_state->updated_text_alloc); cmpl_state->updated_text_alloc = 2*cmpl_len; + cmpl_state->updated_text = + (gchar*)g_realloc (cmpl_state->updated_text, + cmpl_state->updated_text_alloc); } - if(cmpl_state->updated_text_len < 0) + if (cmpl_state->updated_text_len < 0) { - strcpy(cmpl_state->updated_text, cmpl_this_completion(poss)); + strcpy (cmpl_state->updated_text, cmpl_this_completion (poss)); cmpl_state->updated_text_len = cmpl_len; - cmpl_state->re_complete = cmpl_is_directory(poss); + cmpl_state->re_complete = cmpl_is_directory (poss); } - else if(cmpl_state->updated_text_len == 0) + else if (cmpl_state->updated_text_len == 0) { cmpl_state->re_complete = FALSE; } else { gint first_diff = - first_diff_index(cmpl_state->updated_text, - cmpl_this_completion(poss)); + first_diff_index (cmpl_state->updated_text, + cmpl_this_completion (poss)); cmpl_state->re_complete = FALSE; - if(first_diff == PATTERN_MATCH) + if (first_diff == PATTERN_MATCH) return; - if(first_diff > cmpl_state->updated_text_len) - strcpy(cmpl_state->updated_text, cmpl_this_completion(poss)); + if (first_diff > cmpl_state->updated_text_len) + strcpy (cmpl_state->updated_text, cmpl_this_completion (poss)); cmpl_state->updated_text_len = first_diff; cmpl_state->updated_text[first_diff] = 0; @@ -2769,16 +3792,16 @@ update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state) } static PossibleCompletion* -attempt_file_completion(CompletionState *cmpl_state) +attempt_file_completion (CompletionState *cmpl_state) { gchar *pat_buf, *first_slash; CompletionDir *dir = cmpl_state->active_completion_dir; dir->cmpl_index += 1; - if(dir->cmpl_index == dir->sent->entry_count) + if (dir->cmpl_index == dir->sent->entry_count) { - if(dir->cmpl_parent == NULL) + if (dir->cmpl_parent == NULL) { cmpl_state->active_completion_dir = NULL; @@ -2788,49 +3811,48 @@ attempt_file_completion(CompletionState *cmpl_state) { cmpl_state->active_completion_dir = dir->cmpl_parent; - return attempt_file_completion(cmpl_state); + return attempt_file_completion (cmpl_state); } } - g_assert(dir->cmpl_text); + g_assert (dir->cmpl_text); - first_slash = strchr(dir->cmpl_text, G_DIR_SEPARATOR); + first_slash = strchr (dir->cmpl_text, G_DIR_SEPARATOR); - if(first_slash) + if (first_slash) { gint len = first_slash - dir->cmpl_text; pat_buf = g_new (gchar, len + 1); - strncpy(pat_buf, dir->cmpl_text, len); + strncpy (pat_buf, dir->cmpl_text, len); pat_buf[len] = 0; } else { - gint len = strlen(dir->cmpl_text); + gint len = strlen (dir->cmpl_text); pat_buf = g_new (gchar, len + 2); - strcpy(pat_buf, dir->cmpl_text); + strcpy (pat_buf, dir->cmpl_text); /* Don't append a * if the user entered one herself. * This way one can complete *.h and don't get matches * on any .help files, for instance. */ - if (strchr(pat_buf, '*') == NULL) - strcpy(pat_buf + len, "*"); + if (strchr (pat_buf, '*') == NULL) + strcpy (pat_buf + len, "*"); } - if(first_slash) + if (first_slash) { - if(dir->sent->entries[dir->cmpl_index].is_dir) + 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) + if (_gtk_fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name, TRUE)) { CompletionDir* new_dir; - new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name, - dir, cmpl_state); + new_dir = open_relative_dir (dir->sent->entries[dir->cmpl_index].entry_name, + dir, cmpl_state); - if(!new_dir) + if (!new_dir) { g_free (pat_buf); return NULL; @@ -2839,44 +3861,43 @@ attempt_file_completion(CompletionState *cmpl_state) new_dir->cmpl_parent = dir; new_dir->cmpl_index = -1; - new_dir->cmpl_text = first_slash + 1; + new_dir->cmpl_text = g_strdup (first_slash + 1); cmpl_state->active_completion_dir = new_dir; g_free (pat_buf); - return attempt_file_completion(cmpl_state); + return attempt_file_completion (cmpl_state); } else { g_free (pat_buf); - return attempt_file_completion(cmpl_state); + return attempt_file_completion (cmpl_state); } } else { g_free (pat_buf); - return attempt_file_completion(cmpl_state); + return attempt_file_completion (cmpl_state); } } else { - if(dir->cmpl_parent != NULL) + if (dir->cmpl_parent != NULL) { - append_completion_text(dir->fullname + - strlen(cmpl_state->completion_dir->fullname) + 1, - cmpl_state); - append_completion_text(G_DIR_SEPARATOR_S, cmpl_state); + append_completion_text (dir->fullname + + strlen (cmpl_state->completion_dir->fullname) + 1, + cmpl_state); + append_completion_text (G_DIR_SEPARATOR_S, cmpl_state); } - append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state); + append_completion_text (dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state); cmpl_state->the_completion.is_a_completion = - (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name, - FNMATCH_FLAGS) != FNM_NOMATCH); + _gtk_fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name, TRUE); cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir; - if(dir->sent->entries[dir->cmpl_index].is_dir) - append_completion_text(G_DIR_SEPARATOR_S, cmpl_state); + if (dir->sent->entries[dir->cmpl_index].is_dir) + append_completion_text (G_DIR_SEPARATOR_S, cmpl_state); g_free (pat_buf); return &cmpl_state->the_completion; @@ -2886,66 +3907,80 @@ attempt_file_completion(CompletionState *cmpl_state) #ifdef HAVE_PWD_H static gint -get_pwdb(CompletionState* cmpl_state) +get_pwdb (CompletionState* cmpl_state) { struct passwd *pwd_ptr; gchar* buf_ptr; + gchar *utf8; gint len = 0, i, count = 0; - if(cmpl_state->user_dir_name_buffer) + if (cmpl_state->user_dir_name_buffer) return TRUE; setpwent (); - while ((pwd_ptr = getpwent()) != NULL) + while ((pwd_ptr = getpwent ()) != NULL) { - len += strlen(pwd_ptr->pw_name); - len += strlen(pwd_ptr->pw_dir); + utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL); + len += strlen (utf8); + g_free (utf8); + utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL); + len += strlen (utf8); + g_free (utf8); len += 2; count += 1; } setpwent (); - cmpl_state->user_dir_name_buffer = g_new(gchar, len); - cmpl_state->user_directories = g_new(CompletionUserDir, count); + cmpl_state->user_dir_name_buffer = g_new (gchar, len); + cmpl_state->user_directories = g_new (CompletionUserDir, count); cmpl_state->user_directories_len = count; buf_ptr = cmpl_state->user_dir_name_buffer; - for(i = 0; i < count; i += 1) + for (i = 0; i < count; i += 1) { - pwd_ptr = getpwent(); - if(!pwd_ptr) + pwd_ptr = getpwent (); + if (!pwd_ptr) { cmpl_errno = errno; goto error; } - strcpy(buf_ptr, pwd_ptr->pw_name); + utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL); + strcpy (buf_ptr, utf8); + g_free (utf8); + cmpl_state->user_directories[i].login = buf_ptr; - buf_ptr += strlen(buf_ptr); + + buf_ptr += strlen (buf_ptr); buf_ptr += 1; - strcpy(buf_ptr, pwd_ptr->pw_dir); + + utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL); + strcpy (buf_ptr, utf8); + g_free (utf8); + cmpl_state->user_directories[i].homedir = buf_ptr; - buf_ptr += strlen(buf_ptr); + + buf_ptr += strlen (buf_ptr); buf_ptr += 1; } - qsort(cmpl_state->user_directories, - cmpl_state->user_directories_len, - sizeof(CompletionUserDir), - compare_user_dir); + qsort (cmpl_state->user_directories, + cmpl_state->user_directories_len, + sizeof (CompletionUserDir), + compare_user_dir); - endpwent(); + 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); + 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; @@ -2954,37 +3989,37 @@ error: } static gint -compare_user_dir(const void* a, const void* b) +compare_user_dir (const void *a, + const void *b) { - return strcmp((((CompletionUserDir*)a))->login, - (((CompletionUserDir*)b))->login); + return strcmp ((((CompletionUserDir*)a))->login, + (((CompletionUserDir*)b))->login); } #endif static gint -compare_cmpl_dir(const void* a, const void* b) +compare_cmpl_dir (const void *a, + const void *b) { -#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN) - return strcmp((((CompletionDirEntry*)a))->entry_name, - (((CompletionDirEntry*)b))->entry_name); -#else - return g_strcasecmp((((CompletionDirEntry*)a))->entry_name, - (((CompletionDirEntry*)b))->entry_name); -#endif + + return strcmp (((CompletionDirEntry*)a)->sort_key, + (((CompletionDirEntry*)b))->sort_key); } static gint -cmpl_state_okay(CompletionState* cmpl_state) +cmpl_state_okay (CompletionState* cmpl_state) { return cmpl_state && cmpl_state->reference_dir; } -static gchar* -cmpl_strerror(gint err) +static const gchar* +cmpl_strerror (gint err) { - if(err == CMPL_ERRNO_TOO_LONG) - return "Name too long"; + if (err == CMPL_ERRNO_TOO_LONG) + return _("Name too long"); + else if (err == CMPL_ERRNO_DID_NOT_CONVERT) + return _("Couldn't convert filename"); else return g_strerror (err); }