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

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