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