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