]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilesel.c
f2107d9acafb552a2c2bb4ba2b6f5ee431224fc3
[~andy/gtk] / gtk / gtkfilesel.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include "config.h"
28
29 #include <stdio.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #ifdef HAVE_SYS_PARAM_H
33 #include <sys/param.h>
34 #endif
35 #ifdef HAVE_DIRENT_H
36 #include <dirent.h>
37 #endif
38 #include <stdlib.h>
39 #ifdef HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif
42 #include <string.h>
43 #include <errno.h>
44 #ifdef HAVE_PWD_H
45 #include <pwd.h>
46 #endif
47
48 #include "fnmatch.h"
49
50 #include "gdk/gdkkeysyms.h"
51 #include "gtkbutton.h"
52 #include "gtkentry.h"
53 #include "gtkfilesel.h"
54 #include "gtkhbox.h"
55 #include "gtkhbbox.h"
56 #include "gtklabel.h"
57 #include "gtklist.h"
58 #include "gtklistitem.h"
59 #include "gtkmain.h"
60 #include "gtkscrolledwindow.h"
61 #include "gtkstock.h"
62 #include "gtksignal.h"
63 #include "gtkvbox.h"
64 #include "gtkmenu.h"
65 #include "gtkmenuitem.h"
66 #include "gtkoptionmenu.h"
67 #include "gtkclist.h"
68 #include "gtkdialog.h"
69 #include "gtkmessagedialog.h"
70 #include "gtkintl.h"
71
72 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
73 #define STRICT
74 #include <windows.h>
75
76 #ifdef G_OS_WIN32
77 #include <direct.h>
78 #include <io.h>
79 #define mkdir(p,m) _mkdir(p)
80 #ifndef S_ISDIR
81 #define S_ISDIR(mode) ((mode)&_S_IFDIR)
82 #endif
83
84 #endif /* G_OS_WIN32 */
85
86 #endif /* G_OS_WIN32 || G_WITH_CYGWIN */
87
88 #define DIR_LIST_WIDTH   180
89 #define DIR_LIST_HEIGHT  180
90 #define FILE_LIST_WIDTH  180
91 #define FILE_LIST_HEIGHT 180
92
93 /* The Hurd doesn't define either PATH_MAX or MAXPATHLEN, so we put this
94  * in here, since the rest of the code in the file does require some
95  * fixed maximum.
96  */
97 #ifndef MAXPATHLEN
98 #  ifdef PATH_MAX
99 #    define MAXPATHLEN PATH_MAX
100 #  else
101 #    define MAXPATHLEN 2048
102 #  endif
103 #endif
104
105 /* I've put this here so it doesn't get confused with the 
106  * file completion interface */
107 typedef struct _HistoryCallbackArg HistoryCallbackArg;
108
109 struct _HistoryCallbackArg
110 {
111   gchar *directory;
112   GtkWidget *menu_item;
113 };
114
115
116 typedef struct _CompletionState    CompletionState;
117 typedef struct _CompletionDir      CompletionDir;
118 typedef struct _CompletionDirSent  CompletionDirSent;
119 typedef struct _CompletionDirEntry CompletionDirEntry;
120 typedef struct _CompletionUserDir  CompletionUserDir;
121 typedef struct _PossibleCompletion PossibleCompletion;
122
123 /* Non-external file completion decls and structures */
124
125 /* A contant telling PRCS how many directories to cache.  Its actually
126  * kept in a list, so the geometry isn't important. */
127 #define CMPL_DIRECTORY_CACHE_SIZE 10
128
129 /* A constant used to determine whether a substring was an exact
130  * match by first_diff_index()
131  */
132 #define PATTERN_MATCH -1
133 /* The arguments used by all fnmatch() calls below
134  */
135 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
136
137 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
138 #define CMPL_ERRNO_DID_NOT_CONVERT ((1<<16)-2)
139
140 /* This structure contains all the useful information about a directory
141  * for the purposes of filename completion.  These structures are cached
142  * in the CompletionState struct.  CompletionDir's are reference counted.
143  */
144 struct _CompletionDirSent
145 {
146   ino_t inode;
147   time_t mtime;
148   dev_t device;
149
150   gint entry_count;
151   struct _CompletionDirEntry *entries;
152 };
153
154 struct _CompletionDir
155 {
156   CompletionDirSent *sent;
157
158   gchar *fullname;
159   gint fullname_len;
160
161   struct _CompletionDir *cmpl_parent;
162   gint cmpl_index;
163   gchar *cmpl_text;
164 };
165
166 /* This structure contains pairs of directory entry names with a flag saying
167  * whether or not they are a valid directory.  NOTE: This information is used
168  * to provide the caller with information about whether to update its completions
169  * or try to open a file.  Since directories are cached by the directory mtime,
170  * a symlink which points to an invalid file (which will not be a directory),
171  * will not be reevaluated if that file is created, unless the containing
172  * directory is touched.  I consider this case to be worth ignoring (josh).
173  */
174 struct _CompletionDirEntry
175 {
176   gboolean is_dir;
177   gchar *entry_name;
178 };
179
180 struct _CompletionUserDir
181 {
182   gchar *login;
183   gchar *homedir;
184 };
185
186 struct _PossibleCompletion
187 {
188   /* accessible fields, all are accessed externally by functions
189    * declared above
190    */
191   gchar *text;
192   gint is_a_completion;
193   gboolean is_directory;
194
195   /* Private fields
196    */
197   gint text_alloc;
198 };
199
200 struct _CompletionState
201 {
202   gint last_valid_char;
203   gchar *updated_text;
204   gint updated_text_len;
205   gint updated_text_alloc;
206   gboolean re_complete;
207
208   gchar *user_dir_name_buffer;
209   gint user_directories_len;
210
211   gchar *last_completion_text;
212
213   gint user_completion_index; /* if >= 0, currently completing ~user */
214
215   struct _CompletionDir *completion_dir; /* directory completing from */
216   struct _CompletionDir *active_completion_dir;
217
218   struct _PossibleCompletion the_completion;
219
220   struct _CompletionDir *reference_dir; /* initial directory */
221
222   GList* directory_storage;
223   GList* directory_sent_storage;
224
225   struct _CompletionUserDir *user_directories;
226 };
227
228 enum {
229   PROP_0,
230   PROP_SHOW_FILEOPS,
231   PROP_FILENAME
232 };
233
234 /* File completion functions which would be external, were they used
235  * outside of this file.
236  */
237
238 static CompletionState*    cmpl_init_state        (void);
239 static void                cmpl_free_state        (CompletionState *cmpl_state);
240 static gint                cmpl_state_okay        (CompletionState* cmpl_state);
241 static gchar*              cmpl_strerror          (gint);
242
243 static PossibleCompletion* cmpl_completion_matches(gchar           *text_to_complete,
244                                                    gchar          **remaining_text,
245                                                    CompletionState *cmpl_state);
246
247 /* Returns a name for consideration, possibly a completion, this name
248  * will be invalid after the next call to cmpl_next_completion.
249  */
250 static char*               cmpl_this_completion   (PossibleCompletion*);
251
252 /* True if this completion matches the given text.  Otherwise, this
253  * output can be used to have a list of non-completions.
254  */
255 static gint                cmpl_is_a_completion   (PossibleCompletion*);
256
257 /* True if the completion is a directory
258  */
259 static gboolean            cmpl_is_directory      (PossibleCompletion*);
260
261 /* Obtains the next completion, or NULL
262  */
263 static PossibleCompletion* cmpl_next_completion   (CompletionState*);
264
265 /* Updating completions: the return value of cmpl_updated_text() will
266  * be text_to_complete completed as much as possible after the most
267  * recent call to cmpl_completion_matches.  For the present
268  * application, this is the suggested replacement for the user's input
269  * string.  You must CALL THIS AFTER ALL cmpl_text_completions have
270  * been received.
271  */
272 static gchar*              cmpl_updated_text       (CompletionState* cmpl_state);
273
274 /* After updating, to see if the completion was a directory, call
275  * this.  If it was, you should consider re-calling completion_matches.
276  */
277 static gboolean            cmpl_updated_dir        (CompletionState* cmpl_state);
278
279 /* Current location: if using file completion, return the current
280  * directory, from which file completion begins.  More specifically,
281  * the cwd concatenated with all exact completions up to the last
282  * directory delimiter('/').
283  */
284 static gchar*              cmpl_reference_position (CompletionState* cmpl_state);
285
286 /* backing up: if cmpl_completion_matches returns NULL, you may query
287  * the index of the last completable character into cmpl_updated_text.
288  */
289 static gint                cmpl_last_valid_char    (CompletionState* cmpl_state);
290
291 /* When the user selects a non-directory, call cmpl_completion_fullname
292  * to get the full name of the selected file.
293  */
294 static gchar*              cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
295
296
297 /* Directory operations. */
298 static CompletionDir* open_ref_dir         (gchar* text_to_complete,
299                                             gchar** remaining_text,
300                                             CompletionState* cmpl_state);
301 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
302 static gboolean       check_dir            (gchar *dir_name, 
303                                             struct stat *result, 
304                                             gboolean *stat_subdirs);
305 #endif
306 static CompletionDir* open_dir             (gchar* dir_name,
307                                             CompletionState* cmpl_state);
308 #ifdef HAVE_PWD_H
309 static CompletionDir* open_user_dir        (gchar* text_to_complete,
310                                             CompletionState *cmpl_state);
311 #endif
312 static CompletionDir* open_relative_dir    (gchar* dir_name, CompletionDir* dir,
313                                             CompletionState *cmpl_state);
314 static CompletionDirSent* open_new_dir     (gchar* dir_name, 
315                                             struct stat* sbuf,
316                                             gboolean stat_subdirs);
317 static gint           correct_dir_fullname (CompletionDir* cmpl_dir);
318 static gint           correct_parent       (CompletionDir* cmpl_dir,
319                                             struct stat *sbuf);
320 static gchar*         find_parent_dir_fullname    (gchar* dirname);
321 static CompletionDir* attach_dir           (CompletionDirSent* sent,
322                                             gchar* dir_name,
323                                             CompletionState *cmpl_state);
324 static void           free_dir_sent (CompletionDirSent* sent);
325 static void           free_dir      (CompletionDir  *dir);
326 static void           prune_memory_usage(CompletionState *cmpl_state);
327
328 /* Completion operations */
329 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
330                                                       CompletionState *cmpl_state);
331 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
332 static CompletionDir* find_completion_dir(gchar* text_to_complete,
333                                           gchar** remaining_text,
334                                           CompletionState* cmpl_state);
335 static PossibleCompletion* append_completion_text(gchar* text,
336                                                   CompletionState* cmpl_state);
337 #ifdef HAVE_PWD_H
338 static gint get_pwdb(CompletionState* cmpl_state);
339 static gint compare_user_dir(const void* a, const void* b);
340 #endif
341 static gint first_diff_index(gchar* pat, gchar* text);
342 static gint compare_cmpl_dir(const void* a, const void* b);
343 static void update_cmpl(PossibleCompletion* poss,
344                         CompletionState* cmpl_state);
345
346 static void gtk_file_selection_class_init    (GtkFileSelectionClass *klass);
347 static void gtk_file_selection_set_property  (GObject         *object,
348                                               guint            prop_id,
349                                               const GValue    *value,
350                                               GParamSpec      *pspec);
351 static void gtk_file_selection_get_property  (GObject         *object,
352                                               guint            prop_id,
353                                               GValue          *value,
354                                               GParamSpec      *pspec);
355 static void gtk_file_selection_init          (GtkFileSelection      *filesel);
356 static void gtk_file_selection_destroy       (GtkObject             *object);
357 static gint gtk_file_selection_key_press     (GtkWidget             *widget,
358                                               GdkEventKey           *event,
359                                               gpointer               user_data);
360 static gint gtk_file_selection_insert_text   (GtkWidget             *widget,
361                                               const gchar           *new_text,
362                                               gint                   new_text_length,
363                                               gint                  *position,
364                                               gpointer               user_data);
365
366 static void gtk_file_selection_file_button (GtkWidget *widget,
367                                             gint row, 
368                                             gint column, 
369                                             GdkEventButton *bevent,
370                                             gpointer user_data);
371
372 static void gtk_file_selection_dir_button (GtkWidget *widget,
373                                            gint row, 
374                                            gint column, 
375                                            GdkEventButton *bevent,
376                                            gpointer data);
377
378 static void gtk_file_selection_populate      (GtkFileSelection      *fs,
379                                               gchar                 *rel_path,
380                                               gint                   try_complete);
381 static void gtk_file_selection_abort         (GtkFileSelection      *fs);
382
383 static void gtk_file_selection_update_history_menu (GtkFileSelection       *fs,
384                                                     gchar                  *current_dir);
385
386 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
387 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
388 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
389
390
391
392 static GtkWindowClass *parent_class = NULL;
393
394 /* Saves errno when something cmpl does fails. */
395 static gint cmpl_errno;
396
397 #ifdef G_WITH_CYGWIN
398 /*
399  * Take the path currently in the file selection
400  * entry field and translate as necessary from
401  * a WIN32 style to CYGWIN32 style path.  For
402  * instance translate:
403  * x:\somepath\file.jpg
404  * to:
405  * //x/somepath/file.jpg
406  *
407  * Replace the path in the selection text field.
408  * Return a boolean value concerning whether a
409  * translation had to be made.
410  */
411 int
412 translate_win32_path (GtkFileSelection *filesel)
413 {
414   int updated = 0;
415   gchar *path;
416
417   /*
418    * Retrieve the current path
419    */
420   path = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
421
422   /*
423    * Translate only if this looks like a DOS-ish
424    * path... First handle any drive letters.
425    */
426   if (isalpha (path[0]) && (path[1] == ':')) {
427     /*
428      * This part kind of stinks... It isn't possible
429      * to know if there is enough space in the current
430      * string for the extra character required in this
431      * conversion.  Assume that there isn't enough space
432      * and use the set function on the text field to
433      * set the newly created string.
434      */
435     gchar *newPath = g_strdup_printf ("//%c/%s", path[0], (path + 3));
436     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), newPath);
437
438     path = newPath;
439     updated = 1;
440   }
441
442   /*
443    * Now, replace backslashes with forward slashes 
444    * if necessary.
445    */
446   if (strchr (path, '\\'))
447     {
448       int index;
449       for (index = 0; path[index] != '\0'; index++)
450         if (path[index] == '\\')
451           path[index] = '/';
452       
453       updated = 1;
454     }
455     
456   return updated;
457 }
458 #endif
459
460 GtkType
461 gtk_file_selection_get_type (void)
462 {
463   static GtkType file_selection_type = 0;
464
465   if (!file_selection_type)
466     {
467       static const GtkTypeInfo filesel_info =
468       {
469         "GtkFileSelection",
470         sizeof (GtkFileSelection),
471         sizeof (GtkFileSelectionClass),
472         (GtkClassInitFunc) gtk_file_selection_class_init,
473         (GtkObjectInitFunc) gtk_file_selection_init,
474         /* reserved_1 */ NULL,
475         /* reserved_2 */ NULL,
476         (GtkClassInitFunc) NULL,
477       };
478
479       file_selection_type = gtk_type_unique (GTK_TYPE_DIALOG, &filesel_info);
480     }
481
482   return file_selection_type;
483 }
484
485 static void
486 gtk_file_selection_class_init (GtkFileSelectionClass *class)
487 {
488   GObjectClass *gobject_class;
489   GtkObjectClass *object_class;
490
491   gobject_class = (GObjectClass*) class;
492   object_class = (GtkObjectClass*) class;
493
494   parent_class = gtk_type_class (GTK_TYPE_DIALOG);
495
496   gobject_class->set_property = gtk_file_selection_set_property;
497   gobject_class->get_property = gtk_file_selection_get_property;
498    
499   g_object_class_install_property (gobject_class,
500                                    PROP_FILENAME,
501                                    g_param_spec_string ("filename",
502                                                         _("Filename"),
503                                                         _("The currently selected filename."),
504                                                         NULL,
505                                                         G_PARAM_READABLE | G_PARAM_WRITABLE));
506   g_object_class_install_property (gobject_class,
507                                    PROP_SHOW_FILEOPS,
508                                    g_param_spec_boolean ("show_fileops",
509                                                          _("Show file operations"),
510                                                          _("Whether buttons for creating/manipulating files should be displayed."),
511                                                          FALSE,
512                                                          G_PARAM_READABLE |
513                                                          G_PARAM_WRITABLE));
514   object_class->destroy = gtk_file_selection_destroy;
515 }
516
517 static void gtk_file_selection_set_property (GObject         *object,
518                                              guint            prop_id,
519                                              const GValue    *value,
520                                              GParamSpec      *pspec)
521 {
522   GtkFileSelection *filesel;
523
524   filesel = GTK_FILE_SELECTION (object);
525
526   switch (prop_id)
527     {
528     case PROP_FILENAME:
529       gtk_file_selection_set_filename (filesel,
530                                        g_value_get_string (value));
531       break;
532       
533     case PROP_SHOW_FILEOPS:
534       if (g_value_get_boolean (value))
535          gtk_file_selection_show_fileop_buttons (filesel);
536       else
537          gtk_file_selection_hide_fileop_buttons (filesel);
538       break;
539     default:
540       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
541       break;
542     }
543 }
544
545 static void gtk_file_selection_get_property (GObject         *object,
546                                              guint            prop_id,
547                                              GValue          *value,
548                                              GParamSpec      *pspec)
549 {
550   GtkFileSelection *filesel;
551
552   filesel = GTK_FILE_SELECTION (object);
553
554   switch (prop_id)
555     {
556     case PROP_FILENAME:
557       g_value_set_string (value,
558                           gtk_file_selection_get_filename(filesel));
559       break;
560
561     case PROP_SHOW_FILEOPS:
562       /* This is a little bit hacky, but doing otherwise would require
563        * adding a field to the object.
564        */
565       g_value_set_boolean (value, (filesel->fileop_c_dir && 
566                                    filesel->fileop_del_file &&
567                                    filesel->fileop_ren_file));
568       break;
569     default:
570       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
571       break;
572     }
573 }
574
575
576 static void
577 gtk_file_selection_init (GtkFileSelection *filesel)
578 {
579   GtkWidget *entry_vbox;
580   GtkWidget *label;
581   GtkWidget *list_hbox;
582   GtkWidget *confirm_area;
583   GtkWidget *pulldown_hbox;
584   GtkWidget *scrolled_win;
585   GtkDialog *dialog;
586   
587   char *dir_title [2];
588   char *file_title [2];
589
590   dialog = GTK_DIALOG (filesel);
591
592   filesel->cmpl_state = cmpl_init_state ();
593
594   /* The dialog-sized vertical box  */
595   filesel->main_vbox = dialog->vbox;
596   gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
597
598   /* The horizontal box containing create, rename etc. buttons */
599   filesel->button_area = gtk_hbutton_box_new ();
600   gtk_button_box_set_layout (GTK_BUTTON_BOX (filesel->button_area), GTK_BUTTONBOX_START);
601   gtk_box_set_spacing (GTK_BOX (filesel->button_area), 0);
602   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area, 
603                       FALSE, FALSE, 0);
604   gtk_widget_show (filesel->button_area);
605   
606   gtk_file_selection_show_fileop_buttons (filesel);
607
608   /* hbox for pulldown menu */
609   pulldown_hbox = gtk_hbox_new (TRUE, 5);
610   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
611   gtk_widget_show (pulldown_hbox);
612   
613   /* Pulldown menu */
614   filesel->history_pulldown = gtk_option_menu_new ();
615   gtk_widget_show (filesel->history_pulldown);
616   gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown, 
617                       FALSE, FALSE, 0);
618     
619   /*  The horizontal box containing the directory and file listboxes  */
620   list_hbox = gtk_hbox_new (FALSE, 5);
621   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
622   gtk_widget_show (list_hbox);
623
624   /* The directories clist */
625   dir_title[0] = _("Directories");
626   dir_title[1] = NULL;
627   filesel->dir_list = gtk_clist_new_with_titles (1, (gchar**) dir_title);
628   gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
629   gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
630                       (GtkSignalFunc) gtk_file_selection_dir_button, 
631                       (gpointer) filesel);
632   gtk_clist_set_column_auto_resize (GTK_CLIST (filesel->dir_list), 0, TRUE);
633   gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
634
635   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
636   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list);
637   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
638                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
639   gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5);
640   gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0);
641   gtk_widget_show (filesel->dir_list);
642   gtk_widget_show (scrolled_win);
643
644   /* The files clist */
645   file_title[0] = _("Files");
646   file_title[1] = NULL;
647   filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title);
648   gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
649   gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
650                       (GtkSignalFunc) gtk_file_selection_file_button, 
651                       (gpointer) filesel);
652   gtk_clist_set_column_auto_resize (GTK_CLIST (filesel->file_list), 0, TRUE);
653   gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
654
655   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
656   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list);
657   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
658                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
659   gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5);
660   gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0);
661   gtk_widget_show (filesel->file_list);
662   gtk_widget_show (scrolled_win);
663
664   /* action area for packing buttons into. */
665   filesel->action_area = gtk_hbox_new (TRUE, 0);
666   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area, 
667                       FALSE, FALSE, 0);
668   gtk_widget_show (filesel->action_area);
669   
670   /*  The OK/Cancel button area */
671   confirm_area = dialog->action_area;
672
673   /*  The OK button  */
674   filesel->ok_button = gtk_dialog_add_button (dialog,
675                                               GTK_STOCK_BUTTON_OK,
676                                               GTK_RESPONSE_OK);
677   
678   gtk_widget_grab_default (filesel->ok_button);
679
680   /*  The Cancel button  */
681   filesel->cancel_button = gtk_dialog_add_button (dialog,
682                                                   GTK_STOCK_BUTTON_CANCEL,
683                                                   GTK_RESPONSE_CANCEL);
684
685   /*  The selection entry widget  */
686   entry_vbox = gtk_vbox_new (FALSE, 2);
687   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 2);
688   gtk_widget_show (entry_vbox);
689
690   filesel->selection_text = label = gtk_label_new ("");
691   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
692   gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
693   gtk_widget_show (label);
694
695   filesel->selection_entry = gtk_entry_new ();
696   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
697                       (GtkSignalFunc) gtk_file_selection_key_press, filesel);
698   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "insert_text",
699                       (GtkSignalFunc) gtk_file_selection_insert_text, NULL);
700   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
701                              (GtkSignalFunc) gtk_widget_grab_default,
702                              GTK_OBJECT (filesel->ok_button));
703   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
704                              (GtkSignalFunc) gtk_button_clicked,
705                              GTK_OBJECT (filesel->ok_button));
706   gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
707   gtk_widget_show (filesel->selection_entry);
708
709   if (!cmpl_state_okay (filesel->cmpl_state))
710     {
711       gchar err_buf[256];
712
713       sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
714
715       gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
716     }
717   else
718     {
719       gtk_file_selection_populate (filesel, "", FALSE);
720     }
721
722   gtk_widget_grab_focus (filesel->selection_entry);
723 }
724
725 GtkWidget*
726 gtk_file_selection_new (const gchar *title)
727 {
728   GtkFileSelection *filesel;
729
730   filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION);
731   gtk_window_set_title (GTK_WINDOW (filesel), title);
732   gtk_dialog_set_has_separator (GTK_DIALOG (filesel), FALSE);
733
734   return GTK_WIDGET (filesel);
735 }
736
737 void
738 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
739 {
740   g_return_if_fail (filesel != NULL);
741   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
742     
743   /* delete, create directory, and rename */
744   if (!filesel->fileop_c_dir) 
745     {
746       filesel->fileop_c_dir = gtk_button_new_with_label (_("Create Dir"));
747       gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
748                           (GtkSignalFunc) gtk_file_selection_create_dir, 
749                           (gpointer) filesel);
750       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
751                           filesel->fileop_c_dir, TRUE, TRUE, 0);
752       gtk_widget_show (filesel->fileop_c_dir);
753     }
754         
755   if (!filesel->fileop_del_file) 
756     {
757       filesel->fileop_del_file = gtk_button_new_with_label (_("Delete File"));
758       gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
759                           (GtkSignalFunc) gtk_file_selection_delete_file, 
760                           (gpointer) filesel);
761       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
762                           filesel->fileop_del_file, TRUE, TRUE, 0);
763       gtk_widget_show (filesel->fileop_del_file);
764     }
765
766   if (!filesel->fileop_ren_file)
767     {
768       filesel->fileop_ren_file = gtk_button_new_with_label (_("Rename File"));
769       gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
770                           (GtkSignalFunc) gtk_file_selection_rename_file, 
771                           (gpointer) filesel);
772       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
773                           filesel->fileop_ren_file, TRUE, TRUE, 0);
774       gtk_widget_show (filesel->fileop_ren_file);
775     }
776   g_object_notify (G_OBJECT (filesel), "show_fileops");
777   gtk_widget_queue_resize (GTK_WIDGET (filesel));
778 }
779
780 void       
781 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
782 {
783   g_return_if_fail (filesel != NULL);
784   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
785     
786   if (filesel->fileop_ren_file)
787     {
788       gtk_widget_destroy (filesel->fileop_ren_file);
789       filesel->fileop_ren_file = NULL;
790     }
791
792   if (filesel->fileop_del_file)
793     {
794       gtk_widget_destroy (filesel->fileop_del_file);
795       filesel->fileop_del_file = NULL;
796     }
797
798   if (filesel->fileop_c_dir)
799     {
800       gtk_widget_destroy (filesel->fileop_c_dir);
801       filesel->fileop_c_dir = NULL;
802     }
803   g_object_notify (G_OBJECT (filesel), "show_fileops");
804 }
805
806
807
808 void
809 gtk_file_selection_set_filename (GtkFileSelection *filesel,
810                                  const gchar      *filename)
811 {
812   gchar *buf;
813   const char *name, *last_slash;
814
815   g_return_if_fail (filesel != NULL);
816   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
817   g_return_if_fail (filename != NULL);
818
819   last_slash = strrchr (filename, G_DIR_SEPARATOR);
820
821   if (!last_slash)
822     {
823       buf = g_strdup ("");
824       name = filename;
825     }
826   else
827     {
828       buf = g_strdup (filename);
829       buf[last_slash - filename + 1] = 0;
830       name = last_slash + 1;
831     }
832
833   gtk_file_selection_populate (filesel, buf, FALSE);
834
835   if (filesel->selection_entry)
836     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
837   g_free (buf);
838   g_object_notify (G_OBJECT (filesel), "filename");
839 }
840
841 /**
842  * gtk_file_selection_get_filename:
843  * @filesel: a #GtkFileSelection
844  * 
845  * This function returns the selected filename in the C runtime's
846  * multibyte string encoding, which may or may not be the same as that
847  * used by GTK+ (UTF-8). To convert to UTF-8, call g_filename_to_utf8().
848  * The returned string points to a statically allocated buffer and
849  * should be copied if you plan to keep it around.
850  * 
851  * Return value: currently-selected filename in locale's encoding
852  **/
853 G_CONST_RETURN gchar*
854 gtk_file_selection_get_filename (GtkFileSelection *filesel)
855 {
856   static gchar nothing[2] = "";
857   static gchar something[MAXPATHLEN*2];
858   char *sys_filename;
859   char *text;
860
861   g_return_val_if_fail (filesel != NULL, nothing);
862   g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
863
864 #ifdef G_WITH_CYGWIN
865   translate_win32_path (filesel);
866 #endif
867   text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
868   if (text)
869     {
870       sys_filename = g_filename_from_utf8 (cmpl_completion_fullname (text, filesel->cmpl_state), -1, NULL, NULL, NULL);
871       if (sys_filename)
872         return nothing;
873       strncpy (something, sys_filename, sizeof (something));
874       g_free (sys_filename);
875       return something;
876     }
877
878   return nothing;
879 }
880
881 void
882 gtk_file_selection_complete (GtkFileSelection *filesel,
883                              const gchar      *pattern)
884 {
885   g_return_if_fail (filesel != NULL);
886   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
887   g_return_if_fail (pattern != NULL);
888
889   if (filesel->selection_entry)
890     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern);
891   gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE);
892 }
893
894 static void
895 gtk_file_selection_destroy (GtkObject *object)
896 {
897   GtkFileSelection *filesel;
898   GList *list;
899   HistoryCallbackArg *callback_arg;
900   
901   g_return_if_fail (GTK_IS_FILE_SELECTION (object));
902   
903   filesel = GTK_FILE_SELECTION (object);
904   
905   if (filesel->fileop_dialog)
906     {
907       gtk_widget_destroy (filesel->fileop_dialog);
908       filesel->fileop_dialog = NULL;
909     }
910   
911   if (filesel->history_list)
912     {
913       list = filesel->history_list;
914       while (list)
915         {
916           callback_arg = list->data;
917           g_free (callback_arg->directory);
918           g_free (callback_arg);
919           list = list->next;
920         }
921       g_list_free (filesel->history_list);
922       filesel->history_list = NULL;
923     }
924
925   if (filesel->cmpl_state)
926     {
927       cmpl_free_state (filesel->cmpl_state);
928       filesel->cmpl_state = NULL;
929     }
930   
931   GTK_OBJECT_CLASS (parent_class)->destroy (object);
932 }
933
934 /* Begin file operations callbacks */
935
936 static void
937 gtk_file_selection_fileop_error (GtkFileSelection *fs,
938                                  gchar            *error_message)
939 {
940   GtkWidget *dialog;
941     
942   g_return_if_fail (error_message != NULL);
943
944   /* main dialog */
945   dialog = gtk_message_dialog_new (GTK_WINDOW (fs),
946                                    GTK_DIALOG_DESTROY_WITH_PARENT,
947                                    GTK_MESSAGE_ERROR,
948                                    GTK_BUTTONS_CLOSE,
949                                    "%s", error_message);
950
951   /* yes, we free it */
952   g_free (error_message);
953
954   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
955
956   gtk_signal_connect_object (GTK_OBJECT (dialog), "response",
957                              (GtkSignalFunc) gtk_widget_destroy, 
958                              (gpointer) dialog);
959
960   gtk_widget_show (dialog);
961 }
962
963 static void
964 gtk_file_selection_fileop_destroy (GtkWidget *widget,
965                                    gpointer   data)
966 {
967   GtkFileSelection *fs = data;
968
969   g_return_if_fail (fs != NULL);
970   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
971   
972   fs->fileop_dialog = NULL;
973 }
974
975
976 static void
977 gtk_file_selection_create_dir_confirmed (GtkWidget *widget,
978                                          gpointer   data)
979 {
980   GtkFileSelection *fs = data;
981   gchar *dirname;
982   gchar *path;
983   gchar *full_path;
984   gchar *sys_full_path;
985   gchar *buf;
986   GError *error = NULL;
987   CompletionState *cmpl_state;
988   
989   g_return_if_fail (fs != NULL);
990   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
991
992   dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
993   cmpl_state = (CompletionState*) fs->cmpl_state;
994   path = cmpl_reference_position (cmpl_state);
995   
996   full_path = g_strconcat (path, G_DIR_SEPARATOR_S, dirname, NULL);
997   sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
998   if (error)
999     {
1000       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1001         buf = g_strdup_printf (_("The directory name \"%s\" contains symbols that are not allowed in filenames"), dirname);
1002       else
1003         buf = g_strdup_printf (_("Error creating directory \"%s\": %s\n%s"), dirname, error->message,
1004                                _("You probably used symbols not allowed in filenames."));
1005       gtk_file_selection_fileop_error (fs, buf);
1006       g_error_free (error);
1007       goto out;
1008     }
1009
1010   if (mkdir (sys_full_path, 0755) < 0) 
1011     {
1012       buf = g_strdup_printf (_("Error creating directory \"%s\": %s\n"), dirname,
1013                              g_strerror (errno));
1014       gtk_file_selection_fileop_error (fs, buf);
1015     }
1016
1017  out:
1018   g_free (full_path);
1019   g_free (sys_full_path);
1020   
1021   gtk_widget_destroy (fs->fileop_dialog);
1022   gtk_file_selection_populate (fs, "", FALSE);
1023 }
1024   
1025 static void
1026 gtk_file_selection_create_dir (GtkWidget *widget,
1027                                gpointer   data)
1028 {
1029   GtkFileSelection *fs = data;
1030   GtkWidget *label;
1031   GtkWidget *dialog;
1032   GtkWidget *vbox;
1033   GtkWidget *button;
1034
1035   g_return_if_fail (fs != NULL);
1036   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1037
1038   if (fs->fileop_dialog)
1039     return;
1040   
1041   /* main dialog */
1042   dialog = gtk_dialog_new ();
1043   fs->fileop_dialog = dialog;
1044   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1045                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
1046                       (gpointer) fs);
1047   gtk_window_set_title (GTK_WINDOW (dialog), _("Create Directory"));
1048   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1049
1050   /* If file dialog is grabbed, grab option dialog */
1051   /* When option dialog is closed, file dialog will be grabbed again */
1052   if (GTK_WINDOW (fs)->modal)
1053       gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1054
1055   vbox = gtk_vbox_new (FALSE, 0);
1056   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1057   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1058                      FALSE, FALSE, 0);
1059   gtk_widget_show( vbox);
1060   
1061   label = gtk_label_new_with_mnemonic (_("_Directory name:"));
1062   gtk_misc_set_alignment(GTK_MISC (label), 0.0, 0.0);
1063   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1064   gtk_widget_show (label);
1065
1066   /*  The directory entry widget  */
1067   fs->fileop_entry = gtk_entry_new ();
1068   gtk_label_set_mnemonic_widget (GTK_LABEL (label), fs->fileop_entry);
1069   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, 
1070                       TRUE, TRUE, 5);
1071   GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1072   gtk_widget_show (fs->fileop_entry);
1073   
1074   /* buttons */
1075   button = gtk_button_new_with_label (_("Create"));
1076   gtk_signal_connect (GTK_OBJECT (button), "clicked",
1077                       (GtkSignalFunc) gtk_file_selection_create_dir_confirmed, 
1078                       (gpointer) fs);
1079   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1080                      button, TRUE, TRUE, 0);
1081   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1082   gtk_widget_show (button);
1083   
1084   button = gtk_button_new_with_label (_("Cancel"));
1085   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1086                              (GtkSignalFunc) gtk_widget_destroy, 
1087                              (gpointer) dialog);
1088   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1089                      button, TRUE, TRUE, 0);
1090   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1091   gtk_widget_grab_default (button);
1092   gtk_widget_show (button);
1093
1094   gtk_widget_show (dialog);
1095 }
1096
1097 static void
1098 gtk_file_selection_delete_file_confirmed (GtkWidget *widget,
1099                                           gpointer   data)
1100 {
1101   GtkFileSelection *fs = data;
1102   CompletionState *cmpl_state;
1103   gchar *path;
1104   gchar *full_path;
1105   gchar *sys_full_path;
1106   GError *error = NULL;
1107   gchar *buf;
1108   
1109   g_return_if_fail (fs != NULL);
1110   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1111
1112   cmpl_state = (CompletionState*) fs->cmpl_state;
1113   path = cmpl_reference_position (cmpl_state);
1114   
1115   full_path = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL);
1116   sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
1117   if (error)
1118     {
1119       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1120         buf = g_strdup_printf (_("The filename \"%s\" contains symbols that are not allowed in filenames"),
1121                                fs->fileop_file);
1122       else
1123         buf = g_strdup_printf (_("Error deleting file \"%s\": %s\n%s"),
1124                                fs->fileop_file, error->message,
1125                                _("It probably contains symbols not allowed in filenames."));
1126       
1127       gtk_file_selection_fileop_error (fs, buf);
1128       g_error_free (error);
1129       goto out;
1130     }
1131
1132   if (unlink (sys_full_path) < 0) 
1133     {
1134       buf = g_strdup_printf (_("Error deleting file \"%s\": %s"),
1135                              fs->fileop_file, g_strerror (errno));
1136       gtk_file_selection_fileop_error (fs, buf);
1137     }
1138   
1139  out:
1140   g_free (full_path);
1141   g_free (sys_full_path);
1142   
1143   gtk_widget_destroy (fs->fileop_dialog);
1144   gtk_file_selection_populate (fs, "", FALSE);
1145 }
1146
1147 static void
1148 gtk_file_selection_delete_file (GtkWidget *widget,
1149                                 gpointer   data)
1150 {
1151   GtkFileSelection *fs = data;
1152   GtkWidget *label;
1153   GtkWidget *vbox;
1154   GtkWidget *button;
1155   GtkWidget *dialog;
1156   gchar *filename;
1157   gchar *buf;
1158   
1159   g_return_if_fail (fs != NULL);
1160   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1161
1162   if (fs->fileop_dialog)
1163           return;
1164
1165 #ifdef G_WITH_CYGWIN
1166   translate_win32_path (fs);
1167 #endif
1168
1169   filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1170   if (strlen (filename) < 1)
1171     return;
1172
1173   fs->fileop_file = filename;
1174   
1175   /* main dialog */
1176   fs->fileop_dialog = dialog = gtk_dialog_new ();
1177   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1178                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
1179                       (gpointer) fs);
1180   gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File"));
1181   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1182
1183   /* If file dialog is grabbed, grab option dialog */
1184   /* When option dialog is closed, file dialog will be grabbed again */
1185   if (GTK_WINDOW (fs)->modal)
1186       gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1187   
1188   vbox = gtk_vbox_new (FALSE, 0);
1189   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1190   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1191                      FALSE, FALSE, 0);
1192   gtk_widget_show (vbox);
1193
1194   buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
1195   label = gtk_label_new (buf);
1196   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1197   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1198   gtk_widget_show (label);
1199   g_free (buf);
1200   
1201   /* buttons */
1202   button = gtk_button_new_with_label (_("Delete"));
1203   gtk_signal_connect (GTK_OBJECT (button), "clicked",
1204                       (GtkSignalFunc) gtk_file_selection_delete_file_confirmed, 
1205                       (gpointer) fs);
1206   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1207                       button, TRUE, TRUE, 0);
1208   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1209   gtk_widget_show (button);
1210   
1211   button = gtk_button_new_with_label (_("Cancel"));
1212   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1213                              (GtkSignalFunc) gtk_widget_destroy, 
1214                              (gpointer) dialog);
1215   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1216                       button, TRUE, TRUE, 0);
1217   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1218   gtk_widget_grab_default (button);
1219   gtk_widget_show (button);
1220
1221   gtk_widget_show (dialog);
1222
1223 }
1224
1225 static void
1226 gtk_file_selection_rename_file_confirmed (GtkWidget *widget,
1227                                           gpointer   data)
1228 {
1229   GtkFileSelection *fs = data;
1230   gchar *buf;
1231   gchar *file;
1232   gchar *path;
1233   gchar *new_filename;
1234   gchar *old_filename;
1235   gchar *sys_new_filename;
1236   gchar *sys_old_filename;
1237   CompletionState *cmpl_state;
1238   GError *error = NULL;
1239   
1240   g_return_if_fail (fs != NULL);
1241   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1242
1243   file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1244   cmpl_state = (CompletionState*) fs->cmpl_state;
1245   path = cmpl_reference_position (cmpl_state);
1246   
1247   new_filename = g_strconcat (path, G_DIR_SEPARATOR_S, file, NULL);
1248   old_filename = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL);
1249
1250   sys_new_filename = g_filename_from_utf8 (new_filename, -1, NULL, NULL, &error);
1251   if (error)
1252     {
1253       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1254         buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), new_filename);
1255       else
1256         buf = g_strdup_printf (_("Error renaming file to \"%s\": %s\n%s"),
1257                                new_filename, error->message,
1258                                _("You probably used symbols not allowed in filenames."));
1259       gtk_file_selection_fileop_error (fs, buf);
1260       g_error_free (error);
1261       goto out1;
1262     }
1263
1264   sys_old_filename = g_filename_from_utf8 (old_filename, -1, NULL, NULL, &error);
1265   if (error)
1266     {
1267       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1268         buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), old_filename);
1269       else
1270         buf = g_strdup_printf (_("Error renaming file \"%s\": %s\n%s"),
1271                                old_filename, error->message,
1272                                _("It probably contains symbols not allowed in filenames."));
1273       gtk_file_selection_fileop_error (fs, buf);
1274       g_error_free (error);
1275       goto out2;
1276     }
1277   
1278   if (rename (sys_old_filename, sys_new_filename) < 0) 
1279     {
1280       buf = g_strdup_printf (_("Error renaming file \"%s\" to \"%s\": %s"),
1281                              sys_old_filename, sys_new_filename,
1282                              g_strerror (errno));
1283       gtk_file_selection_fileop_error (fs, buf);
1284     }
1285   
1286  out2:
1287   g_free (sys_old_filename);
1288
1289  out1:
1290   g_free (new_filename);
1291   g_free (old_filename);
1292   g_free (sys_new_filename);
1293   
1294   gtk_widget_destroy (fs->fileop_dialog);
1295   gtk_file_selection_populate (fs, "", FALSE);
1296 }
1297   
1298 static void
1299 gtk_file_selection_rename_file (GtkWidget *widget,
1300                                 gpointer   data)
1301 {
1302   GtkFileSelection *fs = data;
1303   GtkWidget *label;
1304   GtkWidget *dialog;
1305   GtkWidget *vbox;
1306   GtkWidget *button;
1307   gchar *buf;
1308   
1309   g_return_if_fail (fs != NULL);
1310   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1311
1312   if (fs->fileop_dialog)
1313           return;
1314
1315   fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1316   if (strlen (fs->fileop_file) < 1)
1317     return;
1318   
1319   /* main dialog */
1320   fs->fileop_dialog = dialog = gtk_dialog_new ();
1321   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1322                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
1323                       (gpointer) fs);
1324   gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File"));
1325   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1326
1327   /* If file dialog is grabbed, grab option dialog */
1328   /* When option dialog  closed, file dialog will be grabbed again */
1329   if (GTK_WINDOW (fs)->modal)
1330     gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1331   
1332   vbox = gtk_vbox_new (FALSE, 0);
1333   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1334   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1335                       FALSE, FALSE, 0);
1336   gtk_widget_show(vbox);
1337   
1338   buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
1339   label = gtk_label_new(buf);
1340   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1341   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1342   gtk_widget_show (label);
1343   g_free (buf);
1344
1345   /* New filename entry */
1346   fs->fileop_entry = gtk_entry_new ();
1347   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, 
1348                       TRUE, TRUE, 5);
1349   GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1350   gtk_widget_show (fs->fileop_entry);
1351   
1352   gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
1353   gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
1354                               0, strlen (fs->fileop_file));
1355
1356   /* buttons */
1357   button = gtk_button_new_with_label (_("Rename"));
1358   gtk_signal_connect (GTK_OBJECT (button), "clicked",
1359                       (GtkSignalFunc) gtk_file_selection_rename_file_confirmed, 
1360                       (gpointer) fs);
1361   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1362                       button, TRUE, TRUE, 0);
1363   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1364   gtk_widget_show (button);
1365   
1366   button = gtk_button_new_with_label (_("Cancel"));
1367   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1368                              (GtkSignalFunc) gtk_widget_destroy, 
1369                              (gpointer) dialog);
1370   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1371                       button, TRUE, TRUE, 0);
1372   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1373   gtk_widget_grab_default (button);
1374   gtk_widget_show (button);
1375
1376   gtk_widget_show (dialog);
1377 }
1378
1379 static gint
1380 gtk_file_selection_insert_text (GtkWidget   *widget,
1381                                 const gchar *new_text,
1382                                 gint         new_text_length,
1383                                 gint        *position,
1384                                 gpointer     user_data)
1385 {
1386   gchar *filename;
1387
1388   filename = g_filename_from_utf8 (new_text, new_text_length, NULL, NULL, NULL);
1389
1390   if (!filename)
1391     {
1392       gdk_beep ();
1393       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "insert_text");
1394       return FALSE;
1395     }
1396   
1397   g_free (filename);
1398   
1399   return TRUE;
1400 }
1401
1402 static gint
1403 gtk_file_selection_key_press (GtkWidget   *widget,
1404                               GdkEventKey *event,
1405                               gpointer     user_data)
1406 {
1407   GtkFileSelection *fs;
1408   char *text;
1409
1410   g_return_val_if_fail (widget != NULL, FALSE);
1411   g_return_val_if_fail (event != NULL, FALSE);
1412
1413   if (event->keyval == GDK_Tab)
1414     {
1415       fs = GTK_FILE_SELECTION (user_data);
1416 #ifdef G_WITH_CYGWIN
1417       translate_win32_path (fs);
1418 #endif
1419       text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1420
1421       text = g_strdup (text);
1422
1423       gtk_file_selection_populate (fs, text, TRUE);
1424
1425       g_free (text);
1426
1427       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1428
1429       return TRUE;
1430     }
1431
1432   return FALSE;
1433 }
1434
1435
1436 static void
1437 gtk_file_selection_history_callback (GtkWidget *widget,
1438                                      gpointer   data)
1439 {
1440   GtkFileSelection *fs = data;
1441   HistoryCallbackArg *callback_arg;
1442   GList *list;
1443
1444   g_return_if_fail (fs != NULL);
1445   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1446
1447   list = fs->history_list;
1448   
1449   while (list) {
1450     callback_arg = list->data;
1451     
1452     if (callback_arg->menu_item == widget)
1453       {
1454         gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1455         break;
1456       }
1457     
1458     list = list->next;
1459   }
1460 }
1461
1462 static void 
1463 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1464                                         gchar            *current_directory)
1465 {
1466   HistoryCallbackArg *callback_arg;
1467   GtkWidget *menu_item;
1468   GList *list;
1469   gchar *current_dir;
1470   gint dir_len;
1471   gint i;
1472   
1473   g_return_if_fail (fs != NULL);
1474   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1475   g_return_if_fail (current_directory != NULL);
1476   
1477   list = fs->history_list;
1478
1479   if (fs->history_menu) 
1480     {
1481       while (list) {
1482         callback_arg = list->data;
1483         g_free (callback_arg->directory);
1484         g_free (callback_arg);
1485         list = list->next;
1486       }
1487       g_list_free (fs->history_list);
1488       fs->history_list = NULL;
1489       
1490       gtk_widget_destroy (fs->history_menu);
1491     }
1492   
1493   fs->history_menu = gtk_menu_new ();
1494
1495   current_dir = g_strdup (current_directory);
1496
1497   dir_len = strlen (current_dir);
1498
1499   for (i = dir_len; i >= 0; i--)
1500     {
1501       /* the i == dir_len is to catch the full path for the first 
1502        * entry. */
1503       if ( (current_dir[i] == G_DIR_SEPARATOR) || (i == dir_len))
1504         {
1505           /* another small hack to catch the full path */
1506           if (i != dir_len) 
1507                   current_dir[i + 1] = '\0';
1508 #ifdef G_WITH_CYGWIN
1509           if (!strcmp (current_dir, "//"))
1510             continue;
1511 #endif
1512           menu_item = gtk_menu_item_new_with_label (current_dir);
1513           
1514           callback_arg = g_new (HistoryCallbackArg, 1);
1515           callback_arg->menu_item = menu_item;
1516           
1517           /* since the autocompletion gets confused if you don't 
1518            * supply a trailing '/' on a dir entry, set the full
1519            * (current) path to "" which just refreshes the filesel */
1520           if (dir_len == i)
1521             {
1522               callback_arg->directory = g_strdup ("");
1523             }
1524           else
1525             {
1526               callback_arg->directory = g_strdup (current_dir);
1527             }
1528           
1529           fs->history_list = g_list_append (fs->history_list, callback_arg);
1530           
1531           gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1532                               (GtkSignalFunc) gtk_file_selection_history_callback,
1533                               (gpointer) fs);
1534           gtk_menu_shell_append (GTK_MENU_SHELL (fs->history_menu), menu_item);
1535           gtk_widget_show (menu_item);
1536         }
1537     }
1538
1539   gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown), 
1540                             fs->history_menu);
1541   g_free (current_dir);
1542 }
1543
1544 static void
1545 gtk_file_selection_file_button (GtkWidget      *widget,
1546                                 gint            row, 
1547                                 gint            column, 
1548                                 GdkEventButton *bevent,
1549                                 gpointer        user_data)
1550 {
1551   GtkFileSelection *fs = NULL;
1552   gchar *filename, *temp = NULL;
1553   
1554   g_return_if_fail (GTK_IS_CLIST (widget));
1555
1556   fs = user_data;
1557   g_return_if_fail (fs != NULL);
1558   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1559   
1560   gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1561   filename = g_strdup (temp);
1562
1563 #ifdef G_WITH_CYGWIN
1564   /* Check to see if the selection was a drive selector */
1565   if (isalpha (filename[0]) && (filename[1] == ':')) {
1566     /* It is... map it to a CYGWIN32 drive */
1567     gchar *temp_filename = g_strdup_printf ("//%c/", tolower (filename[0]));
1568     g_free(filename);
1569     filename = temp_filename;
1570   }
1571 #endif /* G_WITH_CYGWIN */
1572
1573   if (filename)
1574     {
1575       if (bevent)
1576         switch (bevent->type)
1577           {
1578           case GDK_2BUTTON_PRESS:
1579             gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1580             break;
1581             
1582           default:
1583             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1584             break;
1585           }
1586       else
1587         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1588
1589       g_free (filename);
1590     }
1591 }
1592
1593 static void
1594 gtk_file_selection_dir_button (GtkWidget      *widget,
1595                                gint            row, 
1596                                gint            column, 
1597                                GdkEventButton *bevent,
1598                                gpointer        user_data)
1599 {
1600   GtkFileSelection *fs = NULL;
1601   gchar *filename, *temp = NULL;
1602
1603   g_return_if_fail (GTK_IS_CLIST (widget));
1604
1605   fs = GTK_FILE_SELECTION (user_data);
1606   g_return_if_fail (fs != NULL);
1607   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1608
1609   gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1610   filename = g_strdup (temp);
1611
1612   if (filename)
1613     {
1614       if (bevent)
1615         switch (bevent->type)
1616           {
1617           case GDK_2BUTTON_PRESS:
1618             gtk_file_selection_populate (fs, filename, FALSE);
1619             break;
1620           
1621           default:
1622             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1623             break;
1624           }
1625       else
1626         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1627
1628       g_free (filename);
1629     }
1630 }
1631
1632 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
1633
1634 static void
1635 win32_gtk_add_drives_to_dir_list (GtkWidget *the_dir_list)
1636 {
1637   gchar *text[2], *textPtr;
1638   gchar buffer[128];
1639   char volumeNameBuf[128];
1640   char formatBuffer[128];
1641
1642   text[1] = NULL;
1643
1644   /* Get the Drives string */
1645   GetLogicalDriveStrings (sizeof (buffer), buffer);
1646
1647   /* Add the drives as necessary */
1648   textPtr = buffer;
1649   while (*textPtr != '\0') {
1650     /* Get the volume information for this drive */
1651     if ((tolower (textPtr[0]) != 'a') && (tolower (textPtr[0]) != 'b'))
1652       {
1653         /* Ignore floppies (?) */
1654         DWORD maxComponentLength, flags;
1655
1656         GetVolumeInformation (textPtr,
1657                               volumeNameBuf, sizeof(volumeNameBuf),
1658                               NULL, &maxComponentLength,
1659                               &flags, NULL, 0);
1660         /* Build the actual displayable string */
1661
1662         sprintf (formatBuffer, "%c:\\", toupper(textPtr[0]));
1663 #if 0 /* HB: removed to allow drive change AND directory update with one click */
1664         if (strlen (volumeNameBuf) > 0)
1665           sprintf (formatBuffer, "%s (%s)", formatBuffer, volumeNameBuf);
1666 #endif
1667         /* Add to the list */
1668         text[0] = formatBuffer;
1669         gtk_clist_append (GTK_CLIST (the_dir_list), text);
1670       }
1671     textPtr += (strlen (textPtr) + 1);
1672   }
1673 }
1674 #endif
1675
1676 static void
1677 gtk_file_selection_populate (GtkFileSelection *fs,
1678                              gchar            *rel_path,
1679                              gint              try_complete)
1680 {
1681   CompletionState *cmpl_state;
1682   PossibleCompletion* poss;
1683   gchar* filename;
1684   gchar* rem_path = rel_path;
1685   gchar* sel_text;
1686   gchar* text[2];
1687   gint did_recurse = FALSE;
1688   gint possible_count = 0;
1689   gint selection_index = -1;
1690   
1691   g_return_if_fail (fs != NULL);
1692   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1693   
1694   cmpl_state = (CompletionState*) fs->cmpl_state;
1695   poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1696
1697   if (!cmpl_state_okay (cmpl_state))
1698     {
1699       /* Something went wrong. */
1700       gtk_file_selection_abort (fs);
1701       return;
1702     }
1703
1704   g_assert (cmpl_state->reference_dir);
1705
1706   gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1707   gtk_clist_clear (GTK_CLIST (fs->dir_list));
1708   gtk_clist_freeze (GTK_CLIST (fs->file_list));
1709   gtk_clist_clear (GTK_CLIST (fs->file_list));
1710
1711   /* Set the dir_list to include ./ and ../ */
1712   text[1] = NULL;
1713   text[0] = "." G_DIR_SEPARATOR_S;
1714   gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1715
1716   text[0] = ".." G_DIR_SEPARATOR_S;
1717   gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1718
1719   while (poss)
1720     {
1721       if (cmpl_is_a_completion (poss))
1722         {
1723           possible_count += 1;
1724
1725           filename = cmpl_this_completion (poss);
1726
1727           text[0] = filename;
1728           
1729           if (cmpl_is_directory (poss))
1730             {
1731               if (strcmp (filename, "." G_DIR_SEPARATOR_S) != 0 &&
1732                   strcmp (filename, ".." G_DIR_SEPARATOR_S) != 0)
1733                 gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1734             }
1735           else
1736             {
1737               gtk_clist_append (GTK_CLIST (fs->file_list), text);
1738             }
1739         }
1740
1741       poss = cmpl_next_completion (cmpl_state);
1742     }
1743
1744 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
1745   /* For Windows, add drives as potential selections */
1746   win32_gtk_add_drives_to_dir_list (fs->dir_list);
1747 #endif
1748
1749   gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1750   gtk_clist_thaw (GTK_CLIST (fs->file_list));
1751
1752   /* File lists are set. */
1753
1754   g_assert (cmpl_state->reference_dir);
1755
1756   if (try_complete)
1757     {
1758
1759       /* User is trying to complete filenames, so advance the user's input
1760        * string to the updated_text, which is the common leading substring
1761        * of all possible completions, and if its a directory attempt
1762        * attempt completions in it. */
1763
1764       if (cmpl_updated_text (cmpl_state)[0])
1765         {
1766
1767           if (cmpl_updated_dir (cmpl_state))
1768             {
1769               gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1770
1771               did_recurse = TRUE;
1772
1773               gtk_file_selection_populate (fs, dir_name, TRUE);
1774
1775               g_free (dir_name);
1776             }
1777           else
1778             {
1779               if (fs->selection_entry)
1780                       gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1781                                           cmpl_updated_text (cmpl_state));
1782             }
1783         }
1784       else
1785         {
1786           selection_index = cmpl_last_valid_char (cmpl_state) -
1787                             (strlen (rel_path) - strlen (rem_path));
1788           if (fs->selection_entry)
1789             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1790         }
1791     }
1792   else
1793     {
1794       if (fs->selection_entry)
1795         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1796     }
1797
1798   if (!did_recurse)
1799     {
1800       if (fs->selection_entry)
1801         gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1802
1803       if (fs->selection_entry)
1804         {
1805           sel_text = g_strconcat (_("Selection: "),
1806                                   cmpl_reference_position (cmpl_state),
1807                                   NULL);
1808
1809           gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text);
1810           g_free (sel_text);
1811         }
1812
1813       if (fs->history_pulldown) 
1814         {
1815           gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1816         }
1817       
1818     }
1819 }
1820
1821 static void
1822 gtk_file_selection_abort (GtkFileSelection *fs)
1823 {
1824   gchar err_buf[256];
1825
1826   sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
1827
1828   /*  BEEP gdk_beep();  */
1829
1830   if (fs->selection_entry)
1831     gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
1832 }
1833
1834 /**********************************************************************/
1835 /*                        External Interface                          */
1836 /**********************************************************************/
1837
1838 /* The four completion state selectors
1839  */
1840 static gchar*
1841 cmpl_updated_text (CompletionState *cmpl_state)
1842 {
1843   return cmpl_state->updated_text;
1844 }
1845
1846 static gboolean
1847 cmpl_updated_dir (CompletionState *cmpl_state)
1848 {
1849   return cmpl_state->re_complete;
1850 }
1851
1852 static gchar*
1853 cmpl_reference_position (CompletionState *cmpl_state)
1854 {
1855   return cmpl_state->reference_dir->fullname;
1856 }
1857
1858 static gint
1859 cmpl_last_valid_char (CompletionState *cmpl_state)
1860 {
1861   return cmpl_state->last_valid_char;
1862 }
1863
1864 static gchar*
1865 cmpl_completion_fullname (gchar           *text,
1866                           CompletionState *cmpl_state)
1867 {
1868   static char nothing[2] = "";
1869
1870   if (!cmpl_state_okay (cmpl_state))
1871     {
1872       return nothing;
1873     }
1874   else if (g_path_is_absolute (text))
1875     {
1876       strcpy (cmpl_state->updated_text, text);
1877     }
1878 #ifdef HAVE_PWD_H
1879   else if (text[0] == '~')
1880     {
1881       CompletionDir* dir;
1882       char* slash;
1883
1884       dir = open_user_dir (text, cmpl_state);
1885
1886       if (!dir)
1887         {
1888           /* spencer says just return ~something, so
1889            * for now just do it. */
1890           strcpy (cmpl_state->updated_text, text);
1891         }
1892       else
1893         {
1894
1895           strcpy (cmpl_state->updated_text, dir->fullname);
1896
1897           slash = strchr (text, G_DIR_SEPARATOR);
1898
1899           if (slash)
1900             strcat (cmpl_state->updated_text, slash);
1901         }
1902     }
1903 #endif
1904   else
1905     {
1906       strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1907       if (cmpl_state->updated_text[strlen (cmpl_state->updated_text) - 1] != G_DIR_SEPARATOR)
1908         strcat (cmpl_state->updated_text, G_DIR_SEPARATOR_S);
1909       strcat (cmpl_state->updated_text, text);
1910     }
1911
1912   return cmpl_state->updated_text;
1913 }
1914
1915 /* The three completion selectors
1916  */
1917 static gchar*
1918 cmpl_this_completion (PossibleCompletion* pc)
1919 {
1920   return pc->text;
1921 }
1922
1923 static gboolean
1924 cmpl_is_directory (PossibleCompletion* pc)
1925 {
1926   return pc->is_directory;
1927 }
1928
1929 static gint
1930 cmpl_is_a_completion (PossibleCompletion* pc)
1931 {
1932   return pc->is_a_completion;
1933 }
1934
1935 /**********************************************************************/
1936 /*                       Construction, deletion                       */
1937 /**********************************************************************/
1938
1939 static CompletionState*
1940 cmpl_init_state (void)
1941 {
1942   gchar *sys_getcwd_buf;
1943   gchar *utf8_cwd;
1944   CompletionState *new_state;
1945
1946   new_state = g_new (CompletionState, 1);
1947
1948   /* g_get_current_dir() returns a string in the "system" charset */
1949   sys_getcwd_buf = g_get_current_dir ();
1950   utf8_cwd = g_filename_to_utf8 (sys_getcwd_buf, -1, NULL, NULL, NULL);
1951   g_free (sys_getcwd_buf);
1952
1953 tryagain:
1954
1955   new_state->reference_dir = NULL;
1956   new_state->completion_dir = NULL;
1957   new_state->active_completion_dir = NULL;
1958   new_state->directory_storage = NULL;
1959   new_state->directory_sent_storage = NULL;
1960   new_state->last_valid_char = 0;
1961   new_state->updated_text = g_new (gchar, MAXPATHLEN);
1962   new_state->updated_text_alloc = MAXPATHLEN;
1963   new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1964   new_state->the_completion.text_alloc = MAXPATHLEN;
1965   new_state->user_dir_name_buffer = NULL;
1966   new_state->user_directories = NULL;
1967
1968   new_state->reference_dir = open_dir (utf8_cwd, new_state);
1969
1970   if (!new_state->reference_dir)
1971     {
1972       /* Directories changing from underneath us, grumble */
1973       strcpy (utf8_cwd, G_DIR_SEPARATOR_S);
1974       goto tryagain;
1975     }
1976
1977   g_free (utf8_cwd);
1978   return new_state;
1979 }
1980
1981 static void
1982 cmpl_free_dir_list (GList* dp0)
1983 {
1984   GList *dp = dp0;
1985
1986   while (dp)
1987     {
1988       free_dir (dp->data);
1989       dp = dp->next;
1990     }
1991
1992   g_list_free (dp0);
1993 }
1994
1995 static void
1996 cmpl_free_dir_sent_list (GList* dp0)
1997 {
1998   GList *dp = dp0;
1999
2000   while (dp)
2001     {
2002       free_dir_sent (dp->data);
2003       dp = dp->next;
2004     }
2005
2006   g_list_free (dp0);
2007 }
2008
2009 static void
2010 cmpl_free_state (CompletionState* cmpl_state)
2011 {
2012   g_return_if_fail (cmpl_state != NULL);
2013
2014   cmpl_free_dir_list (cmpl_state->directory_storage);
2015   cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
2016
2017   if (cmpl_state->user_dir_name_buffer)
2018     g_free (cmpl_state->user_dir_name_buffer);
2019   if (cmpl_state->user_directories)
2020     g_free (cmpl_state->user_directories);
2021   if (cmpl_state->the_completion.text)
2022     g_free (cmpl_state->the_completion.text);
2023   if (cmpl_state->updated_text)
2024     g_free (cmpl_state->updated_text);
2025
2026   g_free (cmpl_state);
2027 }
2028
2029 static void
2030 free_dir (CompletionDir* dir)
2031 {
2032   g_free (dir->fullname);
2033   g_free (dir);
2034 }
2035
2036 static void
2037 free_dir_sent (CompletionDirSent* sent)
2038 {
2039   gint i;
2040   for (i = 0; i < sent->entry_count; i++)
2041     g_free (sent->entries[i].entry_name);
2042   g_free (sent->entries);
2043   g_free (sent);
2044 }
2045
2046 static void
2047 prune_memory_usage (CompletionState *cmpl_state)
2048 {
2049   GList* cdsl = cmpl_state->directory_sent_storage;
2050   GList* cdl = cmpl_state->directory_storage;
2051   GList* cdl0 = cdl;
2052   gint len = 0;
2053
2054   for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
2055     cdsl = cdsl->next;
2056
2057   if (cdsl)
2058     {
2059       cmpl_free_dir_sent_list (cdsl->next);
2060       cdsl->next = NULL;
2061     }
2062
2063   cmpl_state->directory_storage = NULL;
2064   while (cdl)
2065     {
2066       if (cdl->data == cmpl_state->reference_dir)
2067         cmpl_state->directory_storage = g_list_prepend (NULL, cdl->data);
2068       else
2069         free_dir (cdl->data);
2070       cdl = cdl->next;
2071     }
2072
2073   g_list_free (cdl0);
2074 }
2075
2076 /**********************************************************************/
2077 /*                        The main entrances.                         */
2078 /**********************************************************************/
2079
2080 static PossibleCompletion*
2081 cmpl_completion_matches (gchar           *text_to_complete,
2082                          gchar          **remaining_text,
2083                          CompletionState *cmpl_state)
2084 {
2085   gchar* first_slash;
2086   PossibleCompletion *poss;
2087
2088   prune_memory_usage (cmpl_state);
2089
2090   g_assert (text_to_complete != NULL);
2091
2092   cmpl_state->user_completion_index = -1;
2093   cmpl_state->last_completion_text = text_to_complete;
2094   cmpl_state->the_completion.text[0] = 0;
2095   cmpl_state->last_valid_char = 0;
2096   cmpl_state->updated_text_len = -1;
2097   cmpl_state->updated_text[0] = 0;
2098   cmpl_state->re_complete = FALSE;
2099
2100 #ifdef HAVE_PWD_H
2101   first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2102
2103   if (text_to_complete[0] == '~' && !first_slash)
2104     {
2105       /* Text starts with ~ and there is no slash, show all the
2106        * home directory completions.
2107        */
2108       poss = attempt_homedir_completion (text_to_complete, cmpl_state);
2109
2110       update_cmpl (poss, cmpl_state);
2111
2112       return poss;
2113     }
2114 #endif
2115   cmpl_state->reference_dir =
2116     open_ref_dir (text_to_complete, remaining_text, cmpl_state);
2117
2118   if (!cmpl_state->reference_dir)
2119     return NULL;
2120
2121   cmpl_state->completion_dir =
2122     find_completion_dir (*remaining_text, remaining_text, cmpl_state);
2123
2124   cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2125
2126   if (!cmpl_state->completion_dir)
2127     return NULL;
2128
2129   cmpl_state->completion_dir->cmpl_index = -1;
2130   cmpl_state->completion_dir->cmpl_parent = NULL;
2131   cmpl_state->completion_dir->cmpl_text = *remaining_text;
2132
2133   cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2134
2135   cmpl_state->reference_dir = cmpl_state->completion_dir;
2136
2137   poss = attempt_file_completion (cmpl_state);
2138
2139   update_cmpl (poss, cmpl_state);
2140
2141   return poss;
2142 }
2143
2144 static PossibleCompletion*
2145 cmpl_next_completion (CompletionState* cmpl_state)
2146 {
2147   PossibleCompletion* poss = NULL;
2148
2149   cmpl_state->the_completion.text[0] = 0;
2150
2151 #ifdef HAVE_PWD_H
2152   if (cmpl_state->user_completion_index >= 0)
2153     poss = attempt_homedir_completion (cmpl_state->last_completion_text, cmpl_state);
2154   else
2155     poss = attempt_file_completion (cmpl_state);
2156 #else
2157   poss = attempt_file_completion (cmpl_state);
2158 #endif
2159
2160   update_cmpl (poss, cmpl_state);
2161
2162   return poss;
2163 }
2164
2165 /**********************************************************************/
2166 /*                       Directory Operations                         */
2167 /**********************************************************************/
2168
2169 /* Open the directory where completion will begin from, if possible. */
2170 static CompletionDir*
2171 open_ref_dir (gchar           *text_to_complete,
2172               gchar          **remaining_text,
2173               CompletionState *cmpl_state)
2174 {
2175   gchar* first_slash;
2176   CompletionDir *new_dir;
2177
2178   first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2179
2180 #ifdef G_WITH_CYGWIN
2181   if (text_to_complete[0] == '/' && text_to_complete[1] == '/')
2182     {
2183       char root_dir[5];
2184       sprintf (root_dir, "//%c", text_to_complete[2]);
2185
2186       new_dir = open_dir (root_dir, cmpl_state);
2187
2188       if (new_dir) {
2189         *remaining_text = text_to_complete + 4;
2190       }
2191     }
2192 #else
2193   if (FALSE)
2194     ;
2195 #endif
2196 #ifdef HAVE_PWD_H
2197   else if (text_to_complete[0] == '~')
2198     {
2199       new_dir = open_user_dir (text_to_complete, cmpl_state);
2200
2201       if (new_dir)
2202         {
2203           if (first_slash)
2204             *remaining_text = first_slash + 1;
2205           else
2206             *remaining_text = text_to_complete + strlen (text_to_complete);
2207         }
2208       else
2209         {
2210           return NULL;
2211         }
2212     }
2213 #endif
2214   else if (g_path_is_absolute (text_to_complete) || !cmpl_state->reference_dir)
2215     {
2216       gchar *tmp = g_strdup (text_to_complete);
2217       gchar *p;
2218
2219       p = tmp;
2220       while (*p && *p != '*' && *p != '?')
2221         p++;
2222
2223       *p = '\0';
2224       p = strrchr (tmp, G_DIR_SEPARATOR);
2225       if (p)
2226         {
2227           if (p == tmp)
2228             p++;
2229       
2230           *p = '\0';
2231
2232           new_dir = open_dir (tmp, cmpl_state);
2233
2234           if (new_dir)
2235             *remaining_text = text_to_complete + 
2236               ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp));
2237         }
2238       else
2239         {
2240           /* If no possible candidates, use the cwd */
2241           gchar *sys_curdir = g_get_current_dir ();
2242           gchar *utf8_curdir = g_filename_to_utf8 (sys_curdir, -1, NULL, NULL, NULL);
2243
2244           g_free (sys_curdir);
2245
2246           new_dir = open_dir (utf8_curdir, cmpl_state);
2247
2248           if (new_dir)
2249             *remaining_text = text_to_complete;
2250
2251           g_free (utf8_curdir);
2252         }
2253
2254       g_free (tmp);
2255     }
2256   else
2257     {
2258       *remaining_text = text_to_complete;
2259
2260       new_dir = open_dir (cmpl_state->reference_dir->fullname, cmpl_state);
2261     }
2262
2263   if (new_dir)
2264     {
2265       new_dir->cmpl_index = -1;
2266       new_dir->cmpl_parent = NULL;
2267     }
2268
2269   return new_dir;
2270 }
2271
2272 #ifdef HAVE_PWD_H
2273
2274 /* open a directory by user name */
2275 static CompletionDir*
2276 open_user_dir (gchar           *text_to_complete,
2277                CompletionState *cmpl_state)
2278 {
2279   CompletionDir *result;
2280   gchar *first_slash;
2281   gint cmp_len;
2282
2283   g_assert (text_to_complete && text_to_complete[0] == '~');
2284
2285   first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2286
2287   if (first_slash)
2288     cmp_len = first_slash - text_to_complete - 1;
2289   else
2290     cmp_len = strlen (text_to_complete + 1);
2291
2292   if (!cmp_len)
2293     {
2294       /* ~/ */
2295       gchar *homedir = g_get_home_dir ();
2296       gchar *utf8_homedir = g_filename_to_utf8 (homedir, -1, NULL, NULL, NULL);
2297
2298       g_free (homedir);
2299
2300       if (utf8_homedir)
2301         result = open_dir (utf8_homedir, cmpl_state);
2302       else
2303         result = NULL;
2304       
2305       g_free (utf8_homedir);
2306     }
2307   else
2308     {
2309       /* ~user/ */
2310       gchar* copy = g_new (char, cmp_len + 1);
2311       gchar *utf8_dir;
2312       struct passwd *pwd;
2313
2314       strncpy (copy, text_to_complete + 1, cmp_len);
2315       copy[cmp_len] = 0;
2316       pwd = getpwnam (copy);
2317       g_free (copy);
2318       if (!pwd)
2319         {
2320           cmpl_errno = errno;
2321           return NULL;
2322         }
2323       utf8_dir = g_filename_to_utf8 (pwd->pw_dir, -1, NULL, NULL, NULL);
2324       result = open_dir (utf8_dir, cmpl_state);
2325       g_free (utf8_dir);
2326     }
2327   return result;
2328 }
2329
2330 #endif
2331
2332 /* open a directory relative the the current relative directory */
2333 static CompletionDir*
2334 open_relative_dir (gchar           *dir_name,
2335                    CompletionDir   *dir,
2336                    CompletionState *cmpl_state)
2337 {
2338   CompletionDir *result;
2339   GString *path;
2340
2341   path = g_string_sized_new (dir->fullname_len + strlen (dir_name) + 10);
2342   g_string_assign (path, dir->fullname);
2343
2344   if (dir->fullname_len > 1
2345       && path->str[dir->fullname_len - 1] != G_DIR_SEPARATOR)
2346     g_string_append_c (path, G_DIR_SEPARATOR);
2347   g_string_append (path, dir_name);
2348
2349   result = open_dir (path->str, cmpl_state);
2350
2351   g_string_free (path, TRUE);
2352
2353   return result;
2354 }
2355
2356 /* after the cache lookup fails, really open a new directory */
2357 static CompletionDirSent*
2358 open_new_dir (gchar       *dir_name,
2359               struct stat *sbuf,
2360               gboolean     stat_subdirs)
2361 {
2362   CompletionDirSent *sent;
2363   DIR *directory;
2364   struct dirent *dirent_ptr;
2365   gint entry_count = 0;
2366   gint n_entries = 0;
2367   gint i;
2368   struct stat ent_sbuf;
2369   GString *path;
2370   gchar *sys_dir_name;
2371
2372   sent = g_new (CompletionDirSent, 1);
2373   sent->mtime = sbuf->st_mtime;
2374   sent->inode = sbuf->st_ino;
2375   sent->device = sbuf->st_dev;
2376
2377   path = g_string_sized_new (2*MAXPATHLEN + 10);
2378
2379   sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
2380   if (!sys_dir_name)
2381     {
2382       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2383       return NULL;
2384     }
2385   
2386   directory = opendir (sys_dir_name);
2387   if (!directory)
2388     {
2389       cmpl_errno = errno;
2390       g_free (sys_dir_name);
2391       return NULL;
2392     }
2393
2394   while ((dirent_ptr = readdir (directory)) != NULL)
2395     entry_count++;
2396
2397   sent->entries = g_new (CompletionDirEntry, entry_count);
2398   sent->entry_count = entry_count;
2399
2400   rewinddir (directory);
2401
2402   for (i = 0; i < entry_count; i += 1)
2403     {
2404       dirent_ptr = readdir (directory);
2405
2406       if (!dirent_ptr)
2407         {
2408           cmpl_errno = errno;
2409           closedir (directory);
2410           g_free (sys_dir_name);
2411           return NULL;
2412         }
2413
2414       sent->entries[n_entries].entry_name = g_filename_to_utf8 (dirent_ptr->d_name, -1, NULL, NULL, NULL);
2415       if (!g_utf8_validate (sent->entries[n_entries].entry_name, -1, NULL))
2416         {
2417           g_warning (_("The filename %s couldn't be converted to UTF-8. Try setting the environment variable G_BROKEN_FILENAMES."), dirent_ptr->d_name);
2418           continue;
2419         }
2420       n_entries++;
2421
2422       g_string_assign (path, sys_dir_name);
2423       if (path->str[path->len-1] != G_DIR_SEPARATOR)
2424         {
2425           g_string_append_c (path, G_DIR_SEPARATOR);
2426         }
2427       g_string_append (path, dirent_ptr->d_name);
2428
2429       if (stat_subdirs)
2430         {
2431           /* Here we know path->str is a "system charset" string */
2432           if (stat (path->str, &ent_sbuf) >= 0 && S_ISDIR (ent_sbuf.st_mode))
2433             sent->entries[n_entries].is_dir = TRUE;
2434           else
2435             /* stat may fail, and we don't mind, since it could be a
2436              * dangling symlink. */
2437             sent->entries[n_entries].is_dir = FALSE;
2438         }
2439       else
2440         sent->entries[n_entries].is_dir = 1;
2441     }
2442   sent->entry_count = n_entries;
2443   
2444   g_free (sys_dir_name);
2445   g_string_free (path, TRUE);
2446   qsort (sent->entries, sent->entry_count, sizeof (CompletionDirEntry), compare_cmpl_dir);
2447
2448   closedir (directory);
2449
2450   return sent;
2451 }
2452
2453 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
2454
2455 static gboolean
2456 check_dir (gchar       *dir_name,
2457            struct stat *result,
2458            gboolean    *stat_subdirs)
2459 {
2460   /* A list of directories that we know only contain other directories.
2461    * Trying to stat every file in these directories would be very
2462    * expensive.
2463    */
2464
2465   static struct {
2466     gchar *name;
2467     gboolean present;
2468     struct stat statbuf;
2469   } no_stat_dirs[] = {
2470     { "/afs", FALSE, { 0 } },
2471     { "/net", FALSE, { 0 } }
2472   };
2473
2474   static const gint n_no_stat_dirs = G_N_ELEMENTS (no_stat_dirs);
2475   static gboolean initialized = FALSE;
2476   gchar *sys_dir_name;
2477   gint i;
2478
2479   if (!initialized)
2480     {
2481       initialized = TRUE;
2482       for (i = 0; i < n_no_stat_dirs; i++)
2483         {
2484           if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0)
2485             no_stat_dirs[i].present = TRUE;
2486         }
2487     }
2488
2489   sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
2490   if (!sys_dir_name)
2491     {
2492       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2493       return FALSE;
2494     }
2495   
2496   if (stat (sys_dir_name, result) < 0)
2497     {
2498       g_free (sys_dir_name);
2499       cmpl_errno = errno;
2500       return FALSE;
2501     }
2502   g_free (sys_dir_name);
2503
2504   *stat_subdirs = TRUE;
2505   for (i = 0; i < n_no_stat_dirs; i++)
2506     {
2507       if (no_stat_dirs[i].present &&
2508           (no_stat_dirs[i].statbuf.st_dev == result->st_dev) &&
2509           (no_stat_dirs[i].statbuf.st_ino == result->st_ino))
2510         {
2511           *stat_subdirs = FALSE;
2512           break;
2513         }
2514     }
2515
2516   return TRUE;
2517 }
2518
2519 #endif
2520
2521 /* open a directory by absolute pathname */
2522 static CompletionDir*
2523 open_dir (gchar           *dir_name,
2524           CompletionState *cmpl_state)
2525 {
2526   struct stat sbuf;
2527   gboolean stat_subdirs;
2528   CompletionDirSent *sent;
2529   GList* cdsl;
2530
2531 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
2532   if (!check_dir (dir_name, &sbuf, &stat_subdirs))
2533     return NULL;
2534
2535   cdsl = cmpl_state->directory_sent_storage;
2536
2537   while (cdsl)
2538     {
2539       sent = cdsl->data;
2540
2541       if (sent->inode == sbuf.st_ino &&
2542           sent->mtime == sbuf.st_mtime &&
2543           sent->device == sbuf.st_dev)
2544         return attach_dir (sent, dir_name, cmpl_state);
2545
2546       cdsl = cdsl->next;
2547     }
2548 #else
2549   stat_subdirs = TRUE;
2550 #endif
2551
2552   sent = open_new_dir (dir_name, &sbuf, stat_subdirs);
2553
2554   if (sent)
2555     {
2556       cmpl_state->directory_sent_storage =
2557         g_list_prepend (cmpl_state->directory_sent_storage, sent);
2558
2559       return attach_dir (sent, dir_name, cmpl_state);
2560     }
2561
2562   return NULL;
2563 }
2564
2565 static CompletionDir*
2566 attach_dir (CompletionDirSent *sent,
2567             gchar             *dir_name,
2568             CompletionState   *cmpl_state)
2569 {
2570   CompletionDir* new_dir;
2571
2572   new_dir = g_new (CompletionDir, 1);
2573
2574   cmpl_state->directory_storage =
2575     g_list_prepend (cmpl_state->directory_storage, new_dir);
2576
2577   new_dir->sent = sent;
2578   new_dir->fullname = g_strdup (dir_name);
2579   new_dir->fullname_len = strlen (dir_name);
2580
2581   return new_dir;
2582 }
2583
2584 static gint
2585 correct_dir_fullname (CompletionDir* cmpl_dir)
2586 {
2587   gint length = strlen (cmpl_dir->fullname);
2588   gchar *first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
2589   gchar *sys_filename;
2590   struct stat sbuf;
2591
2592   /* Does it end with /. (\.) ? */
2593   if (length >= 2 &&
2594       strcmp (cmpl_dir->fullname + length - 2, G_DIR_SEPARATOR_S ".") == 0)
2595     {
2596       /* Is it just the root directory (on a drive) ? */
2597       if (cmpl_dir->fullname + length - 2 == first_slash)
2598         {
2599           cmpl_dir->fullname[length - 1] = 0;
2600           cmpl_dir->fullname_len = length - 1;
2601           return TRUE;
2602         }
2603       else
2604         {
2605           cmpl_dir->fullname[length - 2] = 0;
2606         }
2607     }
2608
2609   /* Ends with /./ (\.\)? */
2610   else if (length >= 3 &&
2611            strcmp (cmpl_dir->fullname + length - 3,
2612                    G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) == 0)
2613     cmpl_dir->fullname[length - 2] = 0;
2614
2615   /* Ends with /.. (\..) ? */
2616   else if (length >= 3 &&
2617            strcmp (cmpl_dir->fullname + length - 3,
2618                    G_DIR_SEPARATOR_S "..") == 0)
2619     {
2620       /* Is it just /.. (X:\..)? */
2621       if (cmpl_dir->fullname + length - 3 == first_slash)
2622         {
2623           cmpl_dir->fullname[length - 2] = 0;
2624           cmpl_dir->fullname_len = length - 2;
2625           return TRUE;
2626         }
2627
2628       sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
2629       if (!sys_filename)
2630         {
2631           cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2632           return FALSE;
2633         }
2634       
2635       if (stat (sys_filename, &sbuf) < 0)
2636         {
2637           g_free (sys_filename);
2638           cmpl_errno = errno;
2639           return FALSE;
2640         }
2641       g_free (sys_filename);
2642
2643       cmpl_dir->fullname[length - 3] = 0;
2644
2645       if (!correct_parent (cmpl_dir, &sbuf))
2646         return FALSE;
2647     }
2648
2649   /* Ends with /../ (\..\)? */
2650   else if (length >= 4 &&
2651            strcmp (cmpl_dir->fullname + length - 4,
2652                    G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) == 0)
2653     {
2654       /* Is it just /../ (X:\..\)? */
2655       if (cmpl_dir->fullname + length - 4 == first_slash)
2656         {
2657           cmpl_dir->fullname[length - 3] = 0;
2658           cmpl_dir->fullname_len = length - 3;
2659           return TRUE;
2660         }
2661
2662       sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
2663       if (!sys_filename)
2664         {
2665           cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2666           return FALSE;
2667         }
2668       
2669       if (stat (sys_filename, &sbuf) < 0)
2670         {
2671           g_free (sys_filename);
2672           cmpl_errno = errno;
2673           return FALSE;
2674         }
2675       g_free (sys_filename);
2676
2677       cmpl_dir->fullname[length - 4] = 0;
2678
2679       if (!correct_parent (cmpl_dir, &sbuf))
2680         return FALSE;
2681     }
2682
2683   cmpl_dir->fullname_len = strlen (cmpl_dir->fullname);
2684
2685   return TRUE;
2686 }
2687
2688 static gint
2689 correct_parent (CompletionDir *cmpl_dir,
2690                 struct stat   *sbuf)
2691 {
2692   struct stat parbuf;
2693   gchar *last_slash;
2694   gchar *first_slash;
2695   gchar *new_name;
2696   gchar *sys_filename;
2697   gchar c = 0;
2698
2699   last_slash = strrchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
2700   g_assert (last_slash);
2701   first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
2702
2703   /* Clever (?) way to check for top-level directory that works also on
2704    * Win32, where there is a drive letter and colon prefixed...
2705    */
2706   if (last_slash != first_slash)
2707     {
2708       last_slash[0] = 0;
2709     }
2710   else
2711     {
2712       c = last_slash[1];
2713       last_slash[1] = 0;
2714     }
2715
2716   sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
2717   if (!sys_filename)
2718     {
2719       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2720       if (!c)
2721         last_slash[0] = G_DIR_SEPARATOR;
2722       return FALSE;
2723     }
2724   
2725   if (stat (sys_filename, &parbuf) < 0)
2726     {
2727       g_free (sys_filename);
2728       cmpl_errno = errno;
2729       if (!c)
2730         last_slash[0] = G_DIR_SEPARATOR;
2731       return FALSE;
2732     }
2733   g_free (sys_filename);
2734
2735 #ifndef G_OS_WIN32              /* No inode numbers on Win32 */
2736   if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2737     /* it wasn't a link */
2738     return TRUE;
2739
2740   if (c)
2741     last_slash[1] = c;
2742   else
2743     last_slash[0] = G_DIR_SEPARATOR;
2744
2745   /* it was a link, have to figure it out the hard way */
2746
2747   new_name = find_parent_dir_fullname (cmpl_dir->fullname);
2748
2749   if (!new_name)
2750     return FALSE;
2751
2752   g_free (cmpl_dir->fullname);
2753
2754   cmpl_dir->fullname = new_name;
2755 #endif
2756
2757   return TRUE;
2758 }
2759
2760 #ifndef G_OS_WIN32
2761
2762 static gchar*
2763 find_parent_dir_fullname (gchar* dirname)
2764 {
2765   gchar *sys_orig_dir;
2766   gchar *result;
2767   gchar *sys_cwd;
2768   gchar *sys_dirname;
2769
2770   sys_orig_dir = g_get_current_dir ();
2771   sys_dirname = g_filename_from_utf8 (dirname, -1, NULL, NULL, NULL);
2772   if (!sys_dirname)
2773     {
2774       g_free (sys_orig_dir);
2775       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2776       return NULL;
2777     }
2778   
2779   if (chdir (sys_dirname) != 0 || chdir ("..") != 0)
2780     {
2781       g_free (sys_dirname);
2782       g_free (sys_orig_dir);
2783       cmpl_errno = errno;
2784       return NULL;
2785     }
2786   g_free (sys_dirname);
2787
2788   sys_cwd = g_get_current_dir ();
2789   result = g_filename_to_utf8 (sys_cwd, -1, NULL, NULL, NULL);
2790   g_free (sys_cwd);
2791
2792   if (chdir (sys_orig_dir) != 0)
2793     {
2794       cmpl_errno = errno;
2795       g_free (sys_orig_dir);
2796       return NULL;
2797     }
2798
2799   g_free (sys_orig_dir);
2800   return result;
2801 }
2802
2803 #endif
2804
2805 /**********************************************************************/
2806 /*                        Completion Operations                       */
2807 /**********************************************************************/
2808
2809 #ifdef HAVE_PWD_H
2810
2811 static PossibleCompletion*
2812 attempt_homedir_completion (gchar           *text_to_complete,
2813                             CompletionState *cmpl_state)
2814 {
2815   gint index, length;
2816
2817   if (!cmpl_state->user_dir_name_buffer &&
2818       !get_pwdb (cmpl_state))
2819     return NULL;
2820   length = strlen (text_to_complete) - 1;
2821
2822   cmpl_state->user_completion_index += 1;
2823
2824   while (cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2825     {
2826       index = first_diff_index (text_to_complete + 1,
2827                                 cmpl_state->user_directories
2828                                 [cmpl_state->user_completion_index].login);
2829
2830       switch (index)
2831         {
2832         case PATTERN_MATCH:
2833           break;
2834         default:
2835           if (cmpl_state->last_valid_char < (index + 1))
2836             cmpl_state->last_valid_char = index + 1;
2837           cmpl_state->user_completion_index += 1;
2838           continue;
2839         }
2840
2841       cmpl_state->the_completion.is_a_completion = 1;
2842       cmpl_state->the_completion.is_directory = TRUE;
2843
2844       append_completion_text ("~", cmpl_state);
2845
2846       append_completion_text (cmpl_state->
2847                               user_directories[cmpl_state->user_completion_index].login,
2848                               cmpl_state);
2849
2850       return append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
2851     }
2852
2853   if (text_to_complete[1]
2854       || cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2855     {
2856       cmpl_state->user_completion_index = -1;
2857       return NULL;
2858     }
2859   else
2860     {
2861       cmpl_state->user_completion_index += 1;
2862       cmpl_state->the_completion.is_a_completion = 1;
2863       cmpl_state->the_completion.is_directory = TRUE;
2864
2865       return append_completion_text ("~" G_DIR_SEPARATOR_S, cmpl_state);
2866     }
2867 }
2868
2869 #endif
2870
2871 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
2872 #define FOLD(c) (tolower(c))
2873 #else
2874 #define FOLD(c) (c)
2875 #endif
2876
2877 /* returns the index (>= 0) of the first differing character,
2878  * PATTERN_MATCH if the completion matches */
2879 static gint
2880 first_diff_index (gchar *pat,
2881                   gchar *text)
2882 {
2883   gint diff = 0;
2884
2885   while (*pat && *text && FOLD (*text) == FOLD (*pat))
2886     {
2887       pat += 1;
2888       text += 1;
2889       diff += 1;
2890     }
2891
2892   if (*pat)
2893     return diff;
2894
2895   return PATTERN_MATCH;
2896 }
2897
2898 static PossibleCompletion*
2899 append_completion_text (gchar           *text,
2900                         CompletionState *cmpl_state)
2901 {
2902   gint len, i = 1;
2903
2904   if (!cmpl_state->the_completion.text)
2905     return NULL;
2906
2907   len = strlen (text) + strlen (cmpl_state->the_completion.text) + 1;
2908
2909   if (cmpl_state->the_completion.text_alloc > len)
2910     {
2911       strcat (cmpl_state->the_completion.text, text);
2912       return &cmpl_state->the_completion;
2913     }
2914
2915   while (i < len)
2916     i <<= 1;
2917
2918   cmpl_state->the_completion.text_alloc = i;
2919
2920   cmpl_state->the_completion.text = (gchar*) g_realloc (cmpl_state->the_completion.text, i);
2921
2922   if (!cmpl_state->the_completion.text)
2923     return NULL;
2924   else
2925     {
2926       strcat (cmpl_state->the_completion.text, text);
2927       return &cmpl_state->the_completion;
2928     }
2929 }
2930
2931 static CompletionDir*
2932 find_completion_dir (gchar          *text_to_complete,
2933                     gchar          **remaining_text,
2934                     CompletionState *cmpl_state)
2935 {
2936   gchar* first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2937   CompletionDir* dir = cmpl_state->reference_dir;
2938   CompletionDir* next;
2939   *remaining_text = text_to_complete;
2940
2941   while (first_slash)
2942     {
2943       gint len = first_slash - *remaining_text;
2944       gint found = 0;
2945       gchar *found_name = NULL;         /* Quiet gcc */
2946       gint i;
2947       gchar* pat_buf = g_new (gchar, len + 1);
2948
2949       strncpy (pat_buf, *remaining_text, len);
2950       pat_buf[len] = 0;
2951
2952       for (i = 0; i < dir->sent->entry_count; i += 1)
2953         {
2954           if (dir->sent->entries[i].is_dir &&
2955              fnmatch (pat_buf, dir->sent->entries[i].entry_name,
2956                       FNMATCH_FLAGS)!= FNM_NOMATCH)
2957             {
2958               if (found)
2959                 {
2960                   g_free (pat_buf);
2961                   return dir;
2962                 }
2963               else
2964                 {
2965                   found = 1;
2966                   found_name = dir->sent->entries[i].entry_name;
2967                 }
2968             }
2969         }
2970
2971       if (!found)
2972         {
2973           /* Perhaps we are trying to open an automount directory */
2974           found_name = pat_buf;
2975         }
2976
2977       next = open_relative_dir (found_name, dir, cmpl_state);
2978       
2979       if (!next)
2980         {
2981           g_free (pat_buf);
2982           return NULL;
2983         }
2984       
2985       next->cmpl_parent = dir;
2986       
2987       dir = next;
2988       
2989       if (!correct_dir_fullname (dir))
2990         {
2991           g_free (pat_buf);
2992           return NULL;
2993         }
2994       
2995       *remaining_text = first_slash + 1;
2996       first_slash = strchr (*remaining_text, G_DIR_SEPARATOR);
2997
2998       g_free (pat_buf);
2999     }
3000
3001   return dir;
3002 }
3003
3004 static void
3005 update_cmpl (PossibleCompletion *poss,
3006              CompletionState    *cmpl_state)
3007 {
3008   gint cmpl_len;
3009
3010   if (!poss || !cmpl_is_a_completion (poss))
3011     return;
3012
3013   cmpl_len = strlen (cmpl_this_completion (poss));
3014
3015   if (cmpl_state->updated_text_alloc < cmpl_len + 1)
3016     {
3017       cmpl_state->updated_text =
3018         (gchar*)g_realloc (cmpl_state->updated_text,
3019                            cmpl_state->updated_text_alloc);
3020       cmpl_state->updated_text_alloc = 2*cmpl_len;
3021     }
3022
3023   if (cmpl_state->updated_text_len < 0)
3024     {
3025       strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3026       cmpl_state->updated_text_len = cmpl_len;
3027       cmpl_state->re_complete = cmpl_is_directory (poss);
3028     }
3029   else if (cmpl_state->updated_text_len == 0)
3030     {
3031       cmpl_state->re_complete = FALSE;
3032     }
3033   else
3034     {
3035       gint first_diff =
3036         first_diff_index (cmpl_state->updated_text,
3037                           cmpl_this_completion (poss));
3038
3039       cmpl_state->re_complete = FALSE;
3040
3041       if (first_diff == PATTERN_MATCH)
3042         return;
3043
3044       if (first_diff > cmpl_state->updated_text_len)
3045         strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3046
3047       cmpl_state->updated_text_len = first_diff;
3048       cmpl_state->updated_text[first_diff] = 0;
3049     }
3050 }
3051
3052 static PossibleCompletion*
3053 attempt_file_completion (CompletionState *cmpl_state)
3054 {
3055   gchar *pat_buf, *first_slash;
3056   CompletionDir *dir = cmpl_state->active_completion_dir;
3057
3058   dir->cmpl_index += 1;
3059
3060   if (dir->cmpl_index == dir->sent->entry_count)
3061     {
3062       if (dir->cmpl_parent == NULL)
3063         {
3064           cmpl_state->active_completion_dir = NULL;
3065
3066           return NULL;
3067         }
3068       else
3069         {
3070           cmpl_state->active_completion_dir = dir->cmpl_parent;
3071
3072           return attempt_file_completion (cmpl_state);
3073         }
3074     }
3075
3076   g_assert (dir->cmpl_text);
3077
3078   first_slash = strchr (dir->cmpl_text, G_DIR_SEPARATOR);
3079
3080   if (first_slash)
3081     {
3082       gint len = first_slash - dir->cmpl_text;
3083
3084       pat_buf = g_new (gchar, len + 1);
3085       strncpy (pat_buf, dir->cmpl_text, len);
3086       pat_buf[len] = 0;
3087     }
3088   else
3089     {
3090       gint len = strlen (dir->cmpl_text);
3091
3092       pat_buf = g_new (gchar, len + 2);
3093       strcpy (pat_buf, dir->cmpl_text);
3094       /* Don't append a * if the user entered one herself.
3095        * This way one can complete *.h and don't get matches
3096        * on any .help files, for instance.
3097        */
3098       if (strchr (pat_buf, '*') == NULL)
3099         strcpy (pat_buf + len, "*");
3100     }
3101
3102   if (first_slash)
3103     {
3104       if (dir->sent->entries[dir->cmpl_index].is_dir)
3105         {
3106           if (fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3107                        FNMATCH_FLAGS) != FNM_NOMATCH)
3108             {
3109               CompletionDir* new_dir;
3110
3111               new_dir = open_relative_dir (dir->sent->entries[dir->cmpl_index].entry_name,
3112                                            dir, cmpl_state);
3113
3114               if (!new_dir)
3115                 {
3116                   g_free (pat_buf);
3117                   return NULL;
3118                 }
3119
3120               new_dir->cmpl_parent = dir;
3121
3122               new_dir->cmpl_index = -1;
3123               new_dir->cmpl_text = first_slash + 1;
3124
3125               cmpl_state->active_completion_dir = new_dir;
3126
3127               g_free (pat_buf);
3128               return attempt_file_completion (cmpl_state);
3129             }
3130           else
3131             {
3132               g_free (pat_buf);
3133               return attempt_file_completion (cmpl_state);
3134             }
3135         }
3136       else
3137         {
3138           g_free (pat_buf);
3139           return attempt_file_completion (cmpl_state);
3140         }
3141     }
3142   else
3143     {
3144       if (dir->cmpl_parent != NULL)
3145         {
3146           append_completion_text (dir->fullname +
3147                                   strlen (cmpl_state->completion_dir->fullname) + 1,
3148                                   cmpl_state);
3149           append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3150         }
3151
3152       append_completion_text (dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
3153
3154       cmpl_state->the_completion.is_a_completion =
3155         fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3156                  FNMATCH_FLAGS) != FNM_NOMATCH;
3157
3158       cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3159       if (dir->sent->entries[dir->cmpl_index].is_dir)
3160         append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3161
3162       g_free (pat_buf);
3163       return &cmpl_state->the_completion;
3164     }
3165 }
3166
3167 #ifdef HAVE_PWD_H
3168
3169 static gint
3170 get_pwdb (CompletionState* cmpl_state)
3171 {
3172   struct passwd *pwd_ptr;
3173   gchar* buf_ptr;
3174   gchar *utf8;
3175   gint len = 0, i, count = 0;
3176
3177   if (cmpl_state->user_dir_name_buffer)
3178     return TRUE;
3179   setpwent ();
3180
3181   while ((pwd_ptr = getpwent ()) != NULL)
3182     {
3183       utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3184       len += strlen (utf8);
3185       g_free (utf8);
3186       utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3187       len += strlen (utf8);
3188       g_free (utf8);
3189       len += 2;
3190       count += 1;
3191     }
3192
3193   setpwent ();
3194
3195   cmpl_state->user_dir_name_buffer = g_new (gchar, len);
3196   cmpl_state->user_directories = g_new (CompletionUserDir, count);
3197   cmpl_state->user_directories_len = count;
3198
3199   buf_ptr = cmpl_state->user_dir_name_buffer;
3200
3201   for (i = 0; i < count; i += 1)
3202     {
3203       pwd_ptr = getpwent ();
3204       if (!pwd_ptr)
3205         {
3206           cmpl_errno = errno;
3207           goto error;
3208         }
3209
3210       utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3211       strcpy (buf_ptr, utf8);
3212       g_free (utf8);
3213
3214       cmpl_state->user_directories[i].login = buf_ptr;
3215
3216       buf_ptr += strlen (buf_ptr);
3217       buf_ptr += 1;
3218
3219       utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3220       strcpy (buf_ptr, utf8);
3221       g_free (utf8);
3222
3223       cmpl_state->user_directories[i].homedir = buf_ptr;
3224
3225       buf_ptr += strlen (buf_ptr);
3226       buf_ptr += 1;
3227     }
3228
3229   qsort (cmpl_state->user_directories,
3230          cmpl_state->user_directories_len,
3231          sizeof (CompletionUserDir),
3232          compare_user_dir);
3233
3234   endpwent ();
3235
3236   return TRUE;
3237
3238 error:
3239
3240   if (cmpl_state->user_dir_name_buffer)
3241     g_free (cmpl_state->user_dir_name_buffer);
3242   if (cmpl_state->user_directories)
3243     g_free (cmpl_state->user_directories);
3244
3245   cmpl_state->user_dir_name_buffer = NULL;
3246   cmpl_state->user_directories = NULL;
3247
3248   return FALSE;
3249 }
3250
3251 static gint
3252 compare_user_dir (const void *a,
3253                   const void *b)
3254 {
3255   return strcmp ((((CompletionUserDir*)a))->login,
3256                  (((CompletionUserDir*)b))->login);
3257 }
3258
3259 #endif
3260
3261 static gint
3262 compare_cmpl_dir (const void *a,
3263                   const void *b)
3264 {
3265 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
3266   return strcmp ((((CompletionDirEntry*)a))->entry_name,
3267                  (((CompletionDirEntry*)b))->entry_name);
3268 #else
3269   return g_strcasecmp ((((CompletionDirEntry*)a))->entry_name,
3270                        (((CompletionDirEntry*)b))->entry_name);
3271 #endif
3272 }
3273
3274 static gint
3275 cmpl_state_okay (CompletionState* cmpl_state)
3276 {
3277   return  cmpl_state && cmpl_state->reference_dir;
3278 }
3279
3280 static gchar*
3281 cmpl_strerror (gint err)
3282 {
3283   if (err == CMPL_ERRNO_TOO_LONG)
3284     return _("Name too long");
3285   else if (err == CMPL_ERRNO_DID_NOT_CONVERT)
3286     return _("Couldn't convert filename");
3287   else
3288     return g_strerror (err);
3289 }