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