]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilesel.c
e1e7d4998e16a70b0c4f1e63d2b8b98342e0160d
[~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 Library 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library 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 #include <stdio.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/param.h>
23 #include <dirent.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <pwd.h>
29 #include "fnmatch.h"
30
31 #include "gdk/gdkkeysyms.h"
32 #include "gtkbutton.h"
33 #include "gtkentry.h"
34 #include "gtkfilesel.h"
35 #include "gtkhbox.h"
36 #include "gtkhbbox.h"
37 #include "gtklabel.h"
38 #include "gtklist.h"
39 #include "gtklistitem.h"
40 #include "gtkmain.h"
41 #include "gtkscrolledwindow.h"
42 #include "gtksignal.h"
43 #include "gtkvbox.h"
44 #include "gtkmenu.h"
45 #include "gtkmenuitem.h"
46 #include "gtkoptionmenu.h"
47 #include "gtkclist.h"
48 #include "gtkdialog.h"
49
50 #define DIR_LIST_WIDTH   180
51 #define DIR_LIST_HEIGHT  180
52 #define FILE_LIST_WIDTH  180
53 #define FILE_LIST_HEIGHT 180
54
55 /* I've put this here so it doesn't get confused with the 
56  * file completion interface */
57 typedef struct _HistoryCallbackArg HistoryCallbackArg;
58
59 struct _HistoryCallbackArg
60 {
61   gchar *directory;
62   GtkWidget *menu_item;
63 };
64
65
66 typedef struct _CompletionState    CompletionState;
67 typedef struct _CompletionDir      CompletionDir;
68 typedef struct _CompletionDirSent  CompletionDirSent;
69 typedef struct _CompletionDirEntry CompletionDirEntry;
70 typedef struct _CompletionUserDir  CompletionUserDir;
71 typedef struct _PossibleCompletion PossibleCompletion;
72
73 /* Non-external file completion decls and structures */
74
75 /* A contant telling PRCS how many directories to cache.  Its actually
76  * kept in a list, so the geometry isn't important. */
77 #define CMPL_DIRECTORY_CACHE_SIZE 10
78
79 /* A constant used to determine whether a substring was an exact
80  * match by first_diff_index()
81  */
82 #define PATTERN_MATCH -1
83 /* The arguments used by all fnmatch() calls below
84  */
85 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
86
87 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
88
89 /* This structure contains all the useful information about a directory
90  * for the purposes of filename completion.  These structures are cached
91  * in the CompletionState struct.  CompletionDir's are reference counted.
92  */
93 struct _CompletionDirSent
94 {
95   ino_t inode;
96   time_t mtime;
97   dev_t device;
98
99   gint entry_count;
100   gchar *name_buffer; /* memory segment containing names of all entries */
101
102   struct _CompletionDirEntry *entries;
103 };
104
105 struct _CompletionDir
106 {
107   CompletionDirSent *sent;
108
109   gchar *fullname;
110   gint fullname_len;
111
112   struct _CompletionDir *cmpl_parent;
113   gint cmpl_index;
114   gchar *cmpl_text;
115 };
116
117 /* This structure contains pairs of directory entry names with a flag saying
118  * whether or not they are a valid directory.  NOTE: This information is used
119  * to provide the caller with information about whether to update its completions
120  * or try to open a file.  Since directories are cached by the directory mtime,
121  * a symlink which points to an invalid file (which will not be a directory),
122  * will not be reevaluated if that file is created, unless the containing
123  * directory is touched.  I consider this case to be worth ignoring (josh).
124  */
125 struct _CompletionDirEntry
126 {
127   gint is_dir;
128   gchar *entry_name;
129 };
130
131 struct _CompletionUserDir
132 {
133   gchar *login;
134   gchar *homedir;
135 };
136
137 struct _PossibleCompletion
138 {
139   /* accessible fields, all are accessed externally by functions
140    * declared above
141    */
142   gchar *text;
143   gint is_a_completion;
144   gint is_directory;
145
146   /* Private fields
147    */
148   gint text_alloc;
149 };
150
151 struct _CompletionState
152 {
153   gint last_valid_char;
154   gchar *updated_text;
155   gint updated_text_len;
156   gint updated_text_alloc;
157   gint re_complete;
158
159   gchar *user_dir_name_buffer;
160   gint user_directories_len;
161   gchar *user_home_dir;
162
163   gchar *last_completion_text;
164
165   gint user_completion_index; /* if >= 0, currently completing ~user */
166
167   struct _CompletionDir *completion_dir; /* directory completing from */
168   struct _CompletionDir *active_completion_dir;
169
170   struct _PossibleCompletion the_completion;
171
172   struct _CompletionDir *reference_dir; /* initial directory */
173
174   GList* directory_storage;
175   GList* directory_sent_storage;
176
177   struct _CompletionUserDir *user_directories;
178 };
179
180
181 /* File completion functions which would be external, were they used
182  * outside of this file.
183  */
184
185 static CompletionState*    cmpl_init_state        (void);
186 static void                cmpl_free_state        (CompletionState *cmpl_state);
187 static gint                cmpl_state_okay        (CompletionState* cmpl_state);
188 static gchar*              cmpl_strerror          (gint);
189
190 static PossibleCompletion* cmpl_completion_matches(gchar           *text_to_complete,
191                                                    gchar          **remaining_text,
192                                                    CompletionState *cmpl_state);
193
194 /* Returns a name for consideration, possibly a completion, this name
195  * will be invalid after the next call to cmpl_next_completion.
196  */
197 static char*               cmpl_this_completion   (PossibleCompletion*);
198
199 /* True if this completion matches the given text.  Otherwise, this
200  * output can be used to have a list of non-completions.
201  */
202 static gint                cmpl_is_a_completion   (PossibleCompletion*);
203
204 /* True if the completion is a directory
205  */
206 static gint                cmpl_is_directory      (PossibleCompletion*);
207
208 /* Obtains the next completion, or NULL
209  */
210 static PossibleCompletion* cmpl_next_completion   (CompletionState*);
211
212 /* Updating completions: the return value of cmpl_updated_text() will
213  * be text_to_complete completed as much as possible after the most
214  * recent call to cmpl_completion_matches.  For the present
215  * application, this is the suggested replacement for the user's input
216  * string.  You must CALL THIS AFTER ALL cmpl_text_completions have
217  * been received.
218  */
219 static gchar*              cmpl_updated_text       (CompletionState* cmpl_state);
220
221 /* After updating, to see if the completion was a directory, call
222  * this.  If it was, you should consider re-calling completion_matches.
223  */
224 static gint                cmpl_updated_dir        (CompletionState* cmpl_state);
225
226 /* Current location: if using file completion, return the current
227  * directory, from which file completion begins.  More specifically,
228  * the cwd concatenated with all exact completions up to the last
229  * directory delimiter('/').
230  */
231 static gchar*              cmpl_reference_position (CompletionState* cmpl_state);
232
233 /* backing up: if cmpl_completion_matches returns NULL, you may query
234  * the index of the last completable character into cmpl_updated_text.
235  */
236 static gint                cmpl_last_valid_char    (CompletionState* cmpl_state);
237
238 /* When the user selects a non-directory, call cmpl_completion_fullname
239  * to get the full name of the selected file.
240  */
241 static gchar*              cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
242
243
244 /* Directory operations. */
245 static CompletionDir* open_ref_dir         (gchar* text_to_complete,
246                                             gchar** remaining_text,
247                                             CompletionState* cmpl_state);
248 static CompletionDir* open_dir             (gchar* dir_name,
249                                             CompletionState* cmpl_state);
250 static CompletionDir* open_user_dir        (gchar* text_to_complete,
251                                             CompletionState *cmpl_state);
252 static CompletionDir* open_relative_dir    (gchar* dir_name, CompletionDir* dir,
253                                             CompletionState *cmpl_state);
254 static CompletionDirSent* open_new_dir         (gchar* dir_name, struct stat* sbuf);
255 static gint           correct_dir_fullname (CompletionDir* cmpl_dir);
256 static gint           correct_parent       (CompletionDir* cmpl_dir,
257                                             struct stat *sbuf);
258 static gchar*         find_parent_dir_fullname    (gchar* dirname);
259 static CompletionDir* attach_dir           (CompletionDirSent* sent,
260                                             gchar* dir_name,
261                                             CompletionState *cmpl_state);
262 static void           free_dir_sent (CompletionDirSent* sent);
263 static void           free_dir      (CompletionDir  *dir);
264 static void           prune_memory_usage(CompletionState *cmpl_state);
265
266 /* Completion operations */
267 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
268                                                       CompletionState *cmpl_state);
269 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
270 static CompletionDir* find_completion_dir(gchar* text_to_complete,
271                                           gchar** remaining_text,
272                                           CompletionState* cmpl_state);
273 static PossibleCompletion* append_completion_text(gchar* text,
274                                                   CompletionState* cmpl_state);
275 static gint get_pwdb(CompletionState* cmpl_state);
276 static gint first_diff_index(gchar* pat, gchar* text);
277 static gint compare_user_dir(const void* a, const void* b);
278 static gint compare_cmpl_dir(const void* a, const void* b);
279 static void update_cmpl(PossibleCompletion* poss,
280                         CompletionState* cmpl_state);
281
282 static void gtk_file_selection_class_init    (GtkFileSelectionClass *klass);
283 static void gtk_file_selection_init          (GtkFileSelection      *filesel);
284 static void gtk_file_selection_destroy       (GtkObject             *object);
285 static gint gtk_file_selection_key_press     (GtkWidget             *widget,
286                                               GdkEventKey           *event,
287                                               gpointer               user_data);
288
289 static void gtk_file_selection_file_button (GtkWidget *widget,
290                                             gint row, 
291                                             gint column, 
292                                             GdkEventButton *bevent,
293                                             gpointer user_data);
294
295 static void gtk_file_selection_dir_button (GtkWidget *widget,
296                                            gint row, 
297                                            gint column, 
298                                            GdkEventButton *bevent,
299                                            gpointer data);
300
301 static void gtk_file_selection_populate      (GtkFileSelection      *fs,
302                                               gchar                 *rel_path,
303                                               gint                   try_complete);
304 static void gtk_file_selection_abort         (GtkFileSelection      *fs);
305
306 static void gtk_file_selection_update_history_menu (GtkFileSelection       *fs,
307                                                     gchar                  *current_dir);
308
309 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
310 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
311 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
312
313
314
315 static GtkWindowClass *parent_class = NULL;
316
317 /* Saves errno when something cmpl does fails. */
318 static gint cmpl_errno;
319
320 guint
321 gtk_file_selection_get_type ()
322 {
323   static guint file_selection_type = 0;
324
325   if (!file_selection_type)
326     {
327       GtkTypeInfo filesel_info =
328       {
329         "GtkFileSelection",
330         sizeof (GtkFileSelection),
331         sizeof (GtkFileSelectionClass),
332         (GtkClassInitFunc) gtk_file_selection_class_init,
333         (GtkObjectInitFunc) gtk_file_selection_init,
334         (GtkArgSetFunc) NULL,
335         (GtkArgGetFunc) NULL,
336       };
337
338       file_selection_type = gtk_type_unique (gtk_window_get_type (), &filesel_info);
339     }
340
341   return file_selection_type;
342 }
343
344 static void
345 gtk_file_selection_class_init (GtkFileSelectionClass *class)
346 {
347   GtkObjectClass *object_class;
348
349   object_class = (GtkObjectClass*) class;
350
351   parent_class = gtk_type_class (gtk_window_get_type ());
352
353   object_class->destroy = gtk_file_selection_destroy;
354 }
355
356 static void
357 gtk_file_selection_init (GtkFileSelection *filesel)
358 {
359   GtkWidget *entry_vbox;
360   GtkWidget *label;
361   GtkWidget *list_hbox;
362   GtkWidget *confirm_area;
363   GtkWidget *pulldown_hbox;
364   char *dir_title [] = { "Directories", };
365   char *file_title [] = { "Files", };
366   
367   filesel->cmpl_state = cmpl_init_state ();
368
369   /* The dialog-sized vertical box  */
370   filesel->main_vbox = gtk_vbox_new (FALSE, 10);
371   gtk_container_border_width (GTK_CONTAINER (filesel), 10);
372   gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
373   gtk_widget_show (filesel->main_vbox);
374
375   /* The horizontal box containing create, rename etc. buttons */
376   filesel->button_area = gtk_hbutton_box_new ();
377   gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START);
378   gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0);
379   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area, 
380                       FALSE, FALSE, 0);
381   gtk_widget_show (filesel->button_area);
382   
383   gtk_file_selection_show_fileop_buttons(filesel);
384
385   /* hbox for pulldown menu */
386   pulldown_hbox = gtk_hbox_new (TRUE, 5);
387   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
388   gtk_widget_show (pulldown_hbox);
389   
390   /* Pulldown menu */
391   filesel->history_pulldown = gtk_option_menu_new ();
392   gtk_widget_show (filesel->history_pulldown);
393   gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown, 
394                       FALSE, FALSE, 0);
395     
396   /*  The horizontal box containing the directory and file listboxes  */
397   list_hbox = gtk_hbox_new (FALSE, 5);
398   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
399   gtk_widget_show (list_hbox);
400
401   /* The directories clist */
402   filesel->dir_list = gtk_clist_new_with_titles (1, dir_title);
403   gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
404   gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
405                       (GtkSignalFunc) gtk_file_selection_dir_button, 
406                       (gpointer) filesel);
407   gtk_clist_set_policy (GTK_CLIST (filesel->dir_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
408   gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
409   gtk_container_border_width (GTK_CONTAINER (filesel->dir_list), 5);
410   gtk_box_pack_start (GTK_BOX (list_hbox), filesel->dir_list, TRUE, TRUE, 0);
411   gtk_widget_show (filesel->dir_list);
412
413   /* The files clist */
414   filesel->file_list = gtk_clist_new_with_titles (1, file_title);
415   gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
416   gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
417                       (GtkSignalFunc) gtk_file_selection_file_button, 
418                       (gpointer) filesel);
419   gtk_clist_set_policy (GTK_CLIST (filesel->file_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
420   gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
421   gtk_container_border_width (GTK_CONTAINER (filesel->file_list), 5);
422   gtk_box_pack_start (GTK_BOX (list_hbox), filesel->file_list, TRUE, TRUE, 0);
423   gtk_widget_show (filesel->file_list);
424
425   /* action area for packing buttons into. */
426   filesel->action_area = gtk_hbox_new (TRUE, 0);
427   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area, 
428                       FALSE, FALSE, 0);
429   gtk_widget_show (filesel->action_area);
430   
431   /*  The OK/Cancel button area */
432   confirm_area = gtk_hbutton_box_new ();
433   gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END);
434   gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5);
435   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0);
436   gtk_widget_show (confirm_area);
437
438   /*  The OK button  */
439   filesel->ok_button = gtk_button_new_with_label ("OK");
440   GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
441   gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0);
442   gtk_widget_grab_default (filesel->ok_button);
443   gtk_widget_show (filesel->ok_button);
444
445   /*  The Cancel button  */
446   filesel->cancel_button = gtk_button_new_with_label ("Cancel");
447   GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
448   gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0);
449   gtk_widget_show (filesel->cancel_button);
450
451   /*  The selection entry widget  */
452   entry_vbox = gtk_vbox_new (FALSE, 2);
453   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
454   gtk_widget_show (entry_vbox);
455
456   filesel->selection_text = label = gtk_label_new ("");
457   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
458   gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
459   gtk_widget_show (label);
460
461   filesel->selection_entry = gtk_entry_new ();
462   gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
463                       (GtkSignalFunc) gtk_file_selection_key_press, filesel);
464   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
465                              (GtkSignalFunc) gtk_widget_grab_default,
466                              GTK_OBJECT (filesel->ok_button));
467   gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
468                              (GtkSignalFunc) gtk_button_clicked,
469                              GTK_OBJECT (filesel->ok_button));
470   gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
471   gtk_widget_show (filesel->selection_entry);
472
473   if (!cmpl_state_okay (filesel->cmpl_state))
474     {
475       gchar err_buf[256];
476
477       sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
478
479       gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
480     }
481   else
482     {
483       gtk_file_selection_populate (filesel, "", FALSE);
484     }
485
486   gtk_widget_grab_focus (filesel->selection_entry);
487 }
488
489 GtkWidget*
490 gtk_file_selection_new (const gchar *title)
491 {
492   GtkFileSelection *filesel;
493
494   filesel = gtk_type_new (gtk_file_selection_get_type ());
495   gtk_window_set_title (GTK_WINDOW (filesel), title);
496
497   return GTK_WIDGET (filesel);
498 }
499
500 void
501 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
502 {
503   g_return_if_fail (filesel != NULL);
504   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
505     
506   /* delete, create directory, and rename */
507   if (!filesel->fileop_c_dir) 
508     {
509       filesel->fileop_c_dir = gtk_button_new_with_label ("Create Dir");
510       gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
511                           (GtkSignalFunc) gtk_file_selection_create_dir, 
512                           (gpointer) filesel);
513       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
514                           filesel->fileop_c_dir, TRUE, TRUE, 0);
515       gtk_widget_show (filesel->fileop_c_dir);
516     }
517         
518   if (!filesel->fileop_del_file) 
519     {
520       filesel->fileop_del_file = gtk_button_new_with_label ("Delete File");
521       gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
522                           (GtkSignalFunc) gtk_file_selection_delete_file, 
523                           (gpointer) filesel);
524       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
525                           filesel->fileop_del_file, TRUE, TRUE, 0);
526       gtk_widget_show (filesel->fileop_del_file);
527     }
528
529   if (!filesel->fileop_ren_file)
530     {
531       filesel->fileop_ren_file = gtk_button_new_with_label ("Rename File");
532       gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
533                           (GtkSignalFunc) gtk_file_selection_rename_file, 
534                           (gpointer) filesel);
535       gtk_box_pack_start (GTK_BOX (filesel->button_area), 
536                           filesel->fileop_ren_file, TRUE, TRUE, 0);
537       gtk_widget_show (filesel->fileop_ren_file);
538     }
539
540   gtk_widget_queue_resize(GTK_WIDGET(filesel));
541 }
542
543 void       
544 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
545 {
546   g_return_if_fail (filesel != NULL);
547   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
548     
549   if (filesel->fileop_ren_file) 
550     {
551       gtk_widget_destroy (filesel->fileop_ren_file);
552       filesel->fileop_ren_file = NULL;
553     }
554
555   if (filesel->fileop_del_file)
556     {
557       gtk_widget_destroy (filesel->fileop_del_file);
558       filesel->fileop_del_file = NULL;
559     }
560
561   if (filesel->fileop_c_dir)
562     {
563       gtk_widget_destroy (filesel->fileop_c_dir);
564       filesel->fileop_c_dir = NULL;
565     }
566 }
567
568
569
570 void
571 gtk_file_selection_set_filename (GtkFileSelection *filesel,
572                                  const gchar      *filename)
573 {
574   char  buf[MAXPATHLEN];
575   const char *name, *last_slash;
576
577   g_return_if_fail (filesel != NULL);
578   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
579   g_return_if_fail (filename != NULL);
580
581   last_slash = strrchr (filename, '/');
582
583   if (!last_slash)
584     {
585       buf[0] = 0;
586       name = filename;
587     }
588   else
589     {
590       gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
591
592       strncpy (buf, filename, len);
593       buf[len] = 0;
594
595       name = last_slash + 1;
596     }
597
598   gtk_file_selection_populate (filesel, buf, FALSE);
599
600   if (filesel->selection_entry)
601     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
602 }
603
604 gchar*
605 gtk_file_selection_get_filename (GtkFileSelection *filesel)
606 {
607   static char nothing[2] = "";
608   char *text;
609   char *filename;
610
611   g_return_val_if_fail (filesel != NULL, nothing);
612   g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
613
614   text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
615   if (text)
616     {
617       filename = cmpl_completion_fullname (text, filesel->cmpl_state);
618       return filename;
619     }
620
621   return nothing;
622 }
623
624 static void
625 gtk_file_selection_destroy (GtkObject *object)
626 {
627   GtkFileSelection *filesel;
628   GList *list;
629   HistoryCallbackArg *callback_arg;
630
631   g_return_if_fail (object != NULL);
632   g_return_if_fail (GTK_IS_FILE_SELECTION (object));
633
634   filesel = GTK_FILE_SELECTION (object);
635   
636   if (filesel->fileop_dialog)
637           gtk_widget_destroy (filesel->fileop_dialog);
638   
639   if (filesel->history_list)
640     {
641       list = filesel->history_list;
642       while (list)
643         {
644           callback_arg = list->data;
645           g_free (callback_arg->directory);
646           g_free (callback_arg);
647           list = list->next;
648         }
649       g_list_free (filesel->history_list);
650       filesel->history_list = NULL;
651     }
652   
653   cmpl_free_state (filesel->cmpl_state);
654   filesel->cmpl_state = NULL;
655
656   if (GTK_OBJECT_CLASS (parent_class)->destroy)
657     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
658 }
659
660 /* Begin file operations callbacks */
661
662 static void
663 gtk_file_selection_fileop_error (gchar *error_message)
664 {
665   GtkWidget *label;
666   GtkWidget *vbox;
667   GtkWidget *button;
668   GtkWidget *dialog;
669   
670   g_return_if_fail (error_message != NULL);
671   
672   /* main dialog */
673   dialog = gtk_dialog_new ();
674   /*
675   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
676                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
677                       (gpointer) fs);
678   */
679   gtk_window_set_title (GTK_WINDOW (dialog), "Error");
680   gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
681   
682   vbox = gtk_vbox_new(FALSE, 0);
683   gtk_container_border_width(GTK_CONTAINER(vbox), 8);
684   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
685                      FALSE, FALSE, 0);
686   gtk_widget_show(vbox);
687
688   label = gtk_label_new(error_message);
689   gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
690   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
691   gtk_widget_show(label);
692
693   /* yes, we free it */
694   g_free (error_message);
695   
696   /* close button */
697   button = gtk_button_new_with_label ("Close");
698   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
699                              (GtkSignalFunc) gtk_widget_destroy, 
700                              (gpointer) dialog);
701   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
702                      button, TRUE, TRUE, 0);
703   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
704   gtk_widget_grab_default(button);
705   gtk_widget_show (button);
706
707   gtk_widget_show (dialog);
708 }
709
710 static void
711 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
712 {
713   GtkFileSelection *fs = data;
714
715   g_return_if_fail (fs != NULL);
716   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
717   
718   fs->fileop_dialog = NULL;
719 }
720
721
722 static void
723 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
724 {
725   GtkFileSelection *fs = data;
726   gchar *dirname;
727   gchar *path;
728   gchar *full_path;
729   gchar *buf;
730   CompletionState *cmpl_state;
731   
732   g_return_if_fail (fs != NULL);
733   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
734
735   dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
736   cmpl_state = (CompletionState*) fs->cmpl_state;
737   path = cmpl_reference_position (cmpl_state);
738   
739   full_path = g_strconcat (path, "/", dirname, NULL);
740   if ( (mkdir (full_path, 0755) < 0) ) 
741     {
742       buf = g_strconcat ("Error creating directory \"", dirname, "\":  ", 
743                          g_strerror(errno), NULL);
744       gtk_file_selection_fileop_error (buf);
745     }
746   g_free (full_path);
747   
748   gtk_widget_destroy (fs->fileop_dialog);
749   gtk_file_selection_populate (fs, "", FALSE);
750 }
751   
752 static void
753 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
754 {
755   GtkFileSelection *fs = data;
756   GtkWidget *label;
757   GtkWidget *dialog;
758   GtkWidget *vbox;
759   GtkWidget *button;
760
761   g_return_if_fail (fs != NULL);
762   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
763
764   if (fs->fileop_dialog)
765           return;
766   
767   /* main dialog */
768   fs->fileop_dialog = dialog = gtk_dialog_new ();
769   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
770                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
771                       (gpointer) fs);
772   gtk_window_set_title (GTK_WINDOW (dialog), "Create Directory");
773   gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
774   gtk_widget_show (dialog);
775   
776   vbox = gtk_vbox_new(FALSE, 0);
777   gtk_container_border_width(GTK_CONTAINER(vbox), 8);
778   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
779                      FALSE, FALSE, 0);
780   gtk_widget_show(vbox);
781   
782   label = gtk_label_new("Directory name:");
783   gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
784   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
785   gtk_widget_show(label);
786
787   /*  The directory entry widget  */
788   fs->fileop_entry = gtk_entry_new ();
789   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, 
790                       TRUE, TRUE, 5);
791   GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
792   gtk_widget_show (fs->fileop_entry);
793   
794   /* buttons */
795   button = gtk_button_new_with_label ("Create");
796   gtk_signal_connect (GTK_OBJECT (button), "clicked",
797                       (GtkSignalFunc) gtk_file_selection_create_dir_confirmed, 
798                       (gpointer) fs);
799   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
800                      button, TRUE, TRUE, 0);
801   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
802   gtk_widget_show(button);
803   
804   button = gtk_button_new_with_label ("Cancel");
805   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
806                              (GtkSignalFunc) gtk_widget_destroy, 
807                              (gpointer) dialog);
808   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
809                      button, TRUE, TRUE, 0);
810   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
811   gtk_widget_grab_default(button);
812   gtk_widget_show (button);
813 }
814
815 static void
816 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
817 {
818   GtkFileSelection *fs = data;
819   CompletionState *cmpl_state;
820   gchar *path;
821   gchar *full_path;
822   gchar *buf;
823   
824   g_return_if_fail (fs != NULL);
825   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
826
827   cmpl_state = (CompletionState*) fs->cmpl_state;
828   path = cmpl_reference_position (cmpl_state);
829   
830   full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
831   if ( (unlink (full_path) < 0) ) 
832     {
833       buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\":  ", 
834                          g_strerror(errno), NULL);
835       gtk_file_selection_fileop_error (buf);
836     }
837   g_free (full_path);
838   
839   gtk_widget_destroy (fs->fileop_dialog);
840   gtk_file_selection_populate (fs, "", FALSE);
841 }
842
843 static void
844 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
845 {
846   GtkFileSelection *fs = data;
847   GtkWidget *label;
848   GtkWidget *vbox;
849   GtkWidget *button;
850   GtkWidget *dialog;
851   gchar *filename;
852   gchar *buf;
853   
854   g_return_if_fail (fs != NULL);
855   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
856
857   if (fs->fileop_dialog)
858           return;
859
860   filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
861   if (strlen(filename) < 1)
862           return;
863
864   fs->fileop_file = filename;
865   
866   /* main dialog */
867   fs->fileop_dialog = dialog = gtk_dialog_new ();
868   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
869                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
870                       (gpointer) fs);
871   gtk_window_set_title (GTK_WINDOW (dialog), "Delete File");
872   gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
873   
874   vbox = gtk_vbox_new(FALSE, 0);
875   gtk_container_border_width(GTK_CONTAINER(vbox), 8);
876   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
877                      FALSE, FALSE, 0);
878   gtk_widget_show(vbox);
879
880   buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
881   label = gtk_label_new(buf);
882   gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
883   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
884   gtk_widget_show(label);
885   g_free(buf);
886   
887   /* buttons */
888   button = gtk_button_new_with_label ("Delete");
889   gtk_signal_connect (GTK_OBJECT (button), "clicked",
890                       (GtkSignalFunc) gtk_file_selection_delete_file_confirmed, 
891                       (gpointer) fs);
892   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
893                      button, TRUE, TRUE, 0);
894   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
895   gtk_widget_show(button);
896   
897   button = gtk_button_new_with_label ("Cancel");
898   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
899                              (GtkSignalFunc) gtk_widget_destroy, 
900                              (gpointer) dialog);
901   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
902                      button, TRUE, TRUE, 0);
903   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
904   gtk_widget_grab_default(button);
905   gtk_widget_show (button);
906
907   gtk_widget_show (dialog);
908 }
909
910 static void
911 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
912 {
913   GtkFileSelection *fs = data;
914   gchar *buf;
915   gchar *file;
916   gchar *path;
917   gchar *new_filename;
918   gchar *old_filename;
919   CompletionState *cmpl_state;
920   
921   g_return_if_fail (fs != NULL);
922   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
923
924   file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
925   cmpl_state = (CompletionState*) fs->cmpl_state;
926   path = cmpl_reference_position (cmpl_state);
927   
928   new_filename = g_strconcat (path, "/", file, NULL);
929   old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
930
931   if ( (rename (old_filename, new_filename)) < 0) 
932     {
933       buf = g_strconcat ("Error renaming file \"", file, "\":  ", 
934                          g_strerror(errno), NULL);
935       gtk_file_selection_fileop_error (buf);
936     }
937   g_free (new_filename);
938   g_free (old_filename);
939   
940   gtk_widget_destroy (fs->fileop_dialog);
941   gtk_file_selection_populate (fs, "", FALSE);
942 }
943   
944 static void
945 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
946 {
947   GtkFileSelection *fs = data;
948   GtkWidget *label;
949   GtkWidget *dialog;
950   GtkWidget *vbox;
951   GtkWidget *button;
952   gchar *buf;
953   
954   g_return_if_fail (fs != NULL);
955   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
956
957   if (fs->fileop_dialog)
958           return;
959
960   fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
961   if (strlen(fs->fileop_file) < 1)
962           return;
963   
964   /* main dialog */
965   fs->fileop_dialog = dialog = gtk_dialog_new ();
966   gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
967                       (GtkSignalFunc) gtk_file_selection_fileop_destroy, 
968                       (gpointer) fs);
969   gtk_window_set_title (GTK_WINDOW (dialog), "Rename File");
970   gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
971   gtk_widget_show (dialog);
972   
973   vbox = gtk_vbox_new(FALSE, 0);
974   gtk_container_border_width(GTK_CONTAINER(vbox), 8);
975   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
976                      FALSE, FALSE, 0);
977   gtk_widget_show(vbox);
978   
979   buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
980   label = gtk_label_new(buf);
981   gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
982   gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
983   gtk_widget_show(label);
984   g_free(buf);
985
986   /* New filename entry */
987   fs->fileop_entry = gtk_entry_new ();
988   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, 
989                       TRUE, TRUE, 5);
990   GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
991   gtk_widget_show (fs->fileop_entry);
992   
993   gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
994   gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
995                               0, strlen (fs->fileop_file));
996
997   /* buttons */
998   button = gtk_button_new_with_label ("Rename");
999   gtk_signal_connect (GTK_OBJECT (button), "clicked",
1000                       (GtkSignalFunc) gtk_file_selection_rename_file_confirmed, 
1001                       (gpointer) fs);
1002   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1003                      button, TRUE, TRUE, 0);
1004   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1005   gtk_widget_show(button);
1006   
1007   button = gtk_button_new_with_label ("Cancel");
1008   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1009                              (GtkSignalFunc) gtk_widget_destroy, 
1010                              (gpointer) dialog);
1011   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1012                      button, TRUE, TRUE, 0);
1013   GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1014   gtk_widget_grab_default(button);
1015   gtk_widget_show (button);
1016 }
1017
1018
1019 static gint
1020 gtk_file_selection_key_press (GtkWidget   *widget,
1021                               GdkEventKey *event,
1022                               gpointer     user_data)
1023 {
1024   GtkFileSelection *fs;
1025   char *text;
1026
1027   g_return_val_if_fail (widget != NULL, FALSE);
1028   g_return_val_if_fail (event != NULL, FALSE);
1029
1030   if (event->keyval == GDK_Tab)
1031     {
1032       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1033
1034       fs = GTK_FILE_SELECTION (user_data);
1035       text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1036       gtk_file_selection_populate (fs, text, TRUE);
1037
1038       return TRUE;
1039     }
1040
1041   return FALSE;
1042 }
1043
1044
1045 static void
1046 gtk_file_selection_history_callback (GtkWidget *widget, gpointer data)
1047 {
1048   GtkFileSelection *fs = data;
1049   HistoryCallbackArg *callback_arg;
1050   GList *list;
1051
1052   g_return_if_fail (fs != NULL);
1053   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1054
1055   list = fs->history_list;
1056   
1057   while (list) {
1058     callback_arg = list->data;
1059     
1060     if (callback_arg->menu_item == widget)
1061       {
1062         gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1063         break;
1064       }
1065     
1066     list = list->next;
1067   }
1068 }
1069
1070 static void 
1071 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1072                                         gchar *current_directory)
1073 {
1074   HistoryCallbackArg *callback_arg;
1075   GtkWidget *menu_item;
1076   GList *list;
1077   gchar *current_dir;
1078   gint dir_len;
1079   gint i;
1080   
1081   g_return_if_fail (fs != NULL);
1082   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1083   g_return_if_fail (current_directory != NULL);
1084   
1085   list = fs->history_list;
1086
1087   if (fs->history_menu) 
1088     {
1089       while (list) {
1090         callback_arg = list->data;
1091         g_free (callback_arg->directory);
1092         g_free (callback_arg);
1093         list = list->next;
1094       }
1095       g_list_free (fs->history_list);
1096       fs->history_list = NULL;
1097       
1098       gtk_widget_destroy (fs->history_menu);
1099     }
1100   
1101   fs->history_menu = gtk_menu_new();
1102
1103   current_dir = g_strdup (current_directory);
1104
1105   dir_len = strlen (current_dir);
1106
1107   for (i = dir_len; i >= 0; i--)
1108     {
1109       /* the i == dir_len is to catch the full path for the first 
1110        * entry. */
1111       if ( (current_dir[i] == '/') || (i == dir_len))
1112         {
1113           /* another small hack to catch the full path */
1114           if (i != dir_len) 
1115                   current_dir[i + 1] = '\0';
1116           menu_item = gtk_menu_item_new_with_label (current_dir);
1117           
1118           callback_arg = g_new (HistoryCallbackArg, 1);
1119           callback_arg->menu_item = menu_item;
1120           
1121           /* since the autocompletion gets confused if you don't 
1122            * supply a trailing '/' on a dir entry, set the full
1123            * (current) path to "" which just refreshes the filesel */
1124           if (dir_len == i) {
1125             callback_arg->directory = g_strdup ("");
1126           } else {
1127             callback_arg->directory = g_strdup (current_dir);
1128           }
1129           
1130           fs->history_list = g_list_append (fs->history_list, callback_arg);
1131           
1132           gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1133                               (GtkSignalFunc) gtk_file_selection_history_callback,
1134                               (gpointer) fs);
1135           gtk_menu_append (GTK_MENU (fs->history_menu), menu_item);
1136           gtk_widget_show (menu_item);
1137         }
1138     }
1139
1140   gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown), 
1141                             fs->history_menu);
1142   g_free (current_dir);
1143 }
1144
1145 static void
1146 gtk_file_selection_file_button (GtkWidget *widget,
1147                                gint row, 
1148                                gint column, 
1149                                GdkEventButton *bevent,
1150                                gpointer user_data)
1151 {
1152   GtkFileSelection *fs = NULL;
1153   gchar *filename, *temp = NULL;
1154   
1155   g_return_if_fail (GTK_IS_CLIST (widget));
1156
1157   fs = user_data;
1158   g_return_if_fail (fs != NULL);
1159   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1160   
1161   gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1162   filename = g_strdup (temp);
1163
1164   if (bevent && filename)
1165     {
1166       switch (bevent->type)
1167         {
1168         case GDK_BUTTON_PRESS:
1169           gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1170           break;
1171           
1172         case GDK_2BUTTON_PRESS:
1173           gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1174           break;
1175           
1176         default:
1177           break;
1178         }
1179     }
1180
1181   if (filename)
1182     g_free (filename);
1183 }
1184
1185 static void
1186 gtk_file_selection_dir_button (GtkWidget *widget,
1187                                gint row, 
1188                                gint column, 
1189                                GdkEventButton *bevent,
1190                                gpointer user_data)
1191 {
1192   GtkFileSelection *fs = NULL;
1193   gchar *filename, *temp = NULL;
1194
1195   g_return_if_fail (GTK_IS_CLIST (widget));
1196
1197   fs = GTK_FILE_SELECTION (user_data);
1198   g_return_if_fail (fs != NULL);
1199   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1200
1201   gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1202   filename = g_strdup (temp);
1203
1204   if (bevent && filename)
1205     {
1206       switch (bevent->type)
1207         {
1208         case GDK_BUTTON_PRESS:
1209           gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1210           break;
1211           
1212         case GDK_2BUTTON_PRESS:
1213           gtk_file_selection_populate (fs, filename, FALSE);
1214           break;
1215           
1216         default:
1217           break;
1218         }
1219     }
1220   
1221   if (filename)
1222     g_free (filename);
1223 }
1224
1225 static void
1226 gtk_file_selection_populate (GtkFileSelection *fs,
1227                              gchar            *rel_path,
1228                              gint              try_complete)
1229 {
1230   CompletionState *cmpl_state;
1231   PossibleCompletion* poss;
1232   gchar* filename;
1233   gint row;
1234   gchar* rem_path = rel_path;
1235   gchar* sel_text;
1236   gchar* text[2];
1237   gint did_recurse = FALSE;
1238   gint possible_count = 0;
1239   gint selection_index = -1;
1240   gint file_list_width;
1241   gint dir_list_width;
1242   
1243   g_return_if_fail (fs != NULL);
1244   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1245   
1246   cmpl_state = (CompletionState*) fs->cmpl_state;
1247   poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1248
1249   if (!cmpl_state_okay (cmpl_state))
1250     {
1251       /* Something went wrong. */
1252       gtk_file_selection_abort (fs);
1253       return;
1254     }
1255
1256   g_assert (cmpl_state->reference_dir);
1257
1258   gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1259   gtk_clist_clear (GTK_CLIST (fs->dir_list));
1260   gtk_clist_freeze (GTK_CLIST (fs->file_list));
1261   gtk_clist_clear (GTK_CLIST (fs->file_list));
1262
1263   /* Set the dir_list to include ./ and ../ */
1264   text[1] = NULL;
1265   text[0] = "./";
1266   row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1267
1268   text[0] = "../";
1269   row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1270
1271   /*reset the max widths of the lists*/
1272   dir_list_width = gdk_string_width(fs->dir_list->style->font,"../");
1273   gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width);
1274   file_list_width = 1;
1275   gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width);
1276
1277   while (poss)
1278     {
1279       if (cmpl_is_a_completion (poss))
1280         {
1281           possible_count += 1;
1282
1283           filename = cmpl_this_completion (poss);
1284
1285           text[0] = filename;
1286           
1287           if (cmpl_is_directory (poss))
1288             {
1289               if (strcmp (filename, "./") != 0 &&
1290                   strcmp (filename, "../") != 0)
1291                 {
1292                   int width = gdk_string_width(fs->dir_list->style->font,
1293                                                filename);
1294                   row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1295                   if(width > dir_list_width)
1296                     {
1297                       dir_list_width = width;
1298                       gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,
1299                                                  width);
1300                     }
1301                 }
1302             }
1303           else
1304             {
1305               int width = gdk_string_width(fs->file_list->style->font,
1306                                            filename);
1307               row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1308               if(width > file_list_width)
1309                 {
1310                   file_list_width = width;
1311                   gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1312                                              width);
1313                 }
1314             }
1315         }
1316
1317       poss = cmpl_next_completion (cmpl_state);
1318     }
1319
1320   gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1321   gtk_clist_thaw (GTK_CLIST (fs->file_list));
1322
1323   /* File lists are set. */
1324
1325   g_assert (cmpl_state->reference_dir);
1326
1327   if (try_complete)
1328     {
1329
1330       /* User is trying to complete filenames, so advance the user's input
1331        * string to the updated_text, which is the common leading substring
1332        * of all possible completions, and if its a directory attempt
1333        * attempt completions in it. */
1334
1335       if (cmpl_updated_text (cmpl_state)[0])
1336         {
1337
1338           if (cmpl_updated_dir (cmpl_state))
1339             {
1340               gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1341
1342               did_recurse = TRUE;
1343
1344               gtk_file_selection_populate (fs, dir_name, TRUE);
1345
1346               g_free (dir_name);
1347             }
1348           else
1349             {
1350               if (fs->selection_entry)
1351                       gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1352                                           cmpl_updated_text (cmpl_state));
1353             }
1354         }
1355       else
1356         {
1357           selection_index = cmpl_last_valid_char (cmpl_state) -
1358                             (strlen (rel_path) - strlen (rem_path));
1359           if (fs->selection_entry)
1360             gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1361         }
1362     }
1363   else
1364     {
1365       if (fs->selection_entry)
1366         gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1367     }
1368
1369   if (!did_recurse)
1370     {
1371       if (fs->selection_entry)
1372         gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1373
1374       if (fs->selection_entry)
1375         {
1376           sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
1377                             sizeof ("Selection: "));
1378           strcpy (sel_text, "Selection: ");
1379           strcat (sel_text, cmpl_reference_position (cmpl_state));
1380
1381           gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
1382           g_free (sel_text);
1383         }
1384
1385       if (fs->history_pulldown) 
1386         {
1387           gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1388         }
1389       
1390     }
1391 }
1392
1393 static void
1394 gtk_file_selection_abort (GtkFileSelection *fs)
1395 {
1396   gchar err_buf[256];
1397
1398   sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
1399
1400   /*  BEEP gdk_beep();  */
1401
1402   if (fs->selection_entry)
1403     gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
1404 }
1405
1406 /**********************************************************************/
1407 /*                        External Interface                          */
1408 /**********************************************************************/
1409
1410 /* The four completion state selectors
1411  */
1412 static gchar*
1413 cmpl_updated_text (CompletionState* cmpl_state)
1414 {
1415   return cmpl_state->updated_text;
1416 }
1417
1418 static gint
1419 cmpl_updated_dir (CompletionState* cmpl_state)
1420 {
1421   return cmpl_state->re_complete;
1422 }
1423
1424 static gchar*
1425 cmpl_reference_position (CompletionState* cmpl_state)
1426 {
1427   return cmpl_state->reference_dir->fullname;
1428 }
1429
1430 static gint
1431 cmpl_last_valid_char (CompletionState* cmpl_state)
1432 {
1433   return cmpl_state->last_valid_char;
1434 }
1435
1436 static gchar*
1437 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1438 {
1439   if (text[0] == '/')
1440     {
1441       strcpy (cmpl_state->updated_text, text);
1442     }
1443   else if (text[0] == '~')
1444     {
1445       CompletionDir* dir;
1446       char* slash;
1447
1448       dir = open_user_dir (text, cmpl_state);
1449
1450       if (!dir)
1451         {
1452           /* spencer says just return ~something, so
1453            * for now just do it. */
1454           strcpy (cmpl_state->updated_text, text);
1455         }
1456       else
1457         {
1458
1459           strcpy (cmpl_state->updated_text, dir->fullname);
1460
1461           slash = strchr (text, '/');
1462
1463           if (slash)
1464             strcat (cmpl_state->updated_text, slash);
1465         }
1466     }
1467   else
1468     {
1469       strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1470       strcat (cmpl_state->updated_text, "/");
1471       strcat (cmpl_state->updated_text, text);
1472     }
1473
1474   return cmpl_state->updated_text;
1475 }
1476
1477 /* The three completion selectors
1478  */
1479 static gchar*
1480 cmpl_this_completion (PossibleCompletion* pc)
1481 {
1482   return pc->text;
1483 }
1484
1485 static gint
1486 cmpl_is_directory (PossibleCompletion* pc)
1487 {
1488   return pc->is_directory;
1489 }
1490
1491 static gint
1492 cmpl_is_a_completion (PossibleCompletion* pc)
1493 {
1494   return pc->is_a_completion;
1495 }
1496
1497 /**********************************************************************/
1498 /*                       Construction, deletion                       */
1499 /**********************************************************************/
1500
1501 static CompletionState*
1502 cmpl_init_state (void)
1503 {
1504   gchar getcwd_buf[2*MAXPATHLEN];
1505   CompletionState *new_state;
1506
1507   new_state = g_new (CompletionState, 1);
1508
1509   /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
1510    * and, if that wasn't bad enough, hangs in doing so.
1511    */
1512 #if defined(sun) && !defined(__SVR4)
1513   if (!getwd (getcwd_buf))
1514 #else    
1515   if (!getcwd (getcwd_buf, MAXPATHLEN))
1516 #endif    
1517     {
1518       cmpl_errno = errno;
1519       return NULL;
1520     }
1521
1522   new_state->reference_dir = NULL;
1523   new_state->completion_dir = NULL;
1524   new_state->active_completion_dir = NULL;
1525
1526   if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1527     {
1528       /* if this fails, get_pwdb will fill it in. */
1529       new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1530     }
1531
1532   new_state->directory_storage = NULL;
1533   new_state->directory_sent_storage = NULL;
1534   new_state->last_valid_char = 0;
1535   new_state->updated_text = g_new (gchar, MAXPATHLEN);
1536   new_state->updated_text_alloc = MAXPATHLEN;
1537   new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1538   new_state->the_completion.text_alloc = MAXPATHLEN;
1539   new_state->user_dir_name_buffer = NULL;
1540   new_state->user_directories = NULL;
1541
1542   new_state->reference_dir =  open_dir (getcwd_buf, new_state);
1543
1544   if (!new_state->reference_dir)
1545     return NULL;
1546
1547   return new_state;
1548 }
1549
1550 static void
1551 cmpl_free_dir_list(GList* dp0)
1552 {
1553   GList *dp = dp0;
1554
1555   while (dp) {
1556     free_dir (dp->data);
1557     dp = dp->next;
1558   }
1559
1560   g_list_free(dp0);
1561 }
1562
1563 static void
1564 cmpl_free_dir_sent_list(GList* dp0)
1565 {
1566   GList *dp = dp0;
1567
1568   while (dp) {
1569     free_dir_sent (dp->data);
1570     dp = dp->next;
1571   }
1572
1573   g_list_free(dp0);
1574 }
1575
1576 static void
1577 cmpl_free_state (CompletionState* cmpl_state)
1578 {
1579   cmpl_free_dir_list (cmpl_state->directory_storage);
1580   cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
1581
1582   if (cmpl_state->user_dir_name_buffer)
1583     g_free (cmpl_state->user_dir_name_buffer);
1584   if (cmpl_state->user_home_dir)
1585     g_free (cmpl_state->user_home_dir);
1586   if (cmpl_state->user_directories)
1587     g_free (cmpl_state->user_directories);
1588   if (cmpl_state->the_completion.text)
1589     g_free (cmpl_state->the_completion.text);
1590   if (cmpl_state->updated_text)
1591     g_free (cmpl_state->updated_text);
1592
1593   g_free (cmpl_state);
1594 }
1595
1596 static void
1597 free_dir(CompletionDir* dir)
1598 {
1599   g_free(dir->fullname);
1600   g_free(dir);
1601 }
1602
1603 static void
1604 free_dir_sent(CompletionDirSent* sent)
1605 {
1606   g_free(sent->name_buffer);
1607   g_free(sent->entries);
1608   g_free(sent);
1609 }
1610
1611 static void
1612 prune_memory_usage(CompletionState *cmpl_state)
1613 {
1614   GList* cdsl = cmpl_state->directory_sent_storage;
1615   GList* cdl = cmpl_state->directory_storage;
1616   GList* cdl0 = cdl;
1617   gint len = 0;
1618
1619   for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1620     cdsl = cdsl->next;
1621
1622   if (cdsl) {
1623     cmpl_free_dir_sent_list(cdsl->next);
1624     cdsl->next = NULL;
1625   }
1626
1627   cmpl_state->directory_storage = NULL;
1628   while (cdl) {
1629     if (cdl->data == cmpl_state->reference_dir)
1630       cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1631     else
1632       free_dir (cdl->data);
1633     cdl = cdl->next;
1634   }
1635
1636   g_list_free(cdl0);
1637 }
1638
1639 /**********************************************************************/
1640 /*                        The main entrances.                         */
1641 /**********************************************************************/
1642
1643 static PossibleCompletion*
1644 cmpl_completion_matches (gchar* text_to_complete,
1645                          gchar** remaining_text,
1646                          CompletionState* cmpl_state)
1647 {
1648   gchar* first_slash;
1649   PossibleCompletion *poss;
1650
1651   prune_memory_usage(cmpl_state);
1652
1653   g_assert(text_to_complete);
1654
1655   cmpl_state->user_completion_index = -1;
1656   cmpl_state->last_completion_text = text_to_complete;
1657   cmpl_state->the_completion.text[0] = 0;
1658   cmpl_state->last_valid_char = 0;
1659   cmpl_state->updated_text_len = -1;
1660   cmpl_state->updated_text[0] = 0;
1661   cmpl_state->re_complete = FALSE;
1662
1663   first_slash = strchr(text_to_complete, '/');
1664
1665   if(text_to_complete[0] == '~' && !first_slash)
1666     {
1667       /* Text starts with ~ and there is no slash, show all the
1668        * home directory completions.
1669        */
1670       poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1671
1672       update_cmpl(poss, cmpl_state);
1673
1674       return poss;
1675     }
1676
1677   cmpl_state->reference_dir =
1678     open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1679
1680   if(!cmpl_state->reference_dir)
1681     return NULL;
1682
1683   cmpl_state->completion_dir =
1684     find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1685
1686   cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1687
1688   if(!cmpl_state->completion_dir)
1689     return NULL;
1690
1691   cmpl_state->completion_dir->cmpl_index = -1;
1692   cmpl_state->completion_dir->cmpl_parent = NULL;
1693   cmpl_state->completion_dir->cmpl_text = *remaining_text;
1694
1695   cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1696
1697   cmpl_state->reference_dir = cmpl_state->completion_dir;
1698
1699   poss = attempt_file_completion(cmpl_state);
1700
1701   update_cmpl(poss, cmpl_state);
1702
1703   return poss;
1704 }
1705
1706 static PossibleCompletion*
1707 cmpl_next_completion (CompletionState* cmpl_state)
1708 {
1709   PossibleCompletion* poss = NULL;
1710
1711   cmpl_state->the_completion.text[0] = 0;
1712
1713   if(cmpl_state->user_completion_index >= 0)
1714     poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1715   else
1716     poss = attempt_file_completion(cmpl_state);
1717
1718   update_cmpl(poss, cmpl_state);
1719
1720   return poss;
1721 }
1722
1723 /**********************************************************************/
1724 /*                       Directory Operations                         */
1725 /**********************************************************************/
1726
1727 /* Open the directory where completion will begin from, if possible. */
1728 static CompletionDir*
1729 open_ref_dir(gchar* text_to_complete,
1730              gchar** remaining_text,
1731              CompletionState* cmpl_state)
1732 {
1733   gchar* first_slash;
1734   CompletionDir *new_dir;
1735
1736   first_slash = strchr(text_to_complete, '/');
1737
1738   if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1739     {
1740       new_dir = open_dir("/", cmpl_state);
1741
1742       if(new_dir)
1743         *remaining_text = text_to_complete + 1;
1744     }
1745   else if (text_to_complete[0] == '~')
1746     {
1747       new_dir = open_user_dir(text_to_complete, cmpl_state);
1748
1749       if(new_dir)
1750         {
1751           if(first_slash)
1752             *remaining_text = first_slash + 1;
1753           else
1754             *remaining_text = text_to_complete + strlen(text_to_complete);
1755         }
1756       else
1757         {
1758           return NULL;
1759         }
1760     }
1761   else
1762     {
1763       *remaining_text = text_to_complete;
1764
1765       new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1766     }
1767
1768   if(new_dir)
1769     {
1770       new_dir->cmpl_index = -1;
1771       new_dir->cmpl_parent = NULL;
1772     }
1773
1774   return new_dir;
1775 }
1776
1777 /* open a directory by user name */
1778 static CompletionDir*
1779 open_user_dir(gchar* text_to_complete,
1780               CompletionState *cmpl_state)
1781 {
1782   gchar *first_slash;
1783   gint cmp_len;
1784
1785   g_assert(text_to_complete && text_to_complete[0] == '~');
1786
1787   first_slash = strchr(text_to_complete, '/');
1788
1789   if (first_slash)
1790     cmp_len = first_slash - text_to_complete - 1;
1791   else
1792     cmp_len = strlen(text_to_complete + 1);
1793
1794   if(!cmp_len)
1795     {
1796       /* ~/ */
1797       if (!cmpl_state->user_home_dir &&
1798           !get_pwdb(cmpl_state))
1799         return NULL;
1800       return open_dir(cmpl_state->user_home_dir, cmpl_state);
1801     }
1802   else
1803     {
1804       /* ~user/ */
1805       char* copy = g_new(char, cmp_len + 1);
1806       struct passwd *pwd;
1807       strncpy(copy, text_to_complete + 1, cmp_len);
1808       copy[cmp_len] = 0;
1809       pwd = getpwnam(copy);
1810       g_free(copy);
1811       if (!pwd)
1812         {
1813           cmpl_errno = errno;
1814           return NULL;
1815         }
1816
1817       return open_dir(pwd->pw_dir, cmpl_state);
1818     }
1819 }
1820
1821 /* open a directory relative the the current relative directory */
1822 static CompletionDir*
1823 open_relative_dir(gchar* dir_name,
1824                   CompletionDir* dir,
1825                   CompletionState *cmpl_state)
1826 {
1827   gchar path_buf[2*MAXPATHLEN];
1828
1829   if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1830     {
1831       cmpl_errno = CMPL_ERRNO_TOO_LONG;
1832       return NULL;
1833     }
1834
1835   strcpy(path_buf, dir->fullname);
1836
1837   if(dir->fullname_len > 1)
1838     {
1839       path_buf[dir->fullname_len] = '/';
1840       strcpy(path_buf + dir->fullname_len + 1, dir_name);
1841     }
1842   else
1843     {
1844       strcpy(path_buf + dir->fullname_len, dir_name);
1845     }
1846
1847   return open_dir(path_buf, cmpl_state);
1848 }
1849
1850 /* after the cache lookup fails, really open a new directory */
1851 static CompletionDirSent*
1852 open_new_dir(gchar* dir_name, struct stat* sbuf)
1853 {
1854   CompletionDirSent* sent;
1855   DIR* directory;
1856   gchar *buffer_ptr;
1857   struct dirent *dirent_ptr;
1858   gint buffer_size = 0;
1859   gint entry_count = 0;
1860   gint i;
1861   struct stat ent_sbuf;
1862   char path_buf[MAXPATHLEN*2];
1863   gint path_buf_len;
1864
1865   sent = g_new(CompletionDirSent, 1);
1866   sent->mtime = sbuf->st_mtime;
1867   sent->inode = sbuf->st_ino;
1868   sent->device = sbuf->st_dev;
1869
1870   path_buf_len = strlen(dir_name);
1871
1872   if (path_buf_len > MAXPATHLEN)
1873     {
1874       cmpl_errno = CMPL_ERRNO_TOO_LONG;
1875       return NULL;
1876     }
1877
1878   strcpy(path_buf, dir_name);
1879
1880   directory = opendir(dir_name);
1881
1882   if(!directory)
1883     {
1884       cmpl_errno = errno;
1885       return NULL;
1886     }
1887
1888   while((dirent_ptr = readdir(directory)) != NULL)
1889     {
1890       int entry_len = strlen(dirent_ptr->d_name);
1891       buffer_size += entry_len + 1;
1892       entry_count += 1;
1893
1894       if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1895         {
1896           cmpl_errno = CMPL_ERRNO_TOO_LONG;
1897           closedir(directory);
1898           return NULL;
1899         }
1900     }
1901
1902   sent->name_buffer = g_new(gchar, buffer_size);
1903   sent->entries = g_new(CompletionDirEntry, entry_count);
1904   sent->entry_count = entry_count;
1905
1906   buffer_ptr = sent->name_buffer;
1907
1908   rewinddir(directory);
1909
1910   for(i = 0; i < entry_count; i += 1)
1911     {
1912       dirent_ptr = readdir(directory);
1913
1914       if(!dirent_ptr)
1915         {
1916           cmpl_errno = errno;
1917           closedir(directory);
1918           return NULL;
1919         }
1920
1921       strcpy(buffer_ptr, dirent_ptr->d_name);
1922       sent->entries[i].entry_name = buffer_ptr;
1923       buffer_ptr += strlen(dirent_ptr->d_name);
1924       *buffer_ptr = 0;
1925       buffer_ptr += 1;
1926
1927       path_buf[path_buf_len] = '/';
1928       strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1929
1930       if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1931         sent->entries[i].is_dir = 1;
1932       else
1933         /* stat may fail, and we don't mind, since it could be a
1934          * dangling symlink. */
1935         sent->entries[i].is_dir = 0;
1936     }
1937
1938   qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1939
1940   closedir(directory);
1941
1942   return sent;
1943 }
1944
1945 /* open a directory by absolute pathname */
1946 static CompletionDir*
1947 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1948 {
1949   struct stat sbuf;
1950   CompletionDirSent *sent;
1951   GList* cdsl;
1952
1953   if(stat(dir_name, &sbuf) < 0)
1954     {
1955       cmpl_errno = errno;
1956       return NULL;
1957     }
1958
1959   cdsl = cmpl_state->directory_sent_storage;
1960
1961   while (cdsl)
1962     {
1963       sent = cdsl->data;
1964
1965       if(sent->inode == sbuf.st_ino &&
1966          sent->mtime == sbuf.st_mtime &&
1967          sent->device == sbuf.st_dev)
1968         return attach_dir(sent, dir_name, cmpl_state);
1969
1970       cdsl = cdsl->next;
1971     }
1972
1973   sent = open_new_dir(dir_name, &sbuf);
1974
1975   if (sent) {
1976     cmpl_state->directory_sent_storage =
1977       g_list_prepend(cmpl_state->directory_sent_storage, sent);
1978
1979     return attach_dir(sent, dir_name, cmpl_state);
1980   }
1981
1982   return NULL;
1983 }
1984
1985 static CompletionDir*
1986 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1987 {
1988   CompletionDir* new_dir;
1989
1990   new_dir = g_new(CompletionDir, 1);
1991
1992   cmpl_state->directory_storage =
1993     g_list_prepend(cmpl_state->directory_storage, new_dir);
1994
1995   new_dir->sent = sent;
1996   new_dir->fullname = g_strdup(dir_name);
1997   new_dir->fullname_len = strlen(dir_name);
1998
1999   return new_dir;
2000 }
2001
2002 static gint
2003 correct_dir_fullname(CompletionDir* cmpl_dir)
2004 {
2005   gint length = strlen(cmpl_dir->fullname);
2006   struct stat sbuf;
2007
2008   if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
2009     {
2010       if (length == 2) 
2011         {
2012           strcpy(cmpl_dir->fullname, "/");
2013           cmpl_dir->fullname_len = 1;
2014           return TRUE;
2015         } else {
2016           cmpl_dir->fullname[length - 2] = 0;
2017         }
2018     }
2019   else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
2020     cmpl_dir->fullname[length - 2] = 0;
2021   else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
2022     {
2023       if(length == 3)
2024         {
2025           strcpy(cmpl_dir->fullname, "/");
2026           cmpl_dir->fullname_len = 1;
2027           return TRUE;
2028         }
2029
2030       if(stat(cmpl_dir->fullname, &sbuf) < 0)
2031         {
2032           cmpl_errno = errno;
2033           return FALSE;
2034         }
2035
2036       cmpl_dir->fullname[length - 2] = 0;
2037
2038       if(!correct_parent(cmpl_dir, &sbuf))
2039         return FALSE;
2040     }
2041   else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
2042     {
2043       if(length == 4)
2044         {
2045           strcpy(cmpl_dir->fullname, "/");
2046           cmpl_dir->fullname_len = 1;
2047           return TRUE;
2048         }
2049
2050       if(stat(cmpl_dir->fullname, &sbuf) < 0)
2051         {
2052           cmpl_errno = errno;
2053           return FALSE;
2054         }
2055
2056       cmpl_dir->fullname[length - 3] = 0;
2057
2058       if(!correct_parent(cmpl_dir, &sbuf))
2059         return FALSE;
2060     }
2061
2062   cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
2063
2064   return TRUE;
2065 }
2066
2067 static gint
2068 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
2069 {
2070   struct stat parbuf;
2071   gchar *last_slash;
2072   gchar *new_name;
2073   gchar c = 0;
2074
2075   last_slash = strrchr(cmpl_dir->fullname, '/');
2076
2077   g_assert(last_slash);
2078
2079   if(last_slash != cmpl_dir->fullname)
2080     { /* last_slash[0] = 0; */ }
2081   else
2082     {
2083       c = last_slash[1];
2084       last_slash[1] = 0;
2085     }
2086
2087   if (stat(cmpl_dir->fullname, &parbuf) < 0)
2088     {
2089       cmpl_errno = errno;
2090       return FALSE;
2091     }
2092
2093   if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2094     /* it wasn't a link */
2095     return TRUE;
2096
2097   if(c)
2098     last_slash[1] = c;
2099   /* else
2100     last_slash[0] = '/'; */
2101
2102   /* it was a link, have to figure it out the hard way */
2103
2104   new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2105
2106   if (!new_name)
2107     return FALSE;
2108
2109   g_free(cmpl_dir->fullname);
2110
2111   cmpl_dir->fullname = new_name;
2112
2113   return TRUE;
2114 }
2115
2116 static gchar*
2117 find_parent_dir_fullname(gchar* dirname)
2118 {
2119   gchar buffer[MAXPATHLEN];
2120   gchar buffer2[MAXPATHLEN];
2121
2122 #if defined(sun) && !defined(__SVR4)
2123   if(!getwd(buffer))
2124 #else
2125   if(!getcwd(buffer, MAXPATHLEN))
2126 #endif    
2127     {
2128       cmpl_errno = errno;
2129       return NULL;
2130     }
2131
2132   if(chdir(dirname) != 0 || chdir("..") != 0)
2133     {
2134       cmpl_errno = errno;
2135       return NULL;
2136     }
2137
2138 #if defined(sun) && !defined(__SVR4)
2139   if(!getwd(buffer2))
2140 #else
2141   if(!getcwd(buffer2, MAXPATHLEN))
2142 #endif
2143     {
2144       chdir(buffer);
2145       cmpl_errno = errno;
2146
2147       return NULL;
2148     }
2149
2150   if(chdir(buffer) != 0)
2151     {
2152       cmpl_errno = errno;
2153       return NULL;
2154     }
2155
2156   return g_strdup(buffer2);
2157 }
2158
2159 /**********************************************************************/
2160 /*                        Completion Operations                       */
2161 /**********************************************************************/
2162
2163 static PossibleCompletion*
2164 attempt_homedir_completion(gchar* text_to_complete,
2165                            CompletionState *cmpl_state)
2166 {
2167   gint index, length;
2168
2169   if (!cmpl_state->user_dir_name_buffer &&
2170       !get_pwdb(cmpl_state))
2171     return NULL;
2172   length = strlen(text_to_complete) - 1;
2173
2174   cmpl_state->user_completion_index += 1;
2175
2176   while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2177     {
2178       index = first_diff_index(text_to_complete + 1,
2179                                cmpl_state->user_directories
2180                                [cmpl_state->user_completion_index].login);
2181
2182       switch(index)
2183         {
2184         case PATTERN_MATCH:
2185           break;
2186         default:
2187           if(cmpl_state->last_valid_char < (index + 1))
2188             cmpl_state->last_valid_char = index + 1;
2189           cmpl_state->user_completion_index += 1;
2190           continue;
2191         }
2192
2193       cmpl_state->the_completion.is_a_completion = 1;
2194       cmpl_state->the_completion.is_directory = 1;
2195
2196       append_completion_text("~", cmpl_state);
2197
2198       append_completion_text(cmpl_state->
2199                               user_directories[cmpl_state->user_completion_index].login,
2200                              cmpl_state);
2201
2202       return append_completion_text("/", cmpl_state);
2203     }
2204
2205   if(text_to_complete[1] ||
2206      cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2207     {
2208       cmpl_state->user_completion_index = -1;
2209       return NULL;
2210     }
2211   else
2212     {
2213       cmpl_state->user_completion_index += 1;
2214       cmpl_state->the_completion.is_a_completion = 1;
2215       cmpl_state->the_completion.is_directory = 1;
2216
2217       return append_completion_text("~/", cmpl_state);
2218     }
2219 }
2220
2221 /* returns the index (>= 0) of the first differing character,
2222  * PATTERN_MATCH if the completion matches */
2223 static gint
2224 first_diff_index(gchar* pat, gchar* text)
2225 {
2226   gint diff = 0;
2227
2228   while(*pat && *text && *text == *pat)
2229     {
2230       pat += 1;
2231       text += 1;
2232       diff += 1;
2233     }
2234
2235   if(*pat)
2236     return diff;
2237
2238   return PATTERN_MATCH;
2239 }
2240
2241 static PossibleCompletion*
2242 append_completion_text(gchar* text, CompletionState* cmpl_state)
2243 {
2244   gint len, i = 1;
2245
2246   if(!cmpl_state->the_completion.text)
2247     return NULL;
2248
2249   len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2250
2251   if(cmpl_state->the_completion.text_alloc > len)
2252     {
2253       strcat(cmpl_state->the_completion.text, text);
2254       return &cmpl_state->the_completion;
2255     }
2256
2257   while(i < len) { i <<= 1; }
2258
2259   cmpl_state->the_completion.text_alloc = i;
2260
2261   cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2262
2263   if(!cmpl_state->the_completion.text)
2264     return NULL;
2265   else
2266     {
2267       strcat(cmpl_state->the_completion.text, text);
2268       return &cmpl_state->the_completion;
2269     }
2270 }
2271
2272 static CompletionDir*
2273 find_completion_dir(gchar* text_to_complete,
2274                     gchar** remaining_text,
2275                     CompletionState* cmpl_state)
2276 {
2277   gchar* first_slash = strchr(text_to_complete, '/');
2278   CompletionDir* dir = cmpl_state->reference_dir;
2279   *remaining_text = text_to_complete;
2280
2281   while(first_slash)
2282     {
2283       gint len = first_slash - *remaining_text;
2284       gint found = 0;
2285       gint found_index = -1;
2286       gint i;
2287       gchar* pat_buf = g_new (gchar, len + 1);
2288
2289       strncpy(pat_buf, *remaining_text, len);
2290       pat_buf[len] = 0;
2291
2292       for(i = 0; i < dir->sent->entry_count; i += 1)
2293         {
2294           if(dir->sent->entries[i].is_dir &&
2295              fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2296                      FNMATCH_FLAGS)!= FNM_NOMATCH)
2297             {
2298               if(found)
2299                 {
2300                   g_free (pat_buf);
2301                   return dir;
2302                 }
2303               else
2304                 {
2305                   found = 1;
2306                   found_index = i;
2307                 }
2308             }
2309         }
2310
2311       if(found)
2312         {
2313           CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
2314                                                   dir, cmpl_state);
2315
2316           if(!next)
2317             {
2318               g_free (pat_buf);
2319               return NULL;
2320             }
2321
2322           next->cmpl_parent = dir;
2323
2324           dir = next;
2325
2326           if(!correct_dir_fullname(dir))
2327             {
2328               g_free(pat_buf);
2329               return NULL;
2330             }
2331
2332           *remaining_text = first_slash + 1;
2333           first_slash = strchr(*remaining_text, '/');
2334         }
2335       else
2336         {
2337           g_free (pat_buf);
2338           return NULL;
2339         }
2340
2341       g_free (pat_buf);
2342     }
2343
2344   return dir;
2345 }
2346
2347 static void
2348 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
2349 {
2350   gint cmpl_len;
2351
2352   if(!poss || !cmpl_is_a_completion(poss))
2353     return;
2354
2355   cmpl_len = strlen(cmpl_this_completion(poss));
2356
2357   if(cmpl_state->updated_text_alloc < cmpl_len + 1)
2358     {
2359       cmpl_state->updated_text =
2360         (gchar*)g_realloc(cmpl_state->updated_text,
2361                           cmpl_state->updated_text_alloc);
2362       cmpl_state->updated_text_alloc = 2*cmpl_len;
2363     }
2364
2365   if(cmpl_state->updated_text_len < 0)
2366     {
2367       strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2368       cmpl_state->updated_text_len = cmpl_len;
2369       cmpl_state->re_complete = cmpl_is_directory(poss);
2370     }
2371   else if(cmpl_state->updated_text_len == 0)
2372     {
2373       cmpl_state->re_complete = FALSE;
2374     }
2375   else
2376     {
2377       gint first_diff =
2378         first_diff_index(cmpl_state->updated_text,
2379                          cmpl_this_completion(poss));
2380
2381       cmpl_state->re_complete = FALSE;
2382
2383       if(first_diff == PATTERN_MATCH)
2384         return;
2385
2386       if(first_diff > cmpl_state->updated_text_len)
2387         strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2388
2389       cmpl_state->updated_text_len = first_diff;
2390       cmpl_state->updated_text[first_diff] = 0;
2391     }
2392 }
2393
2394 static PossibleCompletion*
2395 attempt_file_completion(CompletionState *cmpl_state)
2396 {
2397   gchar *pat_buf, *first_slash;
2398   CompletionDir *dir = cmpl_state->active_completion_dir;
2399
2400   dir->cmpl_index += 1;
2401
2402   if(dir->cmpl_index == dir->sent->entry_count)
2403     {
2404       if(dir->cmpl_parent == NULL)
2405         {
2406           cmpl_state->active_completion_dir = NULL;
2407
2408           return NULL;
2409         }
2410       else
2411         {
2412           cmpl_state->active_completion_dir = dir->cmpl_parent;
2413
2414           return attempt_file_completion(cmpl_state);
2415         }
2416     }
2417
2418   g_assert(dir->cmpl_text);
2419
2420   first_slash = strchr(dir->cmpl_text, '/');
2421
2422   if(first_slash)
2423     {
2424       gint len = first_slash - dir->cmpl_text;
2425
2426       pat_buf = g_new (gchar, len + 1);
2427       strncpy(pat_buf, dir->cmpl_text, len);
2428       pat_buf[len] = 0;
2429     }
2430   else
2431     {
2432       gint len = strlen(dir->cmpl_text);
2433
2434       pat_buf = g_new (gchar, len + 2);
2435       strcpy(pat_buf, dir->cmpl_text);
2436       strcpy(pat_buf + len, "*");
2437     }
2438
2439   if(first_slash)
2440     {
2441       if(dir->sent->entries[dir->cmpl_index].is_dir)
2442         {
2443           if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2444                      FNMATCH_FLAGS) != FNM_NOMATCH)
2445             {
2446               CompletionDir* new_dir;
2447
2448               new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
2449                                           dir, cmpl_state);
2450
2451               if(!new_dir)
2452                 {
2453                   g_free (pat_buf);
2454                   return NULL;
2455                 }
2456
2457               new_dir->cmpl_parent = dir;
2458
2459               new_dir->cmpl_index = -1;
2460               new_dir->cmpl_text = first_slash + 1;
2461
2462               cmpl_state->active_completion_dir = new_dir;
2463
2464               g_free (pat_buf);
2465               return attempt_file_completion(cmpl_state);
2466             }
2467           else
2468             {
2469               g_free (pat_buf);
2470               return attempt_file_completion(cmpl_state);
2471             }
2472         }
2473       else
2474         {
2475           g_free (pat_buf);
2476           return attempt_file_completion(cmpl_state);
2477         }
2478     }
2479   else
2480     {
2481       if(dir->cmpl_parent != NULL)
2482         {
2483           append_completion_text(dir->fullname +
2484                                  strlen(cmpl_state->completion_dir->fullname) + 1,
2485                                  cmpl_state);
2486           append_completion_text("/", cmpl_state);
2487         }
2488
2489       append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2490
2491       cmpl_state->the_completion.is_a_completion =
2492         (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2493                  FNMATCH_FLAGS) != FNM_NOMATCH);
2494
2495       cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2496       if(dir->sent->entries[dir->cmpl_index].is_dir)
2497         append_completion_text("/", cmpl_state);
2498
2499       g_free (pat_buf);
2500       return &cmpl_state->the_completion;
2501     }
2502 }
2503
2504
2505 static gint
2506 get_pwdb(CompletionState* cmpl_state)
2507 {
2508   struct passwd *pwd_ptr;
2509   gchar* buf_ptr;
2510   gint len = 0, i, count = 0;
2511
2512   if(cmpl_state->user_dir_name_buffer)
2513     return TRUE;
2514   setpwent ();
2515
2516   while ((pwd_ptr = getpwent()) != NULL)
2517     {
2518       len += strlen(pwd_ptr->pw_name);
2519       len += strlen(pwd_ptr->pw_dir);
2520       len += 2;
2521       count += 1;
2522     }
2523
2524   if (!cmpl_state->user_home_dir)
2525     {
2526       /* the loser doesn't have $HOME set */
2527       setpwent ();
2528
2529       pwd_ptr = getpwuid(getuid());
2530       if(!pwd_ptr)
2531         {
2532           cmpl_errno = errno;
2533           goto error;
2534         }
2535       /* Allocate this separately, since it might be filled in elsewhere */
2536       cmpl_state->user_home_dir = g_strdup (pwd_ptr->pw_dir);
2537     }
2538
2539   setpwent ();
2540
2541   cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2542   cmpl_state->user_directories = g_new(CompletionUserDir, count);
2543   cmpl_state->user_directories_len = count;
2544
2545   buf_ptr = cmpl_state->user_dir_name_buffer;
2546
2547   for(i = 0; i < count; i += 1)
2548     {
2549       pwd_ptr = getpwent();
2550       if(!pwd_ptr)
2551         {
2552           cmpl_errno = errno;
2553           goto error;
2554         }
2555
2556       strcpy(buf_ptr, pwd_ptr->pw_name);
2557       cmpl_state->user_directories[i].login = buf_ptr;
2558       buf_ptr += strlen(buf_ptr);
2559       buf_ptr += 1;
2560       strcpy(buf_ptr, pwd_ptr->pw_dir);
2561       cmpl_state->user_directories[i].homedir = buf_ptr;
2562       buf_ptr += strlen(buf_ptr);
2563       buf_ptr += 1;
2564     }
2565
2566   qsort(cmpl_state->user_directories,
2567         cmpl_state->user_directories_len,
2568         sizeof(CompletionUserDir),
2569         compare_user_dir);
2570
2571   endpwent();
2572
2573   return TRUE;
2574
2575 error:
2576
2577   if(cmpl_state->user_dir_name_buffer)
2578     g_free(cmpl_state->user_dir_name_buffer);
2579   if(cmpl_state->user_directories)
2580     g_free(cmpl_state->user_directories);
2581
2582   cmpl_state->user_dir_name_buffer = NULL;
2583   cmpl_state->user_directories = NULL;
2584
2585   return FALSE;
2586 }
2587
2588 static gint
2589 compare_user_dir(const void* a, const void* b)
2590 {
2591   return strcmp((((CompletionUserDir*)a))->login,
2592                 (((CompletionUserDir*)b))->login);
2593 }
2594
2595 static gint
2596 compare_cmpl_dir(const void* a, const void* b)
2597 {
2598   return strcmp((((CompletionDirEntry*)a))->entry_name,
2599                 (((CompletionDirEntry*)b))->entry_name);
2600 }
2601
2602 static gint
2603 cmpl_state_okay(CompletionState* cmpl_state)
2604 {
2605   return  cmpl_state && cmpl_state->reference_dir;
2606 }
2607
2608 static gchar*
2609 cmpl_strerror(gint err)
2610 {
2611   if(err == CMPL_ERRNO_TOO_LONG)
2612     return "Name too long";
2613   else
2614     return g_strerror (err);
2615 }