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