source: trunk/third/gtk/gtk/gtkfilesel.c @ 14482

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