]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilesel.c
voc Pennington <hp@redhat.com>
[~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 static gboolean
576 grab_default (GtkWidget *widget)
577 {
578   gtk_widget_grab_default (widget);
579   return FALSE;
580 }
581      
582 static void
583 gtk_file_selection_init (GtkFileSelection *filesel)
584 {
585   GtkWidget *entry_vbox;
586   GtkWidget *label;
587   GtkWidget *list_hbox;
588   GtkWidget *confirm_area;
589   GtkWidget *pulldown_hbox;
590   GtkWidget *scrolled_win;
591   GtkDialog *dialog;
592   
593   char *dir_title [2];
594   char *file_title [2];
595
596   dialog = GTK_DIALOG (filesel);
597
598   filesel->cmpl_state = cmpl_init_state ();
599
600   /* The dialog-sized vertical box  */
601   filesel->main_vbox = dialog->vbox;
602   gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
603
604   /* The horizontal box containing create, rename etc. buttons */
605   filesel->button_area = gtk_hbutton_box_new ();
606   gtk_button_box_set_layout (GTK_BUTTON_BOX (filesel->button_area), GTK_BUTTONBOX_START);
607   gtk_box_set_spacing (GTK_BOX (filesel->button_area), 0);
608   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area, 
609                       FALSE, FALSE, 0);
610   gtk_widget_show (filesel->button_area);
611   
612   gtk_file_selection_show_fileop_buttons (filesel);
613
614   /* hbox for pulldown menu */
615   pulldown_hbox = gtk_hbox_new (TRUE, 5);
616   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
617   gtk_widget_show (pulldown_hbox);
618   
619   /* Pulldown menu */
620   filesel->history_pulldown = gtk_option_menu_new ();
621   gtk_widget_show (filesel->history_pulldown);
622   gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown, 
623                       FALSE, FALSE, 0);
624     
625   /*  The horizontal box containing the directory and file listboxes  */
626   list_hbox = gtk_hbox_new (FALSE, 5);
627   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
628   gtk_widget_show (list_hbox);
629
630   /* The directories clist */
631   dir_title[0] = _("Directories");
632   dir_title[1] = NULL;
633   filesel->dir_list = gtk_clist_new_with_titles (1, (gchar**) dir_title);
634   gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
635   gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
636                       (GtkSignalFunc) gtk_file_selection_dir_button, 
637                       (gpointer) filesel);
638   gtk_clist_set_column_auto_resize (GTK_CLIST (filesel->dir_list), 0, TRUE);
639   gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
640
641   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
642   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list);
643   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
644                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
645   gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5);
646   gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0);
647   gtk_widget_show (filesel->dir_list);
648   gtk_widget_show (scrolled_win);
649
650   /* The files clist */
651   file_title[0] = _("Files");
652   file_title[1] = NULL;
653   filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title);
654   gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
655   gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
656                       (GtkSignalFunc) gtk_file_selection_file_button, 
657                       (gpointer) filesel);
658   gtk_clist_set_column_auto_resize (GTK_CLIST (filesel->file_list), 0, TRUE);
659   gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
660
661   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
662   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list);
663   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
664                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
665   gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5);
666   gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0);
667   gtk_widget_show (filesel->file_list);
668   gtk_widget_show (scrolled_win);
669
670   /* action area for packing buttons into. */
671   filesel->action_area = gtk_hbox_new (TRUE, 0);
672   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area, 
673                       FALSE, FALSE, 0);
674   gtk_widget_show (filesel->action_area);
675   
676   /*  The OK/Cancel button area */
677   confirm_area = dialog->action_area;
678
679   /*  The OK button  */
680   filesel->ok_button = gtk_dialog_add_button (dialog,
681                                               GTK_STOCK_BUTTON_OK,
682                                               GTK_RESPONSE_OK);
683   
684   gtk_widget_grab_default (filesel->ok_button);
685
686   /*  The Cancel button  */
687   filesel->cancel_button = gtk_dialog_add_button (dialog,
688                                                   GTK_STOCK_BUTTON_CANCEL,
689                                                   GTK_RESPONSE_CANCEL);
690
691   /*  The selection entry widget  */
692   entry_vbox = gtk_vbox_new (FALSE, 2);
693   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 2);
694   gtk_widget_show (entry_vbox);
695
696   filesel->selection_text = label = gtk_label_new ("");
697   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
698   gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
699   gtk_widget_show (label);
700
701   filesel->selection_entry = gtk_entry_new ();
702   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
703                       (GtkSignalFunc) gtk_file_selection_key_press, filesel);
704   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "insert_text",
705                       (GtkSignalFunc) gtk_file_selection_insert_text, NULL);
706   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
707                              (GtkSignalFunc) grab_default,
708                              GTK_OBJECT (filesel->ok_button));
709   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
710                              (GtkSignalFunc) gtk_button_clicked,
711                              GTK_OBJECT (filesel->ok_button));
712   gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
713   gtk_widget_show (filesel->selection_entry);
714
715   if (!cmpl_state_okay (filesel->cmpl_state))
716     {
717       gchar err_buf[256];
718
719       sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
720
721       gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
722     }
723   else
724     {
725       gtk_file_selection_populate (filesel, "", FALSE);
726     }
727
728   gtk_widget_grab_focus (filesel->selection_entry);
729 }
730
731 GtkWidget*
732 gtk_file_selection_new (const gchar *title)
733 {
734   GtkFileSelection *filesel;
735
736   filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION);
737   gtk_window_set_title (GTK_WINDOW (filesel), title);
738   gtk_dialog_set_has_separator (GTK_DIALOG (filesel), FALSE);
739
740   return GTK_WIDGET (filesel);
741 }
742
743 void
744 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
745 {
746   g_return_if_fail (filesel != NULL);
747   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
748     
749   /* delete, create directory, and rename */
750   if (!filesel->fileop_c_dir) 
751     {
752       filesel->fileop_c_dir = gtk_button_new_with_label (_("Create Dir"));
753       gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
754                           (GtkSignalFunc) gtk_file_selection_create_dir, 
755                           (gpointer) filesel);
756       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
757                           filesel->fileop_c_dir, TRUE, TRUE, 0);
758       gtk_widget_show (filesel->fileop_c_dir);
759     }
760         
761   if (!filesel->fileop_del_file) 
762     {
763       filesel->fileop_del_file = gtk_button_new_with_label (_("Delete File"));
764       gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
765                           (GtkSignalFunc) gtk_file_selection_delete_file, 
766                           (gpointer) filesel);
767       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
768                           filesel->fileop_del_file, TRUE, TRUE, 0);
769       gtk_widget_show (filesel->fileop_del_file);
770     }
771
772   if (!filesel->fileop_ren_file)
773     {
774       filesel->fileop_ren_file = gtk_button_new_with_label (_("Rename File"));
775       gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
776                           (GtkSignalFunc) gtk_file_selection_rename_file, 
777                           (gpointer) filesel);
778       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
779                           filesel->fileop_ren_file, TRUE, TRUE, 0);
780       gtk_widget_show (filesel->fileop_ren_file);
781     }
782   g_object_notify (G_OBJECT (filesel), "show_fileops");
783   gtk_widget_queue_resize (GTK_WIDGET (filesel));
784 }
785
786 void       
787 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
788 {
789   g_return_if_fail (filesel != NULL);
790   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
791     
792   if (filesel->fileop_ren_file)
793     {
794       gtk_widget_destroy (filesel->fileop_ren_file);
795       filesel->fileop_ren_file = NULL;
796     }
797
798   if (filesel->fileop_del_file)
799     {
800       gtk_widget_destroy (filesel->fileop_del_file);
801       filesel->fileop_del_file = NULL;
802     }
803
804   if (filesel->fileop_c_dir)
805     {
806       gtk_widget_destroy (filesel->fileop_c_dir);
807       filesel->fileop_c_dir = NULL;
808     }
809   g_object_notify (G_OBJECT (filesel), "show_fileops");
810 }
811
812
813
814 void
815 gtk_file_selection_set_filename (GtkFileSelection *filesel,
816                                  const gchar      *filename)
817 {
818   gchar *buf;
819   const char *name, *last_slash;
820
821   g_return_if_fail (filesel != NULL);
822   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
823   g_return_if_fail (filename != NULL);
824
825   last_slash = strrchr (filename, G_DIR_SEPARATOR);
826
827   if (!last_slash)
828     {
829       buf = g_strdup ("");
830       name = filename;
831     }
832   else
833     {
834       buf = g_strdup (filename);
835       buf[last_slash - filename + 1] = 0;
836       name = last_slash + 1;
837     }
838
839   gtk_file_selection_populate (filesel, buf, FALSE);
840
841   if (filesel->selection_entry)
842     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
843   g_free (buf);
844   g_object_notify (G_OBJECT (filesel), "filename");
845 }
846
847 /**
848  * gtk_file_selection_get_filename:
849  * @filesel: a #GtkFileSelection
850  * 
851  * This function returns the selected filename in the C runtime's
852  * multibyte string encoding, which may or may not be the same as that
853  * used by GTK+ (UTF-8). To convert to UTF-8, call g_filename_to_utf8().
854  * The returned string points to a statically allocated buffer and
855  * should be copied if you plan to keep it around.
856  * 
857  * Return value: currently-selected filename in locale's encoding
858  **/
859 G_CONST_RETURN gchar*
860 gtk_file_selection_get_filename (GtkFileSelection *filesel)
861 {
862   static gchar nothing[2] = "";
863   static gchar something[MAXPATHLEN*2];
864   char *sys_filename;
865   char *text;
866
867   g_return_val_if_fail (filesel != NULL, nothing);
868   g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
869
870 #ifdef G_WITH_CYGWIN
871   translate_win32_path (filesel);
872 #endif
873   text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
874   if (text)
875     {
876       sys_filename = g_filename_from_utf8 (cmpl_completion_fullname (text, filesel->cmpl_state), -1, NULL, NULL, NULL);
877       if (!sys_filename)
878         return nothing;
879       strncpy (something, sys_filename, sizeof (something));
880       g_free (sys_filename);
881       return something;
882     }
883
884   return nothing;
885 }
886
887 void
888 gtk_file_selection_complete (GtkFileSelection *filesel,
889                              const gchar      *pattern)
890 {
891   g_return_if_fail (filesel != NULL);
892   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
893   g_return_if_fail (pattern != NULL);
894
895   if (filesel->selection_entry)
896     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern);
897   gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE);
898 }
899
900 static void
901 gtk_file_selection_destroy (GtkObject *object)
902 {
903   GtkFileSelection *filesel;
904   GList *list;
905   HistoryCallbackArg *callback_arg;
906   
907   g_return_if_fail (GTK_IS_FILE_SELECTION (object));
908   
909   filesel = GTK_FILE_SELECTION (object);
910   
911   if (filesel->fileop_dialog)
912     {
913       gtk_widget_destroy (filesel->fileop_dialog);
914       filesel->fileop_dialog = NULL;
915     }
916   
917   if (filesel->history_list)
918     {
919       list = filesel->history_list;
920       while (list)
921         {
922           callback_arg = list->data;
923           g_free (callback_arg->directory);
924           g_free (callback_arg);
925           list = list->next;
926         }
927       g_list_free (filesel->history_list);
928       filesel->history_list = NULL;
929     }
930
931   if (filesel->cmpl_state)
932     {
933       cmpl_free_state (filesel->cmpl_state);
934       filesel->cmpl_state = NULL;
935     }
936   
937   GTK_OBJECT_CLASS (parent_class)->destroy (object);
938 }
939
940 /* Begin file operations callbacks */
941
942 static void
943 gtk_file_selection_fileop_error (GtkFileSelection *fs,
944                                  gchar            *error_message)
945 {
946   GtkWidget *dialog;
947     
948   g_return_if_fail (error_message != NULL);
949
950   /* main dialog */
951   dialog = gtk_message_dialog_new (GTK_WINDOW (fs),
952                                    GTK_DIALOG_DESTROY_WITH_PARENT,
953                                    GTK_MESSAGE_ERROR,
954                                    GTK_BUTTONS_CLOSE,
955                                    "%s", error_message);
956
957   /* yes, we free it */
958   g_free (error_message);
959
960   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
961
962   gtk_signal_connect_object (GTK_OBJECT (dialog), "response",
963                              (GtkSignalFunc) gtk_widget_destroy, 
964                              (gpointer) dialog);
965
966   gtk_widget_show (dialog);
967 }
968
969 static void
970 gtk_file_selection_fileop_destroy (GtkWidget *widget,
971                                    gpointer   data)
972 {
973   GtkFileSelection *fs = data;
974
975   g_return_if_fail (fs != NULL);
976   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
977   
978   fs->fileop_dialog = NULL;
979 }
980
981
982 static void
983 gtk_file_selection_create_dir_confirmed (GtkWidget *widget,
984                                          gpointer   data)
985 {
986   GtkFileSelection *fs = data;
987   gchar *dirname;
988   gchar *path;
989   gchar *full_path;
990   gchar *sys_full_path;
991   gchar *buf;
992   GError *error = NULL;
993   CompletionState *cmpl_state;
994   
995   g_return_if_fail (fs != NULL);
996   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
997
998   dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
999   cmpl_state = (CompletionState*) fs->cmpl_state;
1000   path = cmpl_reference_position (cmpl_state);
1001   
1002   full_path = g_strconcat (path, G_DIR_SEPARATOR_S, dirname, NULL);
1003   sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
1004   if (error)
1005     {
1006       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1007         buf = g_strdup_printf (_("The directory name \"%s\" contains symbols that are not allowed in filenames"), dirname);
1008       else
1009         buf = g_strdup_printf (_("Error creating directory \"%s\": %s\n%s"), dirname, error->message,
1010                                _("You probably used symbols not allowed in filenames."));
1011       gtk_file_selection_fileop_error (fs, buf);
1012       g_error_free (error);
1013       goto out;
1014     }
1015
1016   if (mkdir (sys_full_path, 0755) < 0) 
1017     {
1018       buf = g_strdup_printf (_("Error creating directory \"%s\": %s\n"), dirname,
1019                              g_strerror (errno));
1020       gtk_file_selection_fileop_error (fs, buf);
1021     }
1022
1023  out:
1024   g_free (full_path);
1025   g_free (sys_full_path);
1026   
1027   gtk_widget_destroy (fs->fileop_dialog);
1028   gtk_file_selection_populate (fs, "", FALSE);
1029 }
1030   
1031 static void
1032 gtk_file_selection_create_dir (GtkWidget *widget,
1033                                gpointer   data)
1034 {
1035   GtkFileSelection *fs = data;
1036   GtkWidget *label;
1037   GtkWidget *dialog;
1038   GtkWidget *vbox;
1039   GtkWidget *button;
1040
1041   g_return_if_fail (fs != NULL);
1042   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1043
1044   if (fs->fileop_dialog)
1045     return;
1046   
1047   /* main dialog */
1048   dialog = gtk_dialog_new ();
1049   fs->fileop_dialog = dialog;
1050   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1051                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
1052                       (gpointer) fs);
1053   gtk_window_set_title (GTK_WINDOW (dialog), _("Create Directory"));
1054   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1055
1056   /* If file dialog is grabbed, grab option dialog */
1057   /* When option dialog is closed, file dialog will be grabbed again */
1058   if (GTK_WINDOW (fs)->modal)
1059       gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1060
1061   vbox = gtk_vbox_new (FALSE, 0);
1062   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1063   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1064                      FALSE, FALSE, 0);
1065   gtk_widget_show( vbox);
1066   
1067   label = gtk_label_new_with_mnemonic (_("_Directory name:"));
1068   gtk_misc_set_alignment(GTK_MISC (label), 0.0, 0.0);
1069   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1070   gtk_widget_show (label);
1071
1072   /*  The directory entry widget  */
1073   fs->fileop_entry = gtk_entry_new ();
1074   gtk_label_set_mnemonic_widget (GTK_LABEL (label), fs->fileop_entry);
1075   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, 
1076                       TRUE, TRUE, 5);
1077   GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1078   gtk_widget_show (fs->fileop_entry);
1079   
1080   /* buttons */
1081   button = gtk_button_new_with_label (_("Create"));
1082   gtk_signal_connect (GTK_OBJECT (button), "clicked",
1083                       (GtkSignalFunc) gtk_file_selection_create_dir_confirmed, 
1084                       (gpointer) fs);
1085   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1086                      button, TRUE, TRUE, 0);
1087   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1088   gtk_widget_show (button);
1089   
1090   button = gtk_button_new_with_label (_("Cancel"));
1091   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1092                              (GtkSignalFunc) gtk_widget_destroy, 
1093                              (gpointer) dialog);
1094   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1095                      button, TRUE, TRUE, 0);
1096   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1097   gtk_widget_grab_default (button);
1098   gtk_widget_show (button);
1099
1100   gtk_widget_show (dialog);
1101 }
1102
1103 static void
1104 gtk_file_selection_delete_file_confirmed (GtkWidget *widget,
1105                                           gpointer   data)
1106 {
1107   GtkFileSelection *fs = data;
1108   CompletionState *cmpl_state;
1109   gchar *path;
1110   gchar *full_path;
1111   gchar *sys_full_path;
1112   GError *error = NULL;
1113   gchar *buf;
1114   
1115   g_return_if_fail (fs != NULL);
1116   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1117
1118   cmpl_state = (CompletionState*) fs->cmpl_state;
1119   path = cmpl_reference_position (cmpl_state);
1120   
1121   full_path = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL);
1122   sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
1123   if (error)
1124     {
1125       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1126         buf = g_strdup_printf (_("The filename \"%s\" contains symbols that are not allowed in filenames"),
1127                                fs->fileop_file);
1128       else
1129         buf = g_strdup_printf (_("Error deleting file \"%s\": %s\n%s"),
1130                                fs->fileop_file, error->message,
1131                                _("It probably contains symbols not allowed in filenames."));
1132       
1133       gtk_file_selection_fileop_error (fs, buf);
1134       g_error_free (error);
1135       goto out;
1136     }
1137
1138   if (unlink (sys_full_path) < 0) 
1139     {
1140       buf = g_strdup_printf (_("Error deleting file \"%s\": %s"),
1141                              fs->fileop_file, g_strerror (errno));
1142       gtk_file_selection_fileop_error (fs, buf);
1143     }
1144   
1145  out:
1146   g_free (full_path);
1147   g_free (sys_full_path);
1148   
1149   gtk_widget_destroy (fs->fileop_dialog);
1150   gtk_file_selection_populate (fs, "", FALSE);
1151 }
1152
1153 static void
1154 gtk_file_selection_delete_file (GtkWidget *widget,
1155                                 gpointer   data)
1156 {
1157   GtkFileSelection *fs = data;
1158   GtkWidget *label;
1159   GtkWidget *vbox;
1160   GtkWidget *button;
1161   GtkWidget *dialog;
1162   gchar *filename;
1163   gchar *buf;
1164   
1165   g_return_if_fail (fs != NULL);
1166   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1167
1168   if (fs->fileop_dialog)
1169           return;
1170
1171 #ifdef G_WITH_CYGWIN
1172   translate_win32_path (fs);
1173 #endif
1174
1175   filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1176   if (strlen (filename) < 1)
1177     return;
1178
1179   fs->fileop_file = filename;
1180   
1181   /* main dialog */
1182   fs->fileop_dialog = dialog = gtk_dialog_new ();
1183   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1184                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
1185                       (gpointer) fs);
1186   gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File"));
1187   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1188
1189   /* If file dialog is grabbed, grab option dialog */
1190   /* When option dialog is closed, file dialog will be grabbed again */
1191   if (GTK_WINDOW (fs)->modal)
1192       gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1193   
1194   vbox = gtk_vbox_new (FALSE, 0);
1195   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1196   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1197                      FALSE, FALSE, 0);
1198   gtk_widget_show (vbox);
1199
1200   buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
1201   label = gtk_label_new (buf);
1202   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1203   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1204   gtk_widget_show (label);
1205   g_free (buf);
1206   
1207   /* buttons */
1208   button = gtk_button_new_with_label (_("Delete"));
1209   gtk_signal_connect (GTK_OBJECT (button), "clicked",
1210                       (GtkSignalFunc) gtk_file_selection_delete_file_confirmed, 
1211                       (gpointer) fs);
1212   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1213                       button, TRUE, TRUE, 0);
1214   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1215   gtk_widget_show (button);
1216   
1217   button = gtk_button_new_with_label (_("Cancel"));
1218   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1219                              (GtkSignalFunc) gtk_widget_destroy, 
1220                              (gpointer) dialog);
1221   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1222                       button, TRUE, TRUE, 0);
1223   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1224   gtk_widget_grab_default (button);
1225   gtk_widget_show (button);
1226
1227   gtk_widget_show (dialog);
1228
1229 }
1230
1231 static void
1232 gtk_file_selection_rename_file_confirmed (GtkWidget *widget,
1233                                           gpointer   data)
1234 {
1235   GtkFileSelection *fs = data;
1236   gchar *buf;
1237   gchar *file;
1238   gchar *path;
1239   gchar *new_filename;
1240   gchar *old_filename;
1241   gchar *sys_new_filename;
1242   gchar *sys_old_filename;
1243   CompletionState *cmpl_state;
1244   GError *error = NULL;
1245   
1246   g_return_if_fail (fs != NULL);
1247   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1248
1249   file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1250   cmpl_state = (CompletionState*) fs->cmpl_state;
1251   path = cmpl_reference_position (cmpl_state);
1252   
1253   new_filename = g_strconcat (path, G_DIR_SEPARATOR_S, file, NULL);
1254   old_filename = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL);
1255
1256   sys_new_filename = g_filename_from_utf8 (new_filename, -1, NULL, NULL, &error);
1257   if (error)
1258     {
1259       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1260         buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), new_filename);
1261       else
1262         buf = g_strdup_printf (_("Error renaming file to \"%s\": %s\n%s"),
1263                                new_filename, error->message,
1264                                _("You probably used symbols not allowed in filenames."));
1265       gtk_file_selection_fileop_error (fs, buf);
1266       g_error_free (error);
1267       goto out1;
1268     }
1269
1270   sys_old_filename = g_filename_from_utf8 (old_filename, -1, NULL, NULL, &error);
1271   if (error)
1272     {
1273       if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1274         buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), old_filename);
1275       else
1276         buf = g_strdup_printf (_("Error renaming file \"%s\": %s\n%s"),
1277                                old_filename, error->message,
1278                                _("It probably contains symbols not allowed in filenames."));
1279       gtk_file_selection_fileop_error (fs, buf);
1280       g_error_free (error);
1281       goto out2;
1282     }
1283   
1284   if (rename (sys_old_filename, sys_new_filename) < 0) 
1285     {
1286       buf = g_strdup_printf (_("Error renaming file \"%s\" to \"%s\": %s"),
1287                              sys_old_filename, sys_new_filename,
1288                              g_strerror (errno));
1289       gtk_file_selection_fileop_error (fs, buf);
1290     }
1291   
1292  out2:
1293   g_free (sys_old_filename);
1294
1295  out1:
1296   g_free (new_filename);
1297   g_free (old_filename);
1298   g_free (sys_new_filename);
1299   
1300   gtk_widget_destroy (fs->fileop_dialog);
1301   gtk_file_selection_populate (fs, "", FALSE);
1302 }
1303   
1304 static void
1305 gtk_file_selection_rename_file (GtkWidget *widget,
1306                                 gpointer   data)
1307 {
1308   GtkFileSelection *fs = data;
1309   GtkWidget *label;
1310   GtkWidget *dialog;
1311   GtkWidget *vbox;
1312   GtkWidget *button;
1313   gchar *buf;
1314   
1315   g_return_if_fail (fs != NULL);
1316   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1317
1318   if (fs->fileop_dialog)
1319           return;
1320
1321   fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1322   if (strlen (fs->fileop_file) < 1)
1323     return;
1324   
1325   /* main dialog */
1326   fs->fileop_dialog = dialog = gtk_dialog_new ();
1327   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1328                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
1329                       (gpointer) fs);
1330   gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File"));
1331   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1332
1333   /* If file dialog is grabbed, grab option dialog */
1334   /* When option dialog  closed, file dialog will be grabbed again */
1335   if (GTK_WINDOW (fs)->modal)
1336     gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1337   
1338   vbox = gtk_vbox_new (FALSE, 0);
1339   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1340   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1341                       FALSE, FALSE, 0);
1342   gtk_widget_show(vbox);
1343   
1344   buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
1345   label = gtk_label_new(buf);
1346   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1347   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1348   gtk_widget_show (label);
1349   g_free (buf);
1350
1351   /* New filename entry */
1352   fs->fileop_entry = gtk_entry_new ();
1353   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, 
1354                       TRUE, TRUE, 5);
1355   GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1356   gtk_widget_show (fs->fileop_entry);
1357   
1358   gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
1359   gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
1360                               0, strlen (fs->fileop_file));
1361
1362   /* buttons */
1363   button = gtk_button_new_with_label (_("Rename"));
1364   gtk_signal_connect (GTK_OBJECT (button), "clicked",
1365                       (GtkSignalFunc) gtk_file_selection_rename_file_confirmed, 
1366                       (gpointer) fs);
1367   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1368                       button, TRUE, TRUE, 0);
1369   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1370   gtk_widget_show (button);
1371   
1372   button = gtk_button_new_with_label (_("Cancel"));
1373   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1374                              (GtkSignalFunc) gtk_widget_destroy, 
1375                              (gpointer) dialog);
1376   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1377                       button, TRUE, TRUE, 0);
1378   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1379   gtk_widget_grab_default (button);
1380   gtk_widget_show (button);
1381
1382   gtk_widget_show (dialog);
1383 }
1384
1385 static gint
1386 gtk_file_selection_insert_text (GtkWidget   *widget,
1387                                 const gchar *new_text,
1388                                 gint         new_text_length,
1389                                 gint        *position,
1390                                 gpointer     user_data)
1391 {
1392   gchar *filename;
1393
1394   filename = g_filename_from_utf8 (new_text, new_text_length, NULL, NULL, NULL);
1395
1396   if (!filename)
1397     {
1398       gdk_beep ();
1399       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "insert_text");
1400       return FALSE;
1401     }
1402   
1403   g_free (filename);
1404   
1405   return TRUE;
1406 }
1407
1408 static gint
1409 gtk_file_selection_key_press (GtkWidget   *widget,
1410                               GdkEventKey *event,
1411                               gpointer     user_data)
1412 {
1413   GtkFileSelection *fs;
1414   char *text;
1415
1416   g_return_val_if_fail (widget != NULL, FALSE);
1417   g_return_val_if_fail (event != NULL, FALSE);
1418
1419   if (event->keyval == GDK_Tab)
1420     {
1421       fs = GTK_FILE_SELECTION (user_data);
1422 #ifdef G_WITH_CYGWIN
1423       translate_win32_path (fs);
1424 #endif
1425       text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1426
1427       text = g_strdup (text);
1428
1429       gtk_file_selection_populate (fs, text, TRUE);
1430
1431       g_free (text);
1432
1433       return TRUE;
1434     }
1435
1436   return FALSE;
1437 }
1438
1439
1440 static void
1441 gtk_file_selection_history_callback (GtkWidget *widget,
1442                                      gpointer   data)
1443 {
1444   GtkFileSelection *fs = data;
1445   HistoryCallbackArg *callback_arg;
1446   GList *list;
1447
1448   g_return_if_fail (fs != NULL);
1449   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1450
1451   list = fs->history_list;
1452   
1453   while (list) {
1454     callback_arg = list->data;
1455     
1456     if (callback_arg->menu_item == widget)
1457       {
1458         gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1459         break;
1460       }
1461     
1462     list = list->next;
1463   }
1464 }
1465
1466 static void 
1467 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1468                                         gchar            *current_directory)
1469 {
1470   HistoryCallbackArg *callback_arg;
1471   GtkWidget *menu_item;
1472   GList *list;
1473   gchar *current_dir;
1474   gint dir_len;
1475   gint i;
1476   
1477   g_return_if_fail (fs != NULL);
1478   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1479   g_return_if_fail (current_directory != NULL);
1480   
1481   list = fs->history_list;
1482
1483   if (fs->history_menu) 
1484     {
1485       while (list) {
1486         callback_arg = list->data;
1487         g_free (callback_arg->directory);
1488         g_free (callback_arg);
1489         list = list->next;
1490       }
1491       g_list_free (fs->history_list);
1492       fs->history_list = NULL;
1493       
1494       gtk_widget_destroy (fs->history_menu);
1495     }
1496   
1497   fs->history_menu = gtk_menu_new ();
1498
1499   current_dir = g_strdup (current_directory);
1500
1501   dir_len = strlen (current_dir);
1502
1503   for (i = dir_len; i >= 0; i--)
1504     {
1505       /* the i == dir_len is to catch the full path for the first 
1506        * entry. */
1507       if ( (current_dir[i] == G_DIR_SEPARATOR) || (i == dir_len))
1508         {
1509           /* another small hack to catch the full path */
1510           if (i != dir_len) 
1511                   current_dir[i + 1] = '\0';
1512 #ifdef G_WITH_CYGWIN
1513           if (!strcmp (current_dir, "//"))
1514             continue;
1515 #endif
1516           menu_item = gtk_menu_item_new_with_label (current_dir);
1517           
1518           callback_arg = g_new (HistoryCallbackArg, 1);
1519           callback_arg->menu_item = menu_item;
1520           
1521           /* since the autocompletion gets confused if you don't 
1522            * supply a trailing '/' on a dir entry, set the full
1523            * (current) path to "" which just refreshes the filesel */
1524           if (dir_len == i)
1525             {
1526               callback_arg->directory = g_strdup ("");
1527             }
1528           else
1529             {
1530               callback_arg->directory = g_strdup (current_dir);
1531             }
1532           
1533           fs->history_list = g_list_append (fs->history_list, callback_arg);
1534           
1535           gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1536                               (GtkSignalFunc) gtk_file_selection_history_callback,
1537                               (gpointer) fs);
1538           gtk_menu_shell_append (GTK_MENU_SHELL (fs->history_menu), menu_item);
1539           gtk_widget_show (menu_item);
1540         }
1541     }
1542
1543   gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown), 
1544                             fs->history_menu);
1545   g_free (current_dir);
1546 }
1547
1548 static void
1549 gtk_file_selection_file_button (GtkWidget      *widget,
1550                                 gint            row, 
1551                                 gint            column, 
1552                                 GdkEventButton *bevent,
1553                                 gpointer        user_data)
1554 {
1555   GtkFileSelection *fs = NULL;
1556   gchar *filename, *temp = NULL;
1557   
1558   g_return_if_fail (GTK_IS_CLIST (widget));
1559
1560   fs = user_data;
1561   g_return_if_fail (fs != NULL);
1562   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1563   
1564   gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1565   filename = g_strdup (temp);
1566
1567 #ifdef G_WITH_CYGWIN
1568   /* Check to see if the selection was a drive selector */
1569   if (isalpha (filename[0]) && (filename[1] == ':')) {
1570     /* It is... map it to a CYGWIN32 drive */
1571     gchar *temp_filename = g_strdup_printf ("//%c/", tolower (filename[0]));
1572     g_free(filename);
1573     filename = temp_filename;
1574   }
1575 #endif /* G_WITH_CYGWIN */
1576
1577   if (filename)
1578     {
1579       if (bevent)
1580         switch (bevent->type)
1581           {
1582           case GDK_2BUTTON_PRESS:
1583             gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1584             break;
1585             
1586           default:
1587             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1588             break;
1589           }
1590       else
1591         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1592
1593       g_free (filename);
1594     }
1595 }
1596
1597 static void
1598 gtk_file_selection_dir_button (GtkWidget      *widget,
1599                                gint            row, 
1600                                gint            column, 
1601                                GdkEventButton *bevent,
1602                                gpointer        user_data)
1603 {
1604   GtkFileSelection *fs = NULL;
1605   gchar *filename, *temp = NULL;
1606
1607   g_return_if_fail (GTK_IS_CLIST (widget));
1608
1609   fs = GTK_FILE_SELECTION (user_data);
1610   g_return_if_fail (fs != NULL);
1611   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1612
1613   gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1614   filename = g_strdup (temp);
1615
1616   if (filename)
1617     {
1618       if (bevent)
1619         switch (bevent->type)
1620           {
1621           case GDK_2BUTTON_PRESS:
1622             gtk_file_selection_populate (fs, filename, FALSE);
1623             break;
1624           
1625           default:
1626             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1627             break;
1628           }
1629       else
1630         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1631
1632       g_free (filename);
1633     }
1634 }
1635
1636 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
1637
1638 static void
1639 win32_gtk_add_drives_to_dir_list (GtkWidget *the_dir_list)
1640 {
1641   gchar *text[2], *textPtr;
1642   gchar buffer[128];
1643   char volumeNameBuf[128];
1644   char formatBuffer[128];
1645
1646   text[1] = NULL;
1647
1648   /* Get the Drives string */
1649   GetLogicalDriveStrings (sizeof (buffer), buffer);
1650
1651   /* Add the drives as necessary */
1652   textPtr = buffer;
1653   while (*textPtr != '\0') {
1654     /* Get the volume information for this drive */
1655     if ((tolower (textPtr[0]) != 'a') && (tolower (textPtr[0]) != 'b'))
1656       {
1657         /* Ignore floppies (?) */
1658         DWORD maxComponentLength, flags;
1659
1660         GetVolumeInformation (textPtr,
1661                               volumeNameBuf, sizeof(volumeNameBuf),
1662                               NULL, &maxComponentLength,
1663                               &flags, NULL, 0);
1664         /* Build the actual displayable string */
1665
1666         sprintf (formatBuffer, "%c:\\", toupper(textPtr[0]));
1667 #if 0 /* HB: removed to allow drive change AND directory update with one click */
1668         if (strlen (volumeNameBuf) > 0)
1669           sprintf (formatBuffer, "%s (%s)", formatBuffer, volumeNameBuf);
1670 #endif
1671         /* Add to the list */
1672         text[0] = formatBuffer;
1673         gtk_clist_append (GTK_CLIST (the_dir_list), text);
1674       }
1675     textPtr += (strlen (textPtr) + 1);
1676   }
1677 }
1678 #endif
1679
1680 static void
1681 gtk_file_selection_populate (GtkFileSelection *fs,
1682                              gchar            *rel_path,
1683                              gint              try_complete)
1684 {
1685   CompletionState *cmpl_state;
1686   PossibleCompletion* poss;
1687   gchar* filename;
1688   gchar* rem_path = rel_path;
1689   gchar* sel_text;
1690   gchar* text[2];
1691   gint did_recurse = FALSE;
1692   gint possible_count = 0;
1693   gint selection_index = -1;
1694   
1695   g_return_if_fail (fs != NULL);
1696   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1697   
1698   cmpl_state = (CompletionState*) fs->cmpl_state;
1699   poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1700
1701   if (!cmpl_state_okay (cmpl_state))
1702     {
1703       /* Something went wrong. */
1704       gtk_file_selection_abort (fs);
1705       return;
1706     }
1707
1708   g_assert (cmpl_state->reference_dir);
1709
1710   gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1711   gtk_clist_clear (GTK_CLIST (fs->dir_list));
1712   gtk_clist_freeze (GTK_CLIST (fs->file_list));
1713   gtk_clist_clear (GTK_CLIST (fs->file_list));
1714
1715   /* Set the dir_list to include ./ and ../ */
1716   text[1] = NULL;
1717   text[0] = "." G_DIR_SEPARATOR_S;
1718   gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1719
1720   text[0] = ".." G_DIR_SEPARATOR_S;
1721   gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1722
1723   while (poss)
1724     {
1725       if (cmpl_is_a_completion (poss))
1726         {
1727           possible_count += 1;
1728
1729           filename = cmpl_this_completion (poss);
1730
1731           text[0] = filename;
1732           
1733           if (cmpl_is_directory (poss))
1734             {
1735               if (strcmp (filename, "." G_DIR_SEPARATOR_S) != 0 &&
1736                   strcmp (filename, ".." G_DIR_SEPARATOR_S) != 0)
1737                 gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1738             }
1739           else
1740             {
1741               gtk_clist_append (GTK_CLIST (fs->file_list), text);
1742             }
1743         }
1744
1745       poss = cmpl_next_completion (cmpl_state);
1746     }
1747
1748 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
1749   /* For Windows, add drives as potential selections */
1750   win32_gtk_add_drives_to_dir_list (fs->dir_list);
1751 #endif
1752
1753   gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1754   gtk_clist_thaw (GTK_CLIST (fs->file_list));
1755
1756   /* File lists are set. */
1757
1758   g_assert (cmpl_state->reference_dir);
1759
1760   if (try_complete)
1761     {
1762
1763       /* User is trying to complete filenames, so advance the user's input
1764        * string to the updated_text, which is the common leading substring
1765        * of all possible completions, and if its a directory attempt
1766        * attempt completions in it. */
1767
1768       if (cmpl_updated_text (cmpl_state)[0])
1769         {
1770
1771           if (cmpl_updated_dir (cmpl_state))
1772             {
1773               gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1774
1775               did_recurse = TRUE;
1776
1777               gtk_file_selection_populate (fs, dir_name, TRUE);
1778
1779               g_free (dir_name);
1780             }
1781           else
1782             {
1783               if (fs->selection_entry)
1784                       gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1785                                           cmpl_updated_text (cmpl_state));
1786             }
1787         }
1788       else
1789         {
1790           selection_index = cmpl_last_valid_char (cmpl_state) -
1791                             (strlen (rel_path) - strlen (rem_path));
1792           if (fs->selection_entry)
1793             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1794         }
1795     }
1796   else
1797     {
1798       if (fs->selection_entry)
1799         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1800     }
1801
1802   if (!did_recurse)
1803     {
1804       if (fs->selection_entry)
1805         gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1806
1807       if (fs->selection_entry)
1808         {
1809           sel_text = g_strconcat (_("Selection: "),
1810                                   cmpl_reference_position (cmpl_state),
1811                                   NULL);
1812
1813           gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text);
1814           g_free (sel_text);
1815         }
1816
1817       if (fs->history_pulldown) 
1818         {
1819           gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1820         }
1821       
1822     }
1823 }
1824
1825 static void
1826 gtk_file_selection_abort (GtkFileSelection *fs)
1827 {
1828   gchar err_buf[256];
1829
1830   sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
1831
1832   /*  BEEP gdk_beep();  */
1833
1834   if (fs->selection_entry)
1835     gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
1836 }
1837
1838 /**********************************************************************/
1839 /*                        External Interface                          */
1840 /**********************************************************************/
1841
1842 /* The four completion state selectors
1843  */
1844 static gchar*
1845 cmpl_updated_text (CompletionState *cmpl_state)
1846 {
1847   return cmpl_state->updated_text;
1848 }
1849
1850 static gboolean
1851 cmpl_updated_dir (CompletionState *cmpl_state)
1852 {
1853   return cmpl_state->re_complete;
1854 }
1855
1856 static gchar*
1857 cmpl_reference_position (CompletionState *cmpl_state)
1858 {
1859   return cmpl_state->reference_dir->fullname;
1860 }
1861
1862 static gint
1863 cmpl_last_valid_char (CompletionState *cmpl_state)
1864 {
1865   return cmpl_state->last_valid_char;
1866 }
1867
1868 static gchar*
1869 cmpl_completion_fullname (gchar           *text,
1870                           CompletionState *cmpl_state)
1871 {
1872   static char nothing[2] = "";
1873
1874   if (!cmpl_state_okay (cmpl_state))
1875     {
1876       return nothing;
1877     }
1878   else if (g_path_is_absolute (text))
1879     {
1880       strcpy (cmpl_state->updated_text, text);
1881     }
1882 #ifdef HAVE_PWD_H
1883   else if (text[0] == '~')
1884     {
1885       CompletionDir* dir;
1886       char* slash;
1887
1888       dir = open_user_dir (text, cmpl_state);
1889
1890       if (!dir)
1891         {
1892           /* spencer says just return ~something, so
1893            * for now just do it. */
1894           strcpy (cmpl_state->updated_text, text);
1895         }
1896       else
1897         {
1898
1899           strcpy (cmpl_state->updated_text, dir->fullname);
1900
1901           slash = strchr (text, G_DIR_SEPARATOR);
1902
1903           if (slash)
1904             strcat (cmpl_state->updated_text, slash);
1905         }
1906     }
1907 #endif
1908   else
1909     {
1910       strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1911       if (cmpl_state->updated_text[strlen (cmpl_state->updated_text) - 1] != G_DIR_SEPARATOR)
1912         strcat (cmpl_state->updated_text, G_DIR_SEPARATOR_S);
1913       strcat (cmpl_state->updated_text, text);
1914     }
1915
1916   return cmpl_state->updated_text;
1917 }
1918
1919 /* The three completion selectors
1920  */
1921 static gchar*
1922 cmpl_this_completion (PossibleCompletion* pc)
1923 {
1924   return pc->text;
1925 }
1926
1927 static gboolean
1928 cmpl_is_directory (PossibleCompletion* pc)
1929 {
1930   return pc->is_directory;
1931 }
1932
1933 static gint
1934 cmpl_is_a_completion (PossibleCompletion* pc)
1935 {
1936   return pc->is_a_completion;
1937 }
1938
1939 /**********************************************************************/
1940 /*                       Construction, deletion                       */
1941 /**********************************************************************/
1942
1943 static CompletionState*
1944 cmpl_init_state (void)
1945 {
1946   gchar *sys_getcwd_buf;
1947   gchar *utf8_cwd;
1948   CompletionState *new_state;
1949
1950   new_state = g_new (CompletionState, 1);
1951
1952   /* g_get_current_dir() returns a string in the "system" charset */
1953   sys_getcwd_buf = g_get_current_dir ();
1954   utf8_cwd = g_filename_to_utf8 (sys_getcwd_buf, -1, NULL, NULL, NULL);
1955   g_free (sys_getcwd_buf);
1956
1957 tryagain:
1958
1959   new_state->reference_dir = NULL;
1960   new_state->completion_dir = NULL;
1961   new_state->active_completion_dir = NULL;
1962   new_state->directory_storage = NULL;
1963   new_state->directory_sent_storage = NULL;
1964   new_state->last_valid_char = 0;
1965   new_state->updated_text = g_new (gchar, MAXPATHLEN);
1966   new_state->updated_text_alloc = MAXPATHLEN;
1967   new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1968   new_state->the_completion.text_alloc = MAXPATHLEN;
1969   new_state->user_dir_name_buffer = NULL;
1970   new_state->user_directories = NULL;
1971
1972   new_state->reference_dir = open_dir (utf8_cwd, new_state);
1973
1974   if (!new_state->reference_dir)
1975     {
1976       /* Directories changing from underneath us, grumble */
1977       strcpy (utf8_cwd, G_DIR_SEPARATOR_S);
1978       goto tryagain;
1979     }
1980
1981   g_free (utf8_cwd);
1982   return new_state;
1983 }
1984
1985 static void
1986 cmpl_free_dir_list (GList* dp0)
1987 {
1988   GList *dp = dp0;
1989
1990   while (dp)
1991     {
1992       free_dir (dp->data);
1993       dp = dp->next;
1994     }
1995
1996   g_list_free (dp0);
1997 }
1998
1999 static void
2000 cmpl_free_dir_sent_list (GList* dp0)
2001 {
2002   GList *dp = dp0;
2003
2004   while (dp)
2005     {
2006       free_dir_sent (dp->data);
2007       dp = dp->next;
2008     }
2009
2010   g_list_free (dp0);
2011 }
2012
2013 static void
2014 cmpl_free_state (CompletionState* cmpl_state)
2015 {
2016   g_return_if_fail (cmpl_state != NULL);
2017
2018   cmpl_free_dir_list (cmpl_state->directory_storage);
2019   cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
2020
2021   if (cmpl_state->user_dir_name_buffer)
2022     g_free (cmpl_state->user_dir_name_buffer);
2023   if (cmpl_state->user_directories)
2024     g_free (cmpl_state->user_directories);
2025   if (cmpl_state->the_completion.text)
2026     g_free (cmpl_state->the_completion.text);
2027   if (cmpl_state->updated_text)
2028     g_free (cmpl_state->updated_text);
2029
2030   g_free (cmpl_state);
2031 }
2032
2033 static void
2034 free_dir (CompletionDir* dir)
2035 {
2036   g_free (dir->fullname);
2037   g_free (dir);
2038 }
2039
2040 static void
2041 free_dir_sent (CompletionDirSent* sent)
2042 {
2043   gint i;
2044   for (i = 0; i < sent->entry_count; i++)
2045     g_free (sent->entries[i].entry_name);
2046   g_free (sent->entries);
2047   g_free (sent);
2048 }
2049
2050 static void
2051 prune_memory_usage (CompletionState *cmpl_state)
2052 {
2053   GList* cdsl = cmpl_state->directory_sent_storage;
2054   GList* cdl = cmpl_state->directory_storage;
2055   GList* cdl0 = cdl;
2056   gint len = 0;
2057
2058   for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
2059     cdsl = cdsl->next;
2060
2061   if (cdsl)
2062     {
2063       cmpl_free_dir_sent_list (cdsl->next);
2064       cdsl->next = NULL;
2065     }
2066
2067   cmpl_state->directory_storage = NULL;
2068   while (cdl)
2069     {
2070       if (cdl->data == cmpl_state->reference_dir)
2071         cmpl_state->directory_storage = g_list_prepend (NULL, cdl->data);
2072       else
2073         free_dir (cdl->data);
2074       cdl = cdl->next;
2075     }
2076
2077   g_list_free (cdl0);
2078 }
2079
2080 /**********************************************************************/
2081 /*                        The main entrances.                         */
2082 /**********************************************************************/
2083
2084 static PossibleCompletion*
2085 cmpl_completion_matches (gchar           *text_to_complete,
2086                          gchar          **remaining_text,
2087                          CompletionState *cmpl_state)
2088 {
2089   gchar* first_slash;
2090   PossibleCompletion *poss;
2091
2092   prune_memory_usage (cmpl_state);
2093
2094   g_assert (text_to_complete != NULL);
2095
2096   cmpl_state->user_completion_index = -1;
2097   cmpl_state->last_completion_text = text_to_complete;
2098   cmpl_state->the_completion.text[0] = 0;
2099   cmpl_state->last_valid_char = 0;
2100   cmpl_state->updated_text_len = -1;
2101   cmpl_state->updated_text[0] = 0;
2102   cmpl_state->re_complete = FALSE;
2103
2104 #ifdef HAVE_PWD_H
2105   first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2106
2107   if (text_to_complete[0] == '~' && !first_slash)
2108     {
2109       /* Text starts with ~ and there is no slash, show all the
2110        * home directory completions.
2111        */
2112       poss = attempt_homedir_completion (text_to_complete, cmpl_state);
2113
2114       update_cmpl (poss, cmpl_state);
2115
2116       return poss;
2117     }
2118 #endif
2119   cmpl_state->reference_dir =
2120     open_ref_dir (text_to_complete, remaining_text, cmpl_state);
2121
2122   if (!cmpl_state->reference_dir)
2123     return NULL;
2124
2125   cmpl_state->completion_dir =
2126     find_completion_dir (*remaining_text, remaining_text, cmpl_state);
2127
2128   cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2129
2130   if (!cmpl_state->completion_dir)
2131     return NULL;
2132
2133   cmpl_state->completion_dir->cmpl_index = -1;
2134   cmpl_state->completion_dir->cmpl_parent = NULL;
2135   cmpl_state->completion_dir->cmpl_text = *remaining_text;
2136
2137   cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2138
2139   cmpl_state->reference_dir = cmpl_state->completion_dir;
2140
2141   poss = attempt_file_completion (cmpl_state);
2142
2143   update_cmpl (poss, cmpl_state);
2144
2145   return poss;
2146 }
2147
2148 static PossibleCompletion*
2149 cmpl_next_completion (CompletionState* cmpl_state)
2150 {
2151   PossibleCompletion* poss = NULL;
2152
2153   cmpl_state->the_completion.text[0] = 0;
2154
2155 #ifdef HAVE_PWD_H
2156   if (cmpl_state->user_completion_index >= 0)
2157     poss = attempt_homedir_completion (cmpl_state->last_completion_text, cmpl_state);
2158   else
2159     poss = attempt_file_completion (cmpl_state);
2160 #else
2161   poss = attempt_file_completion (cmpl_state);
2162 #endif
2163
2164   update_cmpl (poss, cmpl_state);
2165
2166   return poss;
2167 }
2168
2169 /**********************************************************************/
2170 /*                       Directory Operations                         */
2171 /**********************************************************************/
2172
2173 /* Open the directory where completion will begin from, if possible. */
2174 static CompletionDir*
2175 open_ref_dir (gchar           *text_to_complete,
2176               gchar          **remaining_text,
2177               CompletionState *cmpl_state)
2178 {
2179   gchar* first_slash;
2180   CompletionDir *new_dir;
2181
2182   first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2183
2184 #ifdef G_WITH_CYGWIN
2185   if (text_to_complete[0] == '/' && text_to_complete[1] == '/')
2186     {
2187       char root_dir[5];
2188       sprintf (root_dir, "//%c", text_to_complete[2]);
2189
2190       new_dir = open_dir (root_dir, cmpl_state);
2191
2192       if (new_dir) {
2193         *remaining_text = text_to_complete + 4;
2194       }
2195     }
2196 #else
2197   if (FALSE)
2198     ;
2199 #endif
2200 #ifdef HAVE_PWD_H
2201   else if (text_to_complete[0] == '~')
2202     {
2203       new_dir = open_user_dir (text_to_complete, cmpl_state);
2204
2205       if (new_dir)
2206         {
2207           if (first_slash)
2208             *remaining_text = first_slash + 1;
2209           else
2210             *remaining_text = text_to_complete + strlen (text_to_complete);
2211         }
2212       else
2213         {
2214           return NULL;
2215         }
2216     }
2217 #endif
2218   else if (g_path_is_absolute (text_to_complete) || !cmpl_state->reference_dir)
2219     {
2220       gchar *tmp = g_strdup (text_to_complete);
2221       gchar *p;
2222
2223       p = tmp;
2224       while (*p && *p != '*' && *p != '?')
2225         p++;
2226
2227       *p = '\0';
2228       p = strrchr (tmp, G_DIR_SEPARATOR);
2229       if (p)
2230         {
2231           if (p == tmp)
2232             p++;
2233       
2234           *p = '\0';
2235
2236           new_dir = open_dir (tmp, cmpl_state);
2237
2238           if (new_dir)
2239             *remaining_text = text_to_complete + 
2240               ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp));
2241         }
2242       else
2243         {
2244           /* If no possible candidates, use the cwd */
2245           gchar *sys_curdir = g_get_current_dir ();
2246           gchar *utf8_curdir = g_filename_to_utf8 (sys_curdir, -1, NULL, NULL, NULL);
2247
2248           g_free (sys_curdir);
2249
2250           new_dir = open_dir (utf8_curdir, cmpl_state);
2251
2252           if (new_dir)
2253             *remaining_text = text_to_complete;
2254
2255           g_free (utf8_curdir);
2256         }
2257
2258       g_free (tmp);
2259     }
2260   else
2261     {
2262       *remaining_text = text_to_complete;
2263
2264       new_dir = open_dir (cmpl_state->reference_dir->fullname, cmpl_state);
2265     }
2266
2267   if (new_dir)
2268     {
2269       new_dir->cmpl_index = -1;
2270       new_dir->cmpl_parent = NULL;
2271     }
2272
2273   return new_dir;
2274 }
2275
2276 #ifdef HAVE_PWD_H
2277
2278 /* open a directory by user name */
2279 static CompletionDir*
2280 open_user_dir (gchar           *text_to_complete,
2281                CompletionState *cmpl_state)
2282 {
2283   CompletionDir *result;
2284   gchar *first_slash;
2285   gint cmp_len;
2286
2287   g_assert (text_to_complete && text_to_complete[0] == '~');
2288
2289   first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2290
2291   if (first_slash)
2292     cmp_len = first_slash - text_to_complete - 1;
2293   else
2294     cmp_len = strlen (text_to_complete + 1);
2295
2296   if (!cmp_len)
2297     {
2298       /* ~/ */
2299       gchar *homedir = g_get_home_dir ();
2300       gchar *utf8_homedir = g_filename_to_utf8 (homedir, -1, NULL, NULL, NULL);
2301
2302       g_free (homedir);
2303
2304       if (utf8_homedir)
2305         result = open_dir (utf8_homedir, cmpl_state);
2306       else
2307         result = NULL;
2308       
2309       g_free (utf8_homedir);
2310     }
2311   else
2312     {
2313       /* ~user/ */
2314       gchar* copy = g_new (char, cmp_len + 1);
2315       gchar *utf8_dir;
2316       struct passwd *pwd;
2317
2318       strncpy (copy, text_to_complete + 1, cmp_len);
2319       copy[cmp_len] = 0;
2320       pwd = getpwnam (copy);
2321       g_free (copy);
2322       if (!pwd)
2323         {
2324           cmpl_errno = errno;
2325           return NULL;
2326         }
2327       utf8_dir = g_filename_to_utf8 (pwd->pw_dir, -1, NULL, NULL, NULL);
2328       result = open_dir (utf8_dir, cmpl_state);
2329       g_free (utf8_dir);
2330     }
2331   return result;
2332 }
2333
2334 #endif
2335
2336 /* open a directory relative the the current relative directory */
2337 static CompletionDir*
2338 open_relative_dir (gchar           *dir_name,
2339                    CompletionDir   *dir,
2340                    CompletionState *cmpl_state)
2341 {
2342   CompletionDir *result;
2343   GString *path;
2344
2345   path = g_string_sized_new (dir->fullname_len + strlen (dir_name) + 10);
2346   g_string_assign (path, dir->fullname);
2347
2348   if (dir->fullname_len > 1
2349       && path->str[dir->fullname_len - 1] != G_DIR_SEPARATOR)
2350     g_string_append_c (path, G_DIR_SEPARATOR);
2351   g_string_append (path, dir_name);
2352
2353   result = open_dir (path->str, cmpl_state);
2354
2355   g_string_free (path, TRUE);
2356
2357   return result;
2358 }
2359
2360 /* after the cache lookup fails, really open a new directory */
2361 static CompletionDirSent*
2362 open_new_dir (gchar       *dir_name,
2363               struct stat *sbuf,
2364               gboolean     stat_subdirs)
2365 {
2366   CompletionDirSent *sent;
2367   DIR *directory;
2368   struct dirent *dirent_ptr;
2369   gint entry_count = 0;
2370   gint n_entries = 0;
2371   gint i;
2372   struct stat ent_sbuf;
2373   GString *path;
2374   gchar *sys_dir_name;
2375
2376   sent = g_new (CompletionDirSent, 1);
2377   sent->mtime = sbuf->st_mtime;
2378   sent->inode = sbuf->st_ino;
2379   sent->device = sbuf->st_dev;
2380
2381   path = g_string_sized_new (2*MAXPATHLEN + 10);
2382
2383   sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
2384   if (!sys_dir_name)
2385     {
2386       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2387       return NULL;
2388     }
2389   
2390   directory = opendir (sys_dir_name);
2391   if (!directory)
2392     {
2393       cmpl_errno = errno;
2394       g_free (sys_dir_name);
2395       return NULL;
2396     }
2397
2398   while ((dirent_ptr = readdir (directory)) != NULL)
2399     entry_count++;
2400
2401   sent->entries = g_new (CompletionDirEntry, entry_count);
2402   sent->entry_count = entry_count;
2403
2404   rewinddir (directory);
2405
2406   for (i = 0; i < entry_count; i += 1)
2407     {
2408       dirent_ptr = readdir (directory);
2409
2410       if (!dirent_ptr)
2411         {
2412           cmpl_errno = errno;
2413           closedir (directory);
2414           g_free (sys_dir_name);
2415           return NULL;
2416         }
2417
2418       sent->entries[n_entries].entry_name = g_filename_to_utf8 (dirent_ptr->d_name, -1, NULL, NULL, NULL);
2419       if (!g_utf8_validate (sent->entries[n_entries].entry_name, -1, NULL))
2420         {
2421           g_warning (_("The filename %s couldn't be converted to UTF-8. Try setting the environment variable G_BROKEN_FILENAMES."), dirent_ptr->d_name);
2422           continue;
2423         }
2424
2425       g_string_assign (path, sys_dir_name);
2426       if (path->str[path->len-1] != G_DIR_SEPARATOR)
2427         {
2428           g_string_append_c (path, G_DIR_SEPARATOR);
2429         }
2430       g_string_append (path, dirent_ptr->d_name);
2431
2432       if (stat_subdirs)
2433         {
2434           /* Here we know path->str is a "system charset" string */
2435           if (stat (path->str, &ent_sbuf) >= 0 && S_ISDIR (ent_sbuf.st_mode))
2436             sent->entries[n_entries].is_dir = TRUE;
2437           else
2438             /* stat may fail, and we don't mind, since it could be a
2439              * dangling symlink. */
2440             sent->entries[n_entries].is_dir = FALSE;
2441         }
2442       else
2443         sent->entries[n_entries].is_dir = 1;
2444
2445       n_entries++;
2446     }
2447   sent->entry_count = n_entries;
2448   
2449   g_free (sys_dir_name);
2450   g_string_free (path, TRUE);
2451   qsort (sent->entries, sent->entry_count, sizeof (CompletionDirEntry), compare_cmpl_dir);
2452
2453   closedir (directory);
2454
2455   return sent;
2456 }
2457
2458 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
2459
2460 static gboolean
2461 check_dir (gchar       *dir_name,
2462            struct stat *result,
2463            gboolean    *stat_subdirs)
2464 {
2465   /* A list of directories that we know only contain other directories.
2466    * Trying to stat every file in these directories would be very
2467    * expensive.
2468    */
2469
2470   static struct {
2471     gchar *name;
2472     gboolean present;
2473     struct stat statbuf;
2474   } no_stat_dirs[] = {
2475     { "/afs", FALSE, { 0 } },
2476     { "/net", FALSE, { 0 } }
2477   };
2478
2479   static const gint n_no_stat_dirs = G_N_ELEMENTS (no_stat_dirs);
2480   static gboolean initialized = FALSE;
2481   gchar *sys_dir_name;
2482   gint i;
2483
2484   if (!initialized)
2485     {
2486       initialized = TRUE;
2487       for (i = 0; i < n_no_stat_dirs; i++)
2488         {
2489           if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0)
2490             no_stat_dirs[i].present = TRUE;
2491         }
2492     }
2493
2494   sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
2495   if (!sys_dir_name)
2496     {
2497       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2498       return FALSE;
2499     }
2500   
2501   if (stat (sys_dir_name, result) < 0)
2502     {
2503       g_free (sys_dir_name);
2504       cmpl_errno = errno;
2505       return FALSE;
2506     }
2507   g_free (sys_dir_name);
2508
2509   *stat_subdirs = TRUE;
2510   for (i = 0; i < n_no_stat_dirs; i++)
2511     {
2512       if (no_stat_dirs[i].present &&
2513           (no_stat_dirs[i].statbuf.st_dev == result->st_dev) &&
2514           (no_stat_dirs[i].statbuf.st_ino == result->st_ino))
2515         {
2516           *stat_subdirs = FALSE;
2517           break;
2518         }
2519     }
2520
2521   return TRUE;
2522 }
2523
2524 #endif
2525
2526 /* open a directory by absolute pathname */
2527 static CompletionDir*
2528 open_dir (gchar           *dir_name,
2529           CompletionState *cmpl_state)
2530 {
2531   struct stat sbuf;
2532   gboolean stat_subdirs;
2533   CompletionDirSent *sent;
2534   GList* cdsl;
2535
2536 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
2537   if (!check_dir (dir_name, &sbuf, &stat_subdirs))
2538     return NULL;
2539
2540   cdsl = cmpl_state->directory_sent_storage;
2541
2542   while (cdsl)
2543     {
2544       sent = cdsl->data;
2545
2546       if (sent->inode == sbuf.st_ino &&
2547           sent->mtime == sbuf.st_mtime &&
2548           sent->device == sbuf.st_dev)
2549         return attach_dir (sent, dir_name, cmpl_state);
2550
2551       cdsl = cdsl->next;
2552     }
2553 #else
2554   stat_subdirs = TRUE;
2555 #endif
2556
2557   sent = open_new_dir (dir_name, &sbuf, stat_subdirs);
2558
2559   if (sent)
2560     {
2561       cmpl_state->directory_sent_storage =
2562         g_list_prepend (cmpl_state->directory_sent_storage, sent);
2563
2564       return attach_dir (sent, dir_name, cmpl_state);
2565     }
2566
2567   return NULL;
2568 }
2569
2570 static CompletionDir*
2571 attach_dir (CompletionDirSent *sent,
2572             gchar             *dir_name,
2573             CompletionState   *cmpl_state)
2574 {
2575   CompletionDir* new_dir;
2576
2577   new_dir = g_new (CompletionDir, 1);
2578
2579   cmpl_state->directory_storage =
2580     g_list_prepend (cmpl_state->directory_storage, new_dir);
2581
2582   new_dir->sent = sent;
2583   new_dir->fullname = g_strdup (dir_name);
2584   new_dir->fullname_len = strlen (dir_name);
2585
2586   return new_dir;
2587 }
2588
2589 static gint
2590 correct_dir_fullname (CompletionDir* cmpl_dir)
2591 {
2592   gint length = strlen (cmpl_dir->fullname);
2593   gchar *first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
2594   gchar *sys_filename;
2595   struct stat sbuf;
2596
2597   /* Does it end with /. (\.) ? */
2598   if (length >= 2 &&
2599       strcmp (cmpl_dir->fullname + length - 2, G_DIR_SEPARATOR_S ".") == 0)
2600     {
2601       /* Is it just the root directory (on a drive) ? */
2602       if (cmpl_dir->fullname + length - 2 == first_slash)
2603         {
2604           cmpl_dir->fullname[length - 1] = 0;
2605           cmpl_dir->fullname_len = length - 1;
2606           return TRUE;
2607         }
2608       else
2609         {
2610           cmpl_dir->fullname[length - 2] = 0;
2611         }
2612     }
2613
2614   /* Ends with /./ (\.\)? */
2615   else if (length >= 3 &&
2616            strcmp (cmpl_dir->fullname + length - 3,
2617                    G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) == 0)
2618     cmpl_dir->fullname[length - 2] = 0;
2619
2620   /* Ends with /.. (\..) ? */
2621   else if (length >= 3 &&
2622            strcmp (cmpl_dir->fullname + length - 3,
2623                    G_DIR_SEPARATOR_S "..") == 0)
2624     {
2625       /* Is it just /.. (X:\..)? */
2626       if (cmpl_dir->fullname + length - 3 == first_slash)
2627         {
2628           cmpl_dir->fullname[length - 2] = 0;
2629           cmpl_dir->fullname_len = length - 2;
2630           return TRUE;
2631         }
2632
2633       sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
2634       if (!sys_filename)
2635         {
2636           cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2637           return FALSE;
2638         }
2639       
2640       if (stat (sys_filename, &sbuf) < 0)
2641         {
2642           g_free (sys_filename);
2643           cmpl_errno = errno;
2644           return FALSE;
2645         }
2646       g_free (sys_filename);
2647
2648       cmpl_dir->fullname[length - 3] = 0;
2649
2650       if (!correct_parent (cmpl_dir, &sbuf))
2651         return FALSE;
2652     }
2653
2654   /* Ends with /../ (\..\)? */
2655   else if (length >= 4 &&
2656            strcmp (cmpl_dir->fullname + length - 4,
2657                    G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) == 0)
2658     {
2659       /* Is it just /../ (X:\..\)? */
2660       if (cmpl_dir->fullname + length - 4 == first_slash)
2661         {
2662           cmpl_dir->fullname[length - 3] = 0;
2663           cmpl_dir->fullname_len = length - 3;
2664           return TRUE;
2665         }
2666
2667       sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
2668       if (!sys_filename)
2669         {
2670           cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2671           return FALSE;
2672         }
2673       
2674       if (stat (sys_filename, &sbuf) < 0)
2675         {
2676           g_free (sys_filename);
2677           cmpl_errno = errno;
2678           return FALSE;
2679         }
2680       g_free (sys_filename);
2681
2682       cmpl_dir->fullname[length - 4] = 0;
2683
2684       if (!correct_parent (cmpl_dir, &sbuf))
2685         return FALSE;
2686     }
2687
2688   cmpl_dir->fullname_len = strlen (cmpl_dir->fullname);
2689
2690   return TRUE;
2691 }
2692
2693 static gint
2694 correct_parent (CompletionDir *cmpl_dir,
2695                 struct stat   *sbuf)
2696 {
2697   struct stat parbuf;
2698   gchar *last_slash;
2699   gchar *first_slash;
2700   gchar *new_name;
2701   gchar *sys_filename;
2702   gchar c = 0;
2703
2704   last_slash = strrchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
2705   g_assert (last_slash);
2706   first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
2707
2708   /* Clever (?) way to check for top-level directory that works also on
2709    * Win32, where there is a drive letter and colon prefixed...
2710    */
2711   if (last_slash != first_slash)
2712     {
2713       last_slash[0] = 0;
2714     }
2715   else
2716     {
2717       c = last_slash[1];
2718       last_slash[1] = 0;
2719     }
2720
2721   sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
2722   if (!sys_filename)
2723     {
2724       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2725       if (!c)
2726         last_slash[0] = G_DIR_SEPARATOR;
2727       return FALSE;
2728     }
2729   
2730   if (stat (sys_filename, &parbuf) < 0)
2731     {
2732       g_free (sys_filename);
2733       cmpl_errno = errno;
2734       if (!c)
2735         last_slash[0] = G_DIR_SEPARATOR;
2736       return FALSE;
2737     }
2738   g_free (sys_filename);
2739
2740 #ifndef G_OS_WIN32              /* No inode numbers on Win32 */
2741   if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2742     /* it wasn't a link */
2743     return TRUE;
2744
2745   if (c)
2746     last_slash[1] = c;
2747   else
2748     last_slash[0] = G_DIR_SEPARATOR;
2749
2750   /* it was a link, have to figure it out the hard way */
2751
2752   new_name = find_parent_dir_fullname (cmpl_dir->fullname);
2753
2754   if (!new_name)
2755     return FALSE;
2756
2757   g_free (cmpl_dir->fullname);
2758
2759   cmpl_dir->fullname = new_name;
2760 #endif
2761
2762   return TRUE;
2763 }
2764
2765 #ifndef G_OS_WIN32
2766
2767 static gchar*
2768 find_parent_dir_fullname (gchar* dirname)
2769 {
2770   gchar *sys_orig_dir;
2771   gchar *result;
2772   gchar *sys_cwd;
2773   gchar *sys_dirname;
2774
2775   sys_orig_dir = g_get_current_dir ();
2776   sys_dirname = g_filename_from_utf8 (dirname, -1, NULL, NULL, NULL);
2777   if (!sys_dirname)
2778     {
2779       g_free (sys_orig_dir);
2780       cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
2781       return NULL;
2782     }
2783   
2784   if (chdir (sys_dirname) != 0 || chdir ("..") != 0)
2785     {
2786       g_free (sys_dirname);
2787       g_free (sys_orig_dir);
2788       cmpl_errno = errno;
2789       return NULL;
2790     }
2791   g_free (sys_dirname);
2792
2793   sys_cwd = g_get_current_dir ();
2794   result = g_filename_to_utf8 (sys_cwd, -1, NULL, NULL, NULL);
2795   g_free (sys_cwd);
2796
2797   if (chdir (sys_orig_dir) != 0)
2798     {
2799       cmpl_errno = errno;
2800       g_free (sys_orig_dir);
2801       return NULL;
2802     }
2803
2804   g_free (sys_orig_dir);
2805   return result;
2806 }
2807
2808 #endif
2809
2810 /**********************************************************************/
2811 /*                        Completion Operations                       */
2812 /**********************************************************************/
2813
2814 #ifdef HAVE_PWD_H
2815
2816 static PossibleCompletion*
2817 attempt_homedir_completion (gchar           *text_to_complete,
2818                             CompletionState *cmpl_state)
2819 {
2820   gint index, length;
2821
2822   if (!cmpl_state->user_dir_name_buffer &&
2823       !get_pwdb (cmpl_state))
2824     return NULL;
2825   length = strlen (text_to_complete) - 1;
2826
2827   cmpl_state->user_completion_index += 1;
2828
2829   while (cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2830     {
2831       index = first_diff_index (text_to_complete + 1,
2832                                 cmpl_state->user_directories
2833                                 [cmpl_state->user_completion_index].login);
2834
2835       switch (index)
2836         {
2837         case PATTERN_MATCH:
2838           break;
2839         default:
2840           if (cmpl_state->last_valid_char < (index + 1))
2841             cmpl_state->last_valid_char = index + 1;
2842           cmpl_state->user_completion_index += 1;
2843           continue;
2844         }
2845
2846       cmpl_state->the_completion.is_a_completion = 1;
2847       cmpl_state->the_completion.is_directory = TRUE;
2848
2849       append_completion_text ("~", cmpl_state);
2850
2851       append_completion_text (cmpl_state->
2852                               user_directories[cmpl_state->user_completion_index].login,
2853                               cmpl_state);
2854
2855       return append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
2856     }
2857
2858   if (text_to_complete[1]
2859       || cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2860     {
2861       cmpl_state->user_completion_index = -1;
2862       return NULL;
2863     }
2864   else
2865     {
2866       cmpl_state->user_completion_index += 1;
2867       cmpl_state->the_completion.is_a_completion = 1;
2868       cmpl_state->the_completion.is_directory = TRUE;
2869
2870       return append_completion_text ("~" G_DIR_SEPARATOR_S, cmpl_state);
2871     }
2872 }
2873
2874 #endif
2875
2876 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
2877 #define FOLD(c) (tolower(c))
2878 #else
2879 #define FOLD(c) (c)
2880 #endif
2881
2882 /* returns the index (>= 0) of the first differing character,
2883  * PATTERN_MATCH if the completion matches */
2884 static gint
2885 first_diff_index (gchar *pat,
2886                   gchar *text)
2887 {
2888   gint diff = 0;
2889
2890   while (*pat && *text && FOLD (*text) == FOLD (*pat))
2891     {
2892       pat += 1;
2893       text += 1;
2894       diff += 1;
2895     }
2896
2897   if (*pat)
2898     return diff;
2899
2900   return PATTERN_MATCH;
2901 }
2902
2903 static PossibleCompletion*
2904 append_completion_text (gchar           *text,
2905                         CompletionState *cmpl_state)
2906 {
2907   gint len, i = 1;
2908
2909   if (!cmpl_state->the_completion.text)
2910     return NULL;
2911
2912   len = strlen (text) + strlen (cmpl_state->the_completion.text) + 1;
2913
2914   if (cmpl_state->the_completion.text_alloc > len)
2915     {
2916       strcat (cmpl_state->the_completion.text, text);
2917       return &cmpl_state->the_completion;
2918     }
2919
2920   while (i < len)
2921     i <<= 1;
2922
2923   cmpl_state->the_completion.text_alloc = i;
2924
2925   cmpl_state->the_completion.text = (gchar*) g_realloc (cmpl_state->the_completion.text, i);
2926
2927   if (!cmpl_state->the_completion.text)
2928     return NULL;
2929   else
2930     {
2931       strcat (cmpl_state->the_completion.text, text);
2932       return &cmpl_state->the_completion;
2933     }
2934 }
2935
2936 static CompletionDir*
2937 find_completion_dir (gchar          *text_to_complete,
2938                     gchar          **remaining_text,
2939                     CompletionState *cmpl_state)
2940 {
2941   gchar* first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2942   CompletionDir* dir = cmpl_state->reference_dir;
2943   CompletionDir* next;
2944   *remaining_text = text_to_complete;
2945
2946   while (first_slash)
2947     {
2948       gint len = first_slash - *remaining_text;
2949       gint found = 0;
2950       gchar *found_name = NULL;         /* Quiet gcc */
2951       gint i;
2952       gchar* pat_buf = g_new (gchar, len + 1);
2953
2954       strncpy (pat_buf, *remaining_text, len);
2955       pat_buf[len] = 0;
2956
2957       for (i = 0; i < dir->sent->entry_count; i += 1)
2958         {
2959           if (dir->sent->entries[i].is_dir &&
2960              fnmatch (pat_buf, dir->sent->entries[i].entry_name,
2961                       FNMATCH_FLAGS)!= FNM_NOMATCH)
2962             {
2963               if (found)
2964                 {
2965                   g_free (pat_buf);
2966                   return dir;
2967                 }
2968               else
2969                 {
2970                   found = 1;
2971                   found_name = dir->sent->entries[i].entry_name;
2972                 }
2973             }
2974         }
2975
2976       if (!found)
2977         {
2978           /* Perhaps we are trying to open an automount directory */
2979           found_name = pat_buf;
2980         }
2981
2982       next = open_relative_dir (found_name, dir, cmpl_state);
2983       
2984       if (!next)
2985         {
2986           g_free (pat_buf);
2987           return NULL;
2988         }
2989       
2990       next->cmpl_parent = dir;
2991       
2992       dir = next;
2993       
2994       if (!correct_dir_fullname (dir))
2995         {
2996           g_free (pat_buf);
2997           return NULL;
2998         }
2999       
3000       *remaining_text = first_slash + 1;
3001       first_slash = strchr (*remaining_text, G_DIR_SEPARATOR);
3002
3003       g_free (pat_buf);
3004     }
3005
3006   return dir;
3007 }
3008
3009 static void
3010 update_cmpl (PossibleCompletion *poss,
3011              CompletionState    *cmpl_state)
3012 {
3013   gint cmpl_len;
3014
3015   if (!poss || !cmpl_is_a_completion (poss))
3016     return;
3017
3018   cmpl_len = strlen (cmpl_this_completion (poss));
3019
3020   if (cmpl_state->updated_text_alloc < cmpl_len + 1)
3021     {
3022       cmpl_state->updated_text =
3023         (gchar*)g_realloc (cmpl_state->updated_text,
3024                            cmpl_state->updated_text_alloc);
3025       cmpl_state->updated_text_alloc = 2*cmpl_len;
3026     }
3027
3028   if (cmpl_state->updated_text_len < 0)
3029     {
3030       strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3031       cmpl_state->updated_text_len = cmpl_len;
3032       cmpl_state->re_complete = cmpl_is_directory (poss);
3033     }
3034   else if (cmpl_state->updated_text_len == 0)
3035     {
3036       cmpl_state->re_complete = FALSE;
3037     }
3038   else
3039     {
3040       gint first_diff =
3041         first_diff_index (cmpl_state->updated_text,
3042                           cmpl_this_completion (poss));
3043
3044       cmpl_state->re_complete = FALSE;
3045
3046       if (first_diff == PATTERN_MATCH)
3047         return;
3048
3049       if (first_diff > cmpl_state->updated_text_len)
3050         strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3051
3052       cmpl_state->updated_text_len = first_diff;
3053       cmpl_state->updated_text[first_diff] = 0;
3054     }
3055 }
3056
3057 static PossibleCompletion*
3058 attempt_file_completion (CompletionState *cmpl_state)
3059 {
3060   gchar *pat_buf, *first_slash;
3061   CompletionDir *dir = cmpl_state->active_completion_dir;
3062
3063   dir->cmpl_index += 1;
3064
3065   if (dir->cmpl_index == dir->sent->entry_count)
3066     {
3067       if (dir->cmpl_parent == NULL)
3068         {
3069           cmpl_state->active_completion_dir = NULL;
3070
3071           return NULL;
3072         }
3073       else
3074         {
3075           cmpl_state->active_completion_dir = dir->cmpl_parent;
3076
3077           return attempt_file_completion (cmpl_state);
3078         }
3079     }
3080
3081   g_assert (dir->cmpl_text);
3082
3083   first_slash = strchr (dir->cmpl_text, G_DIR_SEPARATOR);
3084
3085   if (first_slash)
3086     {
3087       gint len = first_slash - dir->cmpl_text;
3088
3089       pat_buf = g_new (gchar, len + 1);
3090       strncpy (pat_buf, dir->cmpl_text, len);
3091       pat_buf[len] = 0;
3092     }
3093   else
3094     {
3095       gint len = strlen (dir->cmpl_text);
3096
3097       pat_buf = g_new (gchar, len + 2);
3098       strcpy (pat_buf, dir->cmpl_text);
3099       /* Don't append a * if the user entered one herself.
3100        * This way one can complete *.h and don't get matches
3101        * on any .help files, for instance.
3102        */
3103       if (strchr (pat_buf, '*') == NULL)
3104         strcpy (pat_buf + len, "*");
3105     }
3106
3107   if (first_slash)
3108     {
3109       if (dir->sent->entries[dir->cmpl_index].is_dir)
3110         {
3111           if (fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3112                        FNMATCH_FLAGS) != FNM_NOMATCH)
3113             {
3114               CompletionDir* new_dir;
3115
3116               new_dir = open_relative_dir (dir->sent->entries[dir->cmpl_index].entry_name,
3117                                            dir, cmpl_state);
3118
3119               if (!new_dir)
3120                 {
3121                   g_free (pat_buf);
3122                   return NULL;
3123                 }
3124
3125               new_dir->cmpl_parent = dir;
3126
3127               new_dir->cmpl_index = -1;
3128               new_dir->cmpl_text = first_slash + 1;
3129
3130               cmpl_state->active_completion_dir = new_dir;
3131
3132               g_free (pat_buf);
3133               return attempt_file_completion (cmpl_state);
3134             }
3135           else
3136             {
3137               g_free (pat_buf);
3138               return attempt_file_completion (cmpl_state);
3139             }
3140         }
3141       else
3142         {
3143           g_free (pat_buf);
3144           return attempt_file_completion (cmpl_state);
3145         }
3146     }
3147   else
3148     {
3149       if (dir->cmpl_parent != NULL)
3150         {
3151           append_completion_text (dir->fullname +
3152                                   strlen (cmpl_state->completion_dir->fullname) + 1,
3153                                   cmpl_state);
3154           append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3155         }
3156
3157       append_completion_text (dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
3158
3159       cmpl_state->the_completion.is_a_completion =
3160         fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3161                  FNMATCH_FLAGS) != FNM_NOMATCH;
3162
3163       cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3164       if (dir->sent->entries[dir->cmpl_index].is_dir)
3165         append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3166
3167       g_free (pat_buf);
3168       return &cmpl_state->the_completion;
3169     }
3170 }
3171
3172 #ifdef HAVE_PWD_H
3173
3174 static gint
3175 get_pwdb (CompletionState* cmpl_state)
3176 {
3177   struct passwd *pwd_ptr;
3178   gchar* buf_ptr;
3179   gchar *utf8;
3180   gint len = 0, i, count = 0;
3181
3182   if (cmpl_state->user_dir_name_buffer)
3183     return TRUE;
3184   setpwent ();
3185
3186   while ((pwd_ptr = getpwent ()) != NULL)
3187     {
3188       utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3189       len += strlen (utf8);
3190       g_free (utf8);
3191       utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3192       len += strlen (utf8);
3193       g_free (utf8);
3194       len += 2;
3195       count += 1;
3196     }
3197
3198   setpwent ();
3199
3200   cmpl_state->user_dir_name_buffer = g_new (gchar, len);
3201   cmpl_state->user_directories = g_new (CompletionUserDir, count);
3202   cmpl_state->user_directories_len = count;
3203
3204   buf_ptr = cmpl_state->user_dir_name_buffer;
3205
3206   for (i = 0; i < count; i += 1)
3207     {
3208       pwd_ptr = getpwent ();
3209       if (!pwd_ptr)
3210         {
3211           cmpl_errno = errno;
3212           goto error;
3213         }
3214
3215       utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3216       strcpy (buf_ptr, utf8);
3217       g_free (utf8);
3218
3219       cmpl_state->user_directories[i].login = buf_ptr;
3220
3221       buf_ptr += strlen (buf_ptr);
3222       buf_ptr += 1;
3223
3224       utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3225       strcpy (buf_ptr, utf8);
3226       g_free (utf8);
3227
3228       cmpl_state->user_directories[i].homedir = buf_ptr;
3229
3230       buf_ptr += strlen (buf_ptr);
3231       buf_ptr += 1;
3232     }
3233
3234   qsort (cmpl_state->user_directories,
3235          cmpl_state->user_directories_len,
3236          sizeof (CompletionUserDir),
3237          compare_user_dir);
3238
3239   endpwent ();
3240
3241   return TRUE;
3242
3243 error:
3244
3245   if (cmpl_state->user_dir_name_buffer)
3246     g_free (cmpl_state->user_dir_name_buffer);
3247   if (cmpl_state->user_directories)
3248     g_free (cmpl_state->user_directories);
3249
3250   cmpl_state->user_dir_name_buffer = NULL;
3251   cmpl_state->user_directories = NULL;
3252
3253   return FALSE;
3254 }
3255
3256 static gint
3257 compare_user_dir (const void *a,
3258                   const void *b)
3259 {
3260   return strcmp ((((CompletionUserDir*)a))->login,
3261                  (((CompletionUserDir*)b))->login);
3262 }
3263
3264 #endif
3265
3266 static gint
3267 compare_cmpl_dir (const void *a,
3268                   const void *b)
3269 {
3270 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
3271   return strcmp ((((CompletionDirEntry*)a))->entry_name,
3272                  (((CompletionDirEntry*)b))->entry_name);
3273 #else
3274   return g_strcasecmp ((((CompletionDirEntry*)a))->entry_name,
3275                        (((CompletionDirEntry*)b))->entry_name);
3276 #endif
3277 }
3278
3279 static gint
3280 cmpl_state_okay (CompletionState* cmpl_state)
3281 {
3282   return  cmpl_state && cmpl_state->reference_dir;
3283 }
3284
3285 static gchar*
3286 cmpl_strerror (gint err)
3287 {
3288   if (err == CMPL_ERRNO_TOO_LONG)
3289     return _("Name too long");
3290   else if (err == CMPL_ERRNO_DID_NOT_CONVERT)
3291     return _("Couldn't convert filename");
3292   else
3293     return g_strerror (err);
3294 }