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