1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
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.
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.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 #include <sys/types.h>
21 #include <sys/param.h>
30 #include "gdk/gdkkeysyms.h"
31 #include "gtkbutton.h"
33 #include "gtkfilesel.h"
38 #include "gtklistitem.h"
40 #include "gtkscrolledwindow.h"
41 #include "gtksignal.h"
44 #include "gtkmenuitem.h"
45 #include "gtkoptionmenu.h"
47 #include "gtkdialog.h"
49 #define DIR_LIST_WIDTH 180
50 #define DIR_LIST_HEIGHT 180
51 #define FILE_LIST_WIDTH 180
52 #define FILE_LIST_HEIGHT 180
54 /* I've put this here so it doesn't get confused with the
55 * file completion interface */
56 typedef struct _HistoryCallbackArg HistoryCallbackArg;
58 struct _HistoryCallbackArg
65 typedef struct _CompletionState CompletionState;
66 typedef struct _CompletionDir CompletionDir;
67 typedef struct _CompletionDirSent CompletionDirSent;
68 typedef struct _CompletionDirEntry CompletionDirEntry;
69 typedef struct _CompletionUserDir CompletionUserDir;
70 typedef struct _PossibleCompletion PossibleCompletion;
72 /* Non-external file completion decls and structures */
74 /* A contant telling PRCS how many directories to cache. Its actually
75 * kept in a list, so the geometry isn't important. */
76 #define CMPL_DIRECTORY_CACHE_SIZE 10
78 /* A constant used to determine whether a substring was an exact
79 * match by first_diff_index()
81 #define PATTERN_MATCH -1
82 /* The arguments used by all fnmatch() calls below
84 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
86 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
88 /* This structure contains all the useful information about a directory
89 * for the purposes of filename completion. These structures are cached
90 * in the CompletionState struct. CompletionDir's are reference counted.
92 struct _CompletionDirSent
99 gchar *name_buffer; /* memory segment containing names of all entries */
101 struct _CompletionDirEntry *entries;
104 struct _CompletionDir
106 CompletionDirSent *sent;
111 struct _CompletionDir *cmpl_parent;
116 /* This structure contains pairs of directory entry names with a flag saying
117 * whether or not they are a valid directory. NOTE: This information is used
118 * to provide the caller with information about whether to update its completions
119 * or try to open a file. Since directories are cached by the directory mtime,
120 * a symlink which points to an invalid file (which will not be a directory),
121 * will not be reevaluated if that file is created, unless the containing
122 * directory is touched. I consider this case to be worth ignoring (josh).
124 struct _CompletionDirEntry
130 struct _CompletionUserDir
136 struct _PossibleCompletion
138 /* accessible fields, all are accessed externally by functions
142 gint is_a_completion;
150 struct _CompletionState
152 gint last_valid_char;
154 gint updated_text_len;
155 gint updated_text_alloc;
158 gchar *user_dir_name_buffer;
159 gint user_directories_len;
160 gchar *user_home_dir;
162 gchar *last_completion_text;
164 gint user_completion_index; /* if >= 0, currently completing ~user */
166 struct _CompletionDir *completion_dir; /* directory completing from */
167 struct _CompletionDir *active_completion_dir;
169 struct _PossibleCompletion the_completion;
171 struct _CompletionDir *reference_dir; /* initial directory */
173 GList* directory_storage;
174 GList* directory_sent_storage;
176 struct _CompletionUserDir *user_directories;
180 /* File completion functions which would be external, were they used
181 * outside of this file.
184 static CompletionState* cmpl_init_state (void);
185 static void cmpl_free_state (CompletionState *cmpl_state);
186 static gint cmpl_state_okay (CompletionState* cmpl_state);
187 static gchar* cmpl_strerror (gint);
189 static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete,
190 gchar **remaining_text,
191 CompletionState *cmpl_state);
193 /* Returns a name for consideration, possibly a completion, this name
194 * will be invalid after the next call to cmpl_next_completion.
196 static char* cmpl_this_completion (PossibleCompletion*);
198 /* True if this completion matches the given text. Otherwise, this
199 * output can be used to have a list of non-completions.
201 static gint cmpl_is_a_completion (PossibleCompletion*);
203 /* True if the completion is a directory
205 static gint cmpl_is_directory (PossibleCompletion*);
207 /* Obtains the next completion, or NULL
209 static PossibleCompletion* cmpl_next_completion (CompletionState*);
211 /* Updating completions: the return value of cmpl_updated_text() will
212 * be text_to_complete completed as much as possible after the most
213 * recent call to cmpl_completion_matches. For the present
214 * application, this is the suggested replacement for the user's input
215 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
218 static gchar* cmpl_updated_text (CompletionState* cmpl_state);
220 /* After updating, to see if the completion was a directory, call
221 * this. If it was, you should consider re-calling completion_matches.
223 static gint cmpl_updated_dir (CompletionState* cmpl_state);
225 /* Current location: if using file completion, return the current
226 * directory, from which file completion begins. More specifically,
227 * the cwd concatenated with all exact completions up to the last
228 * directory delimiter('/').
230 static gchar* cmpl_reference_position (CompletionState* cmpl_state);
232 /* backing up: if cmpl_completion_matches returns NULL, you may query
233 * the index of the last completable character into cmpl_updated_text.
235 static gint cmpl_last_valid_char (CompletionState* cmpl_state);
237 /* When the user selects a non-directory, call cmpl_completion_fullname
238 * to get the full name of the selected file.
240 static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
243 /* Directory operations. */
244 static CompletionDir* open_ref_dir (gchar* text_to_complete,
245 gchar** remaining_text,
246 CompletionState* cmpl_state);
247 static CompletionDir* open_dir (gchar* dir_name,
248 CompletionState* cmpl_state);
249 static CompletionDir* open_user_dir (gchar* text_to_complete,
250 CompletionState *cmpl_state);
251 static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
252 CompletionState *cmpl_state);
253 static CompletionDirSent* open_new_dir (gchar* dir_name, struct stat* sbuf);
254 static gint correct_dir_fullname (CompletionDir* cmpl_dir);
255 static gint correct_parent (CompletionDir* cmpl_dir,
257 static gchar* find_parent_dir_fullname (gchar* dirname);
258 static CompletionDir* attach_dir (CompletionDirSent* sent,
260 CompletionState *cmpl_state);
261 static void free_dir_sent (CompletionDirSent* sent);
262 static void free_dir (CompletionDir *dir);
263 static void prune_memory_usage(CompletionState *cmpl_state);
265 /* Completion operations */
266 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
267 CompletionState *cmpl_state);
268 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
269 static CompletionDir* find_completion_dir(gchar* text_to_complete,
270 gchar** remaining_text,
271 CompletionState* cmpl_state);
272 static PossibleCompletion* append_completion_text(gchar* text,
273 CompletionState* cmpl_state);
274 static gint get_pwdb(CompletionState* cmpl_state);
275 static gint first_diff_index(gchar* pat, gchar* text);
276 static gint compare_user_dir(const void* a, const void* b);
277 static gint compare_cmpl_dir(const void* a, const void* b);
278 static void update_cmpl(PossibleCompletion* poss,
279 CompletionState* cmpl_state);
281 static void gtk_file_selection_class_init (GtkFileSelectionClass *klass);
282 static void gtk_file_selection_init (GtkFileSelection *filesel);
283 static void gtk_file_selection_destroy (GtkObject *object);
284 static gint gtk_file_selection_key_press (GtkWidget *widget,
288 static void gtk_file_selection_file_button (GtkWidget *widget,
291 GdkEventButton *bevent,
294 static void gtk_file_selection_dir_button (GtkWidget *widget,
297 GdkEventButton *bevent,
300 static void gtk_file_selection_populate (GtkFileSelection *fs,
303 static void gtk_file_selection_abort (GtkFileSelection *fs);
305 static void gtk_file_selection_update_history_menu (GtkFileSelection *fs,
308 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
309 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
310 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
314 static GtkWindowClass *parent_class = NULL;
316 /* Saves errno when something cmpl does fails. */
317 static gint cmpl_errno;
320 gtk_file_selection_get_type ()
322 static guint file_selection_type = 0;
324 if (!file_selection_type)
326 GtkTypeInfo filesel_info =
329 sizeof (GtkFileSelection),
330 sizeof (GtkFileSelectionClass),
331 (GtkClassInitFunc) gtk_file_selection_class_init,
332 (GtkObjectInitFunc) gtk_file_selection_init,
333 (GtkArgSetFunc) NULL,
334 (GtkArgGetFunc) NULL,
337 file_selection_type = gtk_type_unique (gtk_window_get_type (), &filesel_info);
340 return file_selection_type;
344 gtk_file_selection_class_init (GtkFileSelectionClass *class)
346 GtkObjectClass *object_class;
348 object_class = (GtkObjectClass*) class;
350 parent_class = gtk_type_class (gtk_window_get_type ());
352 object_class->destroy = gtk_file_selection_destroy;
356 gtk_file_selection_init (GtkFileSelection *filesel)
358 GtkWidget *entry_vbox;
360 GtkWidget *list_hbox;
361 GtkWidget *confirm_area;
362 GtkWidget *pulldown_hbox;
363 char *dir_title [] = { "Directories", };
364 char *file_title [] = { "Files", };
366 filesel->cmpl_state = cmpl_init_state ();
368 /* The dialog-sized vertical box */
369 filesel->main_vbox = gtk_vbox_new (FALSE, 10);
370 gtk_container_border_width (GTK_CONTAINER (filesel), 10);
371 gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
372 gtk_widget_show (filesel->main_vbox);
374 /* The horizontal box containing create, rename etc. buttons */
375 filesel->button_area = gtk_hbutton_box_new ();
376 gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START);
377 gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0);
378 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area,
380 gtk_widget_show (filesel->button_area);
382 gtk_file_selection_show_fileop_buttons(filesel);
384 /* hbox for pulldown menu */
385 pulldown_hbox = gtk_hbox_new (TRUE, 5);
386 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
387 gtk_widget_show (pulldown_hbox);
390 filesel->history_pulldown = gtk_option_menu_new ();
391 gtk_widget_show (filesel->history_pulldown);
392 gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown,
395 /* The horizontal box containing the directory and file listboxes */
396 list_hbox = gtk_hbox_new (FALSE, 5);
397 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
398 gtk_widget_show (list_hbox);
400 /* The directories clist */
401 filesel->dir_list = gtk_clist_new_with_titles (1, dir_title);
402 gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
403 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
404 (GtkSignalFunc) gtk_file_selection_dir_button,
406 gtk_clist_set_policy (GTK_CLIST (filesel->dir_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
407 gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
408 gtk_container_border_width (GTK_CONTAINER (filesel->dir_list), 5);
409 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->dir_list, TRUE, TRUE, 0);
410 gtk_widget_show (filesel->dir_list);
412 /* The files clist */
413 filesel->file_list = gtk_clist_new_with_titles (1, file_title);
414 gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
415 gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
416 (GtkSignalFunc) gtk_file_selection_file_button,
418 gtk_clist_set_policy (GTK_CLIST (filesel->file_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
419 gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
420 gtk_container_border_width (GTK_CONTAINER (filesel->file_list), 5);
421 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->file_list, TRUE, TRUE, 0);
422 gtk_widget_show (filesel->file_list);
424 /* action area for packing buttons into. */
425 filesel->action_area = gtk_hbox_new (TRUE, 0);
426 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area,
428 gtk_widget_show (filesel->action_area);
430 /* The OK/Cancel button area */
431 confirm_area = gtk_hbutton_box_new ();
432 gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END);
433 gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5);
434 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0);
435 gtk_widget_show (confirm_area);
438 filesel->ok_button = gtk_button_new_with_label ("OK");
439 GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
440 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0);
441 gtk_widget_grab_default (filesel->ok_button);
442 gtk_widget_show (filesel->ok_button);
444 /* The Cancel button */
445 filesel->cancel_button = gtk_button_new_with_label ("Cancel");
446 GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
447 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0);
448 gtk_widget_show (filesel->cancel_button);
450 /* The selection entry widget */
451 entry_vbox = gtk_vbox_new (FALSE, 2);
452 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
453 gtk_widget_show (entry_vbox);
455 filesel->selection_text = label = gtk_label_new ("");
456 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
457 gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
458 gtk_widget_show (label);
460 filesel->selection_entry = gtk_entry_new ();
461 gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
462 (GtkSignalFunc) gtk_file_selection_key_press, filesel);
463 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
464 (GtkSignalFunc) gtk_widget_grab_default,
465 GTK_OBJECT (filesel->ok_button));
466 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
467 (GtkSignalFunc) gtk_button_clicked,
468 GTK_OBJECT (filesel->ok_button));
469 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
470 gtk_widget_show (filesel->selection_entry);
472 if (!cmpl_state_okay (filesel->cmpl_state))
476 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
478 gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
482 gtk_file_selection_populate (filesel, "", FALSE);
485 gtk_widget_grab_focus (filesel->selection_entry);
489 gtk_file_selection_new (const gchar *title)
491 GtkFileSelection *filesel;
493 filesel = gtk_type_new (gtk_file_selection_get_type ());
494 gtk_window_set_title (GTK_WINDOW (filesel), title);
496 return GTK_WIDGET (filesel);
500 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
502 g_return_if_fail (filesel != NULL);
503 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
505 /* delete, create directory, and rename */
506 if (!filesel->fileop_c_dir)
508 filesel->fileop_c_dir = gtk_button_new_with_label ("Create Dir");
509 gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
510 (GtkSignalFunc) gtk_file_selection_create_dir,
512 gtk_box_pack_start (GTK_BOX (filesel->button_area),
513 filesel->fileop_c_dir, TRUE, TRUE, 0);
514 gtk_widget_show (filesel->fileop_c_dir);
517 if (!filesel->fileop_del_file)
519 filesel->fileop_del_file = gtk_button_new_with_label ("Delete File");
520 gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
521 (GtkSignalFunc) gtk_file_selection_delete_file,
523 gtk_box_pack_start (GTK_BOX (filesel->button_area),
524 filesel->fileop_del_file, TRUE, TRUE, 0);
525 gtk_widget_show (filesel->fileop_del_file);
528 if (!filesel->fileop_ren_file)
530 filesel->fileop_ren_file = gtk_button_new_with_label ("Rename File");
531 gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
532 (GtkSignalFunc) gtk_file_selection_rename_file,
534 gtk_box_pack_start (GTK_BOX (filesel->button_area),
535 filesel->fileop_ren_file, TRUE, TRUE, 0);
536 gtk_widget_show (filesel->fileop_ren_file);
539 gtk_widget_queue_resize(GTK_WIDGET(filesel));
543 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
545 g_return_if_fail (filesel != NULL);
546 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
548 if (filesel->fileop_ren_file)
550 gtk_widget_destroy (filesel->fileop_ren_file);
551 filesel->fileop_ren_file = NULL;
554 if (filesel->fileop_del_file)
556 gtk_widget_destroy (filesel->fileop_del_file);
557 filesel->fileop_del_file = NULL;
560 if (filesel->fileop_c_dir)
562 gtk_widget_destroy (filesel->fileop_c_dir);
563 filesel->fileop_c_dir = NULL;
570 gtk_file_selection_set_filename (GtkFileSelection *filesel,
571 const gchar *filename)
573 char buf[MAXPATHLEN];
574 const char *name, *last_slash;
576 g_return_if_fail (filesel != NULL);
577 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
578 g_return_if_fail (filename != NULL);
580 last_slash = strrchr (filename, '/');
589 gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
591 strncpy (buf, filename, len);
594 name = last_slash + 1;
597 gtk_file_selection_populate (filesel, buf, FALSE);
599 if (filesel->selection_entry)
600 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
604 gtk_file_selection_get_filename (GtkFileSelection *filesel)
606 static char nothing[2] = "";
610 g_return_val_if_fail (filesel != NULL, nothing);
611 g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
613 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
616 filename = cmpl_completion_fullname (text, filesel->cmpl_state);
624 gtk_file_selection_destroy (GtkObject *object)
626 GtkFileSelection *filesel;
628 HistoryCallbackArg *callback_arg;
630 g_return_if_fail (object != NULL);
631 g_return_if_fail (GTK_IS_FILE_SELECTION (object));
633 filesel = GTK_FILE_SELECTION (object);
635 if (filesel->fileop_dialog)
636 gtk_widget_destroy (filesel->fileop_dialog);
638 if (filesel->history_list)
640 list = filesel->history_list;
643 callback_arg = list->data;
644 g_free (callback_arg->directory);
645 g_free (callback_arg);
648 g_list_free (filesel->history_list);
649 filesel->history_list = NULL;
652 cmpl_free_state (filesel->cmpl_state);
653 filesel->cmpl_state = NULL;
655 if (GTK_OBJECT_CLASS (parent_class)->destroy)
656 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
659 /* Begin file operations callbacks */
662 gtk_file_selection_fileop_error (gchar *error_message)
669 g_return_if_fail (error_message != NULL);
672 dialog = gtk_dialog_new ();
674 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
675 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
678 gtk_window_set_title (GTK_WINDOW (dialog), "Error");
679 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
681 vbox = gtk_vbox_new(FALSE, 0);
682 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
683 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
685 gtk_widget_show(vbox);
687 label = gtk_label_new(error_message);
688 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
689 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
690 gtk_widget_show(label);
692 /* yes, we free it */
693 g_free (error_message);
696 button = gtk_button_new_with_label ("Close");
697 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
698 (GtkSignalFunc) gtk_widget_destroy,
700 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
701 button, TRUE, TRUE, 0);
702 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
703 gtk_widget_grab_default(button);
704 gtk_widget_show (button);
706 gtk_widget_show (dialog);
710 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
712 GtkFileSelection *fs = data;
714 g_return_if_fail (fs != NULL);
715 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
717 fs->fileop_dialog = NULL;
722 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
724 GtkFileSelection *fs = data;
729 CompletionState *cmpl_state;
731 g_return_if_fail (fs != NULL);
732 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
734 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
735 cmpl_state = (CompletionState*) fs->cmpl_state;
736 path = cmpl_reference_position (cmpl_state);
738 full_path = g_strconcat (path, "/", dirname, NULL);
739 if ( (mkdir (full_path, 0755) < 0) )
741 buf = g_strconcat ("Error creating directory \"", dirname, "\": ",
742 g_strerror(errno), NULL);
743 gtk_file_selection_fileop_error (buf);
747 gtk_widget_destroy (fs->fileop_dialog);
748 gtk_file_selection_populate (fs, "", FALSE);
752 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
754 GtkFileSelection *fs = data;
760 g_return_if_fail (fs != NULL);
761 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
763 if (fs->fileop_dialog)
767 fs->fileop_dialog = dialog = gtk_dialog_new ();
768 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
769 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
771 gtk_window_set_title (GTK_WINDOW (dialog), "Create Directory");
772 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
773 gtk_widget_show (dialog);
775 vbox = gtk_vbox_new(FALSE, 0);
776 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
777 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
779 gtk_widget_show(vbox);
781 label = gtk_label_new("Directory name:");
782 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
783 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
784 gtk_widget_show(label);
786 /* The directory entry widget */
787 fs->fileop_entry = gtk_entry_new ();
788 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
790 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
791 gtk_widget_show (fs->fileop_entry);
794 button = gtk_button_new_with_label ("Create");
795 gtk_signal_connect (GTK_OBJECT (button), "clicked",
796 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
798 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
799 button, TRUE, TRUE, 0);
800 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
801 gtk_widget_show(button);
803 button = gtk_button_new_with_label ("Cancel");
804 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
805 (GtkSignalFunc) gtk_widget_destroy,
807 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
808 button, TRUE, TRUE, 0);
809 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
810 gtk_widget_grab_default(button);
811 gtk_widget_show (button);
815 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
817 GtkFileSelection *fs = data;
818 CompletionState *cmpl_state;
823 g_return_if_fail (fs != NULL);
824 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
826 cmpl_state = (CompletionState*) fs->cmpl_state;
827 path = cmpl_reference_position (cmpl_state);
829 full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
830 if ( (unlink (full_path) < 0) )
832 buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ",
833 g_strerror(errno), NULL);
834 gtk_file_selection_fileop_error (buf);
838 gtk_widget_destroy (fs->fileop_dialog);
839 gtk_file_selection_populate (fs, "", FALSE);
843 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
845 GtkFileSelection *fs = data;
853 g_return_if_fail (fs != NULL);
854 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
856 if (fs->fileop_dialog)
859 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
860 if (strlen(filename) < 1)
863 fs->fileop_file = filename;
866 fs->fileop_dialog = dialog = gtk_dialog_new ();
867 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
868 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
870 gtk_window_set_title (GTK_WINDOW (dialog), "Delete File");
871 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
873 vbox = gtk_vbox_new(FALSE, 0);
874 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
875 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
877 gtk_widget_show(vbox);
879 buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
880 label = gtk_label_new(buf);
881 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
882 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
883 gtk_widget_show(label);
887 button = gtk_button_new_with_label ("Delete");
888 gtk_signal_connect (GTK_OBJECT (button), "clicked",
889 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
891 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
892 button, TRUE, TRUE, 0);
893 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
894 gtk_widget_show(button);
896 button = gtk_button_new_with_label ("Cancel");
897 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
898 (GtkSignalFunc) gtk_widget_destroy,
900 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
901 button, TRUE, TRUE, 0);
902 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
903 gtk_widget_grab_default(button);
904 gtk_widget_show (button);
906 gtk_widget_show (dialog);
910 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
912 GtkFileSelection *fs = data;
918 CompletionState *cmpl_state;
920 g_return_if_fail (fs != NULL);
921 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
923 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
924 cmpl_state = (CompletionState*) fs->cmpl_state;
925 path = cmpl_reference_position (cmpl_state);
927 new_filename = g_strconcat (path, "/", file, NULL);
928 old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
930 if ( (rename (old_filename, new_filename)) < 0)
932 buf = g_strconcat ("Error renaming file \"", file, "\": ",
933 g_strerror(errno), NULL);
934 gtk_file_selection_fileop_error (buf);
936 g_free (new_filename);
937 g_free (old_filename);
939 gtk_widget_destroy (fs->fileop_dialog);
940 gtk_file_selection_populate (fs, "", FALSE);
944 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
946 GtkFileSelection *fs = data;
953 g_return_if_fail (fs != NULL);
954 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
956 if (fs->fileop_dialog)
959 fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
960 if (strlen(fs->fileop_file) < 1)
964 fs->fileop_dialog = dialog = gtk_dialog_new ();
965 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
966 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
968 gtk_window_set_title (GTK_WINDOW (dialog), "Rename File");
969 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
970 gtk_widget_show (dialog);
972 vbox = gtk_vbox_new(FALSE, 0);
973 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
974 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
976 gtk_widget_show(vbox);
978 buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
979 label = gtk_label_new(buf);
980 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
981 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
982 gtk_widget_show(label);
985 /* New filename entry */
986 fs->fileop_entry = gtk_entry_new ();
987 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
989 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
990 gtk_widget_show (fs->fileop_entry);
992 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
993 gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
994 0, strlen (fs->fileop_file));
997 button = gtk_button_new_with_label ("Rename");
998 gtk_signal_connect (GTK_OBJECT (button), "clicked",
999 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
1001 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1002 button, TRUE, TRUE, 0);
1003 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1004 gtk_widget_show(button);
1006 button = gtk_button_new_with_label ("Cancel");
1007 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1008 (GtkSignalFunc) gtk_widget_destroy,
1010 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1011 button, TRUE, TRUE, 0);
1012 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1013 gtk_widget_grab_default(button);
1014 gtk_widget_show (button);
1019 gtk_file_selection_key_press (GtkWidget *widget,
1023 GtkFileSelection *fs;
1026 g_return_val_if_fail (widget != NULL, FALSE);
1027 g_return_val_if_fail (event != NULL, FALSE);
1029 if (event->keyval == GDK_Tab)
1031 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1033 fs = GTK_FILE_SELECTION (user_data);
1034 text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1035 gtk_file_selection_populate (fs, text, TRUE);
1045 gtk_file_selection_history_callback (GtkWidget *widget, gpointer data)
1047 GtkFileSelection *fs = data;
1048 HistoryCallbackArg *callback_arg;
1051 g_return_if_fail (fs != NULL);
1052 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1054 list = fs->history_list;
1057 callback_arg = list->data;
1059 if (callback_arg->menu_item == widget)
1061 gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1070 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1071 gchar *current_directory)
1073 HistoryCallbackArg *callback_arg;
1074 GtkWidget *menu_item;
1080 g_return_if_fail (fs != NULL);
1081 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1082 g_return_if_fail (current_directory != NULL);
1084 list = fs->history_list;
1086 if (fs->history_menu)
1089 callback_arg = list->data;
1090 g_free (callback_arg->directory);
1091 g_free (callback_arg);
1094 g_list_free (fs->history_list);
1095 fs->history_list = NULL;
1097 gtk_widget_destroy (fs->history_menu);
1100 fs->history_menu = gtk_menu_new();
1102 current_dir = g_strdup (current_directory);
1104 dir_len = strlen (current_dir);
1106 for (i = dir_len; i >= 0; i--)
1108 /* the i == dir_len is to catch the full path for the first
1110 if ( (current_dir[i] == '/') || (i == dir_len))
1112 /* another small hack to catch the full path */
1114 current_dir[i + 1] = '\0';
1115 menu_item = gtk_menu_item_new_with_label (current_dir);
1117 callback_arg = g_new (HistoryCallbackArg, 1);
1118 callback_arg->menu_item = menu_item;
1120 /* since the autocompletion gets confused if you don't
1121 * supply a trailing '/' on a dir entry, set the full
1122 * (current) path to "" which just refreshes the filesel */
1124 callback_arg->directory = g_strdup ("");
1126 callback_arg->directory = g_strdup (current_dir);
1129 fs->history_list = g_list_append (fs->history_list, callback_arg);
1131 gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1132 (GtkSignalFunc) gtk_file_selection_history_callback,
1134 gtk_menu_append (GTK_MENU (fs->history_menu), menu_item);
1135 gtk_widget_show (menu_item);
1139 gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown),
1141 g_free (current_dir);
1145 gtk_file_selection_file_button (GtkWidget *widget,
1148 GdkEventButton *bevent,
1151 GtkFileSelection *fs = NULL;
1152 gchar *filename, *temp = NULL;
1154 g_return_if_fail (GTK_IS_CLIST (widget));
1157 g_return_if_fail (fs != NULL);
1158 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1160 gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1161 filename = g_strdup (temp);
1163 if (bevent && filename)
1165 switch (bevent->type)
1167 case GDK_BUTTON_PRESS:
1168 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1171 case GDK_2BUTTON_PRESS:
1172 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1185 gtk_file_selection_dir_button (GtkWidget *widget,
1188 GdkEventButton *bevent,
1191 GtkFileSelection *fs = NULL;
1192 gchar *filename, *temp = NULL;
1194 g_return_if_fail (GTK_IS_CLIST (widget));
1196 fs = GTK_FILE_SELECTION (user_data);
1197 g_return_if_fail (fs != NULL);
1198 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1200 gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1201 filename = g_strdup (temp);
1203 if (bevent && filename)
1205 switch (bevent->type)
1207 case GDK_BUTTON_PRESS:
1208 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1211 case GDK_2BUTTON_PRESS:
1212 gtk_file_selection_populate (fs, filename, FALSE);
1225 gtk_file_selection_populate (GtkFileSelection *fs,
1229 CompletionState *cmpl_state;
1230 PossibleCompletion* poss;
1233 gchar* rem_path = rel_path;
1236 gint did_recurse = FALSE;
1237 gint possible_count = 0;
1238 gint selection_index = -1;
1239 gint file_list_width;
1240 gint dir_list_width;
1242 g_return_if_fail (fs != NULL);
1243 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1245 cmpl_state = (CompletionState*) fs->cmpl_state;
1246 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1248 if (!cmpl_state_okay (cmpl_state))
1250 /* Something went wrong. */
1251 gtk_file_selection_abort (fs);
1255 g_assert (cmpl_state->reference_dir);
1257 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1258 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1259 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1260 gtk_clist_clear (GTK_CLIST (fs->file_list));
1262 /* Set the dir_list to include ./ and ../ */
1265 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1268 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1270 /*reset the max widths of the lists*/
1271 dir_list_width = gdk_string_width(fs->dir_list->style->font,"../");
1272 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width);
1273 file_list_width = 1;
1274 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width);
1278 if (cmpl_is_a_completion (poss))
1280 possible_count += 1;
1282 filename = cmpl_this_completion (poss);
1286 if (cmpl_is_directory (poss))
1288 if (strcmp (filename, "./") != 0 &&
1289 strcmp (filename, "../") != 0)
1291 int width = gdk_string_width(fs->dir_list->style->font,
1293 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1294 if(width > dir_list_width)
1296 dir_list_width = width;
1297 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,
1304 int width = gdk_string_width(fs->file_list->style->font,
1306 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1307 if(width > file_list_width)
1309 file_list_width = width;
1310 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1316 poss = cmpl_next_completion (cmpl_state);
1319 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1320 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1322 /* File lists are set. */
1324 g_assert (cmpl_state->reference_dir);
1329 /* User is trying to complete filenames, so advance the user's input
1330 * string to the updated_text, which is the common leading substring
1331 * of all possible completions, and if its a directory attempt
1332 * attempt completions in it. */
1334 if (cmpl_updated_text (cmpl_state)[0])
1337 if (cmpl_updated_dir (cmpl_state))
1339 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1343 gtk_file_selection_populate (fs, dir_name, TRUE);
1349 if (fs->selection_entry)
1350 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1351 cmpl_updated_text (cmpl_state));
1356 selection_index = cmpl_last_valid_char (cmpl_state) -
1357 (strlen (rel_path) - strlen (rem_path));
1358 if (fs->selection_entry)
1359 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1364 if (fs->selection_entry)
1365 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1370 if (fs->selection_entry)
1371 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1373 if (fs->selection_entry)
1375 sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
1376 sizeof ("Selection: "));
1377 strcpy (sel_text, "Selection: ");
1378 strcat (sel_text, cmpl_reference_position (cmpl_state));
1380 gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
1384 if (fs->history_pulldown)
1386 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1393 gtk_file_selection_abort (GtkFileSelection *fs)
1397 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
1399 /* BEEP gdk_beep(); */
1401 if (fs->selection_entry)
1402 gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
1405 /**********************************************************************/
1406 /* External Interface */
1407 /**********************************************************************/
1409 /* The four completion state selectors
1412 cmpl_updated_text (CompletionState* cmpl_state)
1414 return cmpl_state->updated_text;
1418 cmpl_updated_dir (CompletionState* cmpl_state)
1420 return cmpl_state->re_complete;
1424 cmpl_reference_position (CompletionState* cmpl_state)
1426 return cmpl_state->reference_dir->fullname;
1430 cmpl_last_valid_char (CompletionState* cmpl_state)
1432 return cmpl_state->last_valid_char;
1436 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1440 strcpy (cmpl_state->updated_text, text);
1442 else if (text[0] == '~')
1447 dir = open_user_dir (text, cmpl_state);
1451 /* spencer says just return ~something, so
1452 * for now just do it. */
1453 strcpy (cmpl_state->updated_text, text);
1458 strcpy (cmpl_state->updated_text, dir->fullname);
1460 slash = strchr (text, '/');
1463 strcat (cmpl_state->updated_text, slash);
1468 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1469 strcat (cmpl_state->updated_text, "/");
1470 strcat (cmpl_state->updated_text, text);
1473 return cmpl_state->updated_text;
1476 /* The three completion selectors
1479 cmpl_this_completion (PossibleCompletion* pc)
1485 cmpl_is_directory (PossibleCompletion* pc)
1487 return pc->is_directory;
1491 cmpl_is_a_completion (PossibleCompletion* pc)
1493 return pc->is_a_completion;
1496 /**********************************************************************/
1497 /* Construction, deletion */
1498 /**********************************************************************/
1500 static CompletionState*
1501 cmpl_init_state (void)
1503 gchar getcwd_buf[2*MAXPATHLEN];
1504 CompletionState *new_state;
1506 new_state = g_new (CompletionState, 1);
1508 /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
1509 * and, if that wasn't bad enough, hangs in doing so.
1511 #if defined(sun) && !defined(__SVR4)
1512 if (!getwd (getcwd_buf))
1514 if (!getcwd (getcwd_buf, MAXPATHLEN))
1521 new_state->reference_dir = NULL;
1522 new_state->completion_dir = NULL;
1523 new_state->active_completion_dir = NULL;
1525 if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1527 /* if this fails, get_pwdb will fill it in. */
1528 new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1531 new_state->directory_storage = NULL;
1532 new_state->directory_sent_storage = NULL;
1533 new_state->last_valid_char = 0;
1534 new_state->updated_text = g_new (gchar, MAXPATHLEN);
1535 new_state->updated_text_alloc = MAXPATHLEN;
1536 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1537 new_state->the_completion.text_alloc = MAXPATHLEN;
1538 new_state->user_dir_name_buffer = NULL;
1539 new_state->user_directories = NULL;
1541 new_state->reference_dir = open_dir (getcwd_buf, new_state);
1543 if (!new_state->reference_dir)
1550 cmpl_free_dir_list(GList* dp0)
1555 free_dir (dp->data);
1563 cmpl_free_dir_sent_list(GList* dp0)
1568 free_dir_sent (dp->data);
1576 cmpl_free_state (CompletionState* cmpl_state)
1578 cmpl_free_dir_list (cmpl_state->directory_storage);
1579 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
1581 if (cmpl_state->user_dir_name_buffer)
1582 g_free (cmpl_state->user_dir_name_buffer);
1583 if (cmpl_state->user_home_dir)
1584 g_free (cmpl_state->user_home_dir);
1585 if (cmpl_state->user_directories)
1586 g_free (cmpl_state->user_directories);
1587 if (cmpl_state->the_completion.text)
1588 g_free (cmpl_state->the_completion.text);
1589 if (cmpl_state->updated_text)
1590 g_free (cmpl_state->updated_text);
1592 g_free (cmpl_state);
1596 free_dir(CompletionDir* dir)
1598 g_free(dir->fullname);
1603 free_dir_sent(CompletionDirSent* sent)
1605 g_free(sent->name_buffer);
1606 g_free(sent->entries);
1611 prune_memory_usage(CompletionState *cmpl_state)
1613 GList* cdsl = cmpl_state->directory_sent_storage;
1614 GList* cdl = cmpl_state->directory_storage;
1618 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1622 cmpl_free_dir_sent_list(cdsl->next);
1626 cmpl_state->directory_storage = NULL;
1628 if (cdl->data == cmpl_state->reference_dir)
1629 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1631 free_dir (cdl->data);
1638 /**********************************************************************/
1639 /* The main entrances. */
1640 /**********************************************************************/
1642 static PossibleCompletion*
1643 cmpl_completion_matches (gchar* text_to_complete,
1644 gchar** remaining_text,
1645 CompletionState* cmpl_state)
1648 PossibleCompletion *poss;
1650 prune_memory_usage(cmpl_state);
1652 g_assert(text_to_complete);
1654 cmpl_state->user_completion_index = -1;
1655 cmpl_state->last_completion_text = text_to_complete;
1656 cmpl_state->the_completion.text[0] = 0;
1657 cmpl_state->last_valid_char = 0;
1658 cmpl_state->updated_text_len = -1;
1659 cmpl_state->updated_text[0] = 0;
1660 cmpl_state->re_complete = FALSE;
1662 first_slash = strchr(text_to_complete, '/');
1664 if(text_to_complete[0] == '~' && !first_slash)
1666 /* Text starts with ~ and there is no slash, show all the
1667 * home directory completions.
1669 poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1671 update_cmpl(poss, cmpl_state);
1676 cmpl_state->reference_dir =
1677 open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1679 if(!cmpl_state->reference_dir)
1682 cmpl_state->completion_dir =
1683 find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1685 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1687 if(!cmpl_state->completion_dir)
1690 cmpl_state->completion_dir->cmpl_index = -1;
1691 cmpl_state->completion_dir->cmpl_parent = NULL;
1692 cmpl_state->completion_dir->cmpl_text = *remaining_text;
1694 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1696 cmpl_state->reference_dir = cmpl_state->completion_dir;
1698 poss = attempt_file_completion(cmpl_state);
1700 update_cmpl(poss, cmpl_state);
1705 static PossibleCompletion*
1706 cmpl_next_completion (CompletionState* cmpl_state)
1708 PossibleCompletion* poss = NULL;
1710 cmpl_state->the_completion.text[0] = 0;
1712 if(cmpl_state->user_completion_index >= 0)
1713 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1715 poss = attempt_file_completion(cmpl_state);
1717 update_cmpl(poss, cmpl_state);
1722 /**********************************************************************/
1723 /* Directory Operations */
1724 /**********************************************************************/
1726 /* Open the directory where completion will begin from, if possible. */
1727 static CompletionDir*
1728 open_ref_dir(gchar* text_to_complete,
1729 gchar** remaining_text,
1730 CompletionState* cmpl_state)
1733 CompletionDir *new_dir;
1735 first_slash = strchr(text_to_complete, '/');
1737 if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1739 new_dir = open_dir("/", cmpl_state);
1742 *remaining_text = text_to_complete + 1;
1744 else if (text_to_complete[0] == '~')
1746 new_dir = open_user_dir(text_to_complete, cmpl_state);
1751 *remaining_text = first_slash + 1;
1753 *remaining_text = text_to_complete + strlen(text_to_complete);
1762 *remaining_text = text_to_complete;
1764 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1769 new_dir->cmpl_index = -1;
1770 new_dir->cmpl_parent = NULL;
1776 /* open a directory by user name */
1777 static CompletionDir*
1778 open_user_dir(gchar* text_to_complete,
1779 CompletionState *cmpl_state)
1784 g_assert(text_to_complete && text_to_complete[0] == '~');
1786 first_slash = strchr(text_to_complete, '/');
1789 cmp_len = first_slash - text_to_complete - 1;
1791 cmp_len = strlen(text_to_complete + 1);
1796 if (!cmpl_state->user_home_dir &&
1797 !get_pwdb(cmpl_state))
1799 return open_dir(cmpl_state->user_home_dir, cmpl_state);
1804 char* copy = g_new(char, cmp_len + 1);
1806 strncpy(copy, text_to_complete + 1, cmp_len);
1808 pwd = getpwnam(copy);
1816 return open_dir(pwd->pw_dir, cmpl_state);
1820 /* open a directory relative the the current relative directory */
1821 static CompletionDir*
1822 open_relative_dir(gchar* dir_name,
1824 CompletionState *cmpl_state)
1826 gchar path_buf[2*MAXPATHLEN];
1828 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1830 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1834 strcpy(path_buf, dir->fullname);
1836 if(dir->fullname_len > 1)
1838 path_buf[dir->fullname_len] = '/';
1839 strcpy(path_buf + dir->fullname_len + 1, dir_name);
1843 strcpy(path_buf + dir->fullname_len, dir_name);
1846 return open_dir(path_buf, cmpl_state);
1849 /* after the cache lookup fails, really open a new directory */
1850 static CompletionDirSent*
1851 open_new_dir(gchar* dir_name, struct stat* sbuf)
1853 CompletionDirSent* sent;
1856 struct dirent *dirent_ptr;
1857 gint buffer_size = 0;
1858 gint entry_count = 0;
1860 struct stat ent_sbuf;
1861 char path_buf[MAXPATHLEN*2];
1864 sent = g_new(CompletionDirSent, 1);
1865 sent->mtime = sbuf->st_mtime;
1866 sent->inode = sbuf->st_ino;
1867 sent->device = sbuf->st_dev;
1869 path_buf_len = strlen(dir_name);
1871 if (path_buf_len > MAXPATHLEN)
1873 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1877 strcpy(path_buf, dir_name);
1879 directory = opendir(dir_name);
1887 while((dirent_ptr = readdir(directory)) != NULL)
1889 int entry_len = strlen(dirent_ptr->d_name);
1890 buffer_size += entry_len + 1;
1893 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1895 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1896 closedir(directory);
1901 sent->name_buffer = g_new(gchar, buffer_size);
1902 sent->entries = g_new(CompletionDirEntry, entry_count);
1903 sent->entry_count = entry_count;
1905 buffer_ptr = sent->name_buffer;
1907 rewinddir(directory);
1909 for(i = 0; i < entry_count; i += 1)
1911 dirent_ptr = readdir(directory);
1916 closedir(directory);
1920 strcpy(buffer_ptr, dirent_ptr->d_name);
1921 sent->entries[i].entry_name = buffer_ptr;
1922 buffer_ptr += strlen(dirent_ptr->d_name);
1926 path_buf[path_buf_len] = '/';
1927 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1929 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1930 sent->entries[i].is_dir = 1;
1932 /* stat may fail, and we don't mind, since it could be a
1933 * dangling symlink. */
1934 sent->entries[i].is_dir = 0;
1937 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1939 closedir(directory);
1944 /* open a directory by absolute pathname */
1945 static CompletionDir*
1946 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1949 CompletionDirSent *sent;
1952 if(stat(dir_name, &sbuf) < 0)
1958 cdsl = cmpl_state->directory_sent_storage;
1964 if(sent->inode == sbuf.st_ino &&
1965 sent->mtime == sbuf.st_mtime &&
1966 sent->device == sbuf.st_dev)
1967 return attach_dir(sent, dir_name, cmpl_state);
1972 sent = open_new_dir(dir_name, &sbuf);
1975 cmpl_state->directory_sent_storage =
1976 g_list_prepend(cmpl_state->directory_sent_storage, sent);
1978 return attach_dir(sent, dir_name, cmpl_state);
1984 static CompletionDir*
1985 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1987 CompletionDir* new_dir;
1989 new_dir = g_new(CompletionDir, 1);
1991 cmpl_state->directory_storage =
1992 g_list_prepend(cmpl_state->directory_storage, new_dir);
1994 new_dir->sent = sent;
1995 new_dir->fullname = g_strdup(dir_name);
1996 new_dir->fullname_len = strlen(dir_name);
2002 correct_dir_fullname(CompletionDir* cmpl_dir)
2004 gint length = strlen(cmpl_dir->fullname);
2007 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
2011 strcpy(cmpl_dir->fullname, "/");
2012 cmpl_dir->fullname_len = 1;
2015 cmpl_dir->fullname[length - 2] = 0;
2018 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
2019 cmpl_dir->fullname[length - 2] = 0;
2020 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
2024 strcpy(cmpl_dir->fullname, "/");
2025 cmpl_dir->fullname_len = 1;
2029 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2035 cmpl_dir->fullname[length - 2] = 0;
2037 if(!correct_parent(cmpl_dir, &sbuf))
2040 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
2044 strcpy(cmpl_dir->fullname, "/");
2045 cmpl_dir->fullname_len = 1;
2049 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2055 cmpl_dir->fullname[length - 3] = 0;
2057 if(!correct_parent(cmpl_dir, &sbuf))
2061 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
2067 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
2074 last_slash = strrchr(cmpl_dir->fullname, '/');
2076 g_assert(last_slash);
2078 if(last_slash != cmpl_dir->fullname)
2079 { /* last_slash[0] = 0; */ }
2086 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2092 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2093 /* it wasn't a link */
2099 last_slash[0] = '/'; */
2101 /* it was a link, have to figure it out the hard way */
2103 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2108 g_free(cmpl_dir->fullname);
2110 cmpl_dir->fullname = new_name;
2116 find_parent_dir_fullname(gchar* dirname)
2118 gchar buffer[MAXPATHLEN];
2119 gchar buffer2[MAXPATHLEN];
2121 #if defined(sun) && !defined(__SVR4)
2124 if(!getcwd(buffer, MAXPATHLEN))
2131 if(chdir(dirname) != 0 || chdir("..") != 0)
2137 #if defined(sun) && !defined(__SVR4)
2140 if(!getcwd(buffer2, MAXPATHLEN))
2149 if(chdir(buffer) != 0)
2155 return g_strdup(buffer2);
2158 /**********************************************************************/
2159 /* Completion Operations */
2160 /**********************************************************************/
2162 static PossibleCompletion*
2163 attempt_homedir_completion(gchar* text_to_complete,
2164 CompletionState *cmpl_state)
2168 if (!cmpl_state->user_dir_name_buffer &&
2169 !get_pwdb(cmpl_state))
2171 length = strlen(text_to_complete) - 1;
2173 cmpl_state->user_completion_index += 1;
2175 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2177 index = first_diff_index(text_to_complete + 1,
2178 cmpl_state->user_directories
2179 [cmpl_state->user_completion_index].login);
2186 if(cmpl_state->last_valid_char < (index + 1))
2187 cmpl_state->last_valid_char = index + 1;
2188 cmpl_state->user_completion_index += 1;
2192 cmpl_state->the_completion.is_a_completion = 1;
2193 cmpl_state->the_completion.is_directory = 1;
2195 append_completion_text("~", cmpl_state);
2197 append_completion_text(cmpl_state->
2198 user_directories[cmpl_state->user_completion_index].login,
2201 return append_completion_text("/", cmpl_state);
2204 if(text_to_complete[1] ||
2205 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2207 cmpl_state->user_completion_index = -1;
2212 cmpl_state->user_completion_index += 1;
2213 cmpl_state->the_completion.is_a_completion = 1;
2214 cmpl_state->the_completion.is_directory = 1;
2216 return append_completion_text("~/", cmpl_state);
2220 /* returns the index (>= 0) of the first differing character,
2221 * PATTERN_MATCH if the completion matches */
2223 first_diff_index(gchar* pat, gchar* text)
2227 while(*pat && *text && *text == *pat)
2237 return PATTERN_MATCH;
2240 static PossibleCompletion*
2241 append_completion_text(gchar* text, CompletionState* cmpl_state)
2245 if(!cmpl_state->the_completion.text)
2248 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2250 if(cmpl_state->the_completion.text_alloc > len)
2252 strcat(cmpl_state->the_completion.text, text);
2253 return &cmpl_state->the_completion;
2256 while(i < len) { i <<= 1; }
2258 cmpl_state->the_completion.text_alloc = i;
2260 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2262 if(!cmpl_state->the_completion.text)
2266 strcat(cmpl_state->the_completion.text, text);
2267 return &cmpl_state->the_completion;
2271 static CompletionDir*
2272 find_completion_dir(gchar* text_to_complete,
2273 gchar** remaining_text,
2274 CompletionState* cmpl_state)
2276 gchar* first_slash = strchr(text_to_complete, '/');
2277 CompletionDir* dir = cmpl_state->reference_dir;
2278 *remaining_text = text_to_complete;
2282 gint len = first_slash - *remaining_text;
2284 gint found_index = -1;
2286 gchar* pat_buf = g_new (gchar, len + 1);
2288 strncpy(pat_buf, *remaining_text, len);
2291 for(i = 0; i < dir->sent->entry_count; i += 1)
2293 if(dir->sent->entries[i].is_dir &&
2294 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2295 FNMATCH_FLAGS)!= FNM_NOMATCH)
2312 CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
2321 next->cmpl_parent = dir;
2325 if(!correct_dir_fullname(dir))
2331 *remaining_text = first_slash + 1;
2332 first_slash = strchr(*remaining_text, '/');
2347 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
2351 if(!poss || !cmpl_is_a_completion(poss))
2354 cmpl_len = strlen(cmpl_this_completion(poss));
2356 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
2358 cmpl_state->updated_text =
2359 (gchar*)g_realloc(cmpl_state->updated_text,
2360 cmpl_state->updated_text_alloc);
2361 cmpl_state->updated_text_alloc = 2*cmpl_len;
2364 if(cmpl_state->updated_text_len < 0)
2366 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2367 cmpl_state->updated_text_len = cmpl_len;
2368 cmpl_state->re_complete = cmpl_is_directory(poss);
2370 else if(cmpl_state->updated_text_len == 0)
2372 cmpl_state->re_complete = FALSE;
2377 first_diff_index(cmpl_state->updated_text,
2378 cmpl_this_completion(poss));
2380 cmpl_state->re_complete = FALSE;
2382 if(first_diff == PATTERN_MATCH)
2385 if(first_diff > cmpl_state->updated_text_len)
2386 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2388 cmpl_state->updated_text_len = first_diff;
2389 cmpl_state->updated_text[first_diff] = 0;
2393 static PossibleCompletion*
2394 attempt_file_completion(CompletionState *cmpl_state)
2396 gchar *pat_buf, *first_slash;
2397 CompletionDir *dir = cmpl_state->active_completion_dir;
2399 dir->cmpl_index += 1;
2401 if(dir->cmpl_index == dir->sent->entry_count)
2403 if(dir->cmpl_parent == NULL)
2405 cmpl_state->active_completion_dir = NULL;
2411 cmpl_state->active_completion_dir = dir->cmpl_parent;
2413 return attempt_file_completion(cmpl_state);
2417 g_assert(dir->cmpl_text);
2419 first_slash = strchr(dir->cmpl_text, '/');
2423 gint len = first_slash - dir->cmpl_text;
2425 pat_buf = g_new (gchar, len + 1);
2426 strncpy(pat_buf, dir->cmpl_text, len);
2431 gint len = strlen(dir->cmpl_text);
2433 pat_buf = g_new (gchar, len + 2);
2434 strcpy(pat_buf, dir->cmpl_text);
2435 strcpy(pat_buf + len, "*");
2440 if(dir->sent->entries[dir->cmpl_index].is_dir)
2442 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2443 FNMATCH_FLAGS) != FNM_NOMATCH)
2445 CompletionDir* new_dir;
2447 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
2456 new_dir->cmpl_parent = dir;
2458 new_dir->cmpl_index = -1;
2459 new_dir->cmpl_text = first_slash + 1;
2461 cmpl_state->active_completion_dir = new_dir;
2464 return attempt_file_completion(cmpl_state);
2469 return attempt_file_completion(cmpl_state);
2475 return attempt_file_completion(cmpl_state);
2480 if(dir->cmpl_parent != NULL)
2482 append_completion_text(dir->fullname +
2483 strlen(cmpl_state->completion_dir->fullname) + 1,
2485 append_completion_text("/", cmpl_state);
2488 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2490 cmpl_state->the_completion.is_a_completion =
2491 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2492 FNMATCH_FLAGS) != FNM_NOMATCH);
2494 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2495 if(dir->sent->entries[dir->cmpl_index].is_dir)
2496 append_completion_text("/", cmpl_state);
2499 return &cmpl_state->the_completion;
2505 get_pwdb(CompletionState* cmpl_state)
2507 struct passwd *pwd_ptr;
2509 gint len = 0, i, count = 0;
2511 if(cmpl_state->user_dir_name_buffer)
2515 while ((pwd_ptr = getpwent()) != NULL)
2517 len += strlen(pwd_ptr->pw_name);
2518 len += strlen(pwd_ptr->pw_dir);
2523 if (!cmpl_state->user_home_dir)
2525 /* the loser doesn't have $HOME set */
2528 pwd_ptr = getpwuid(getuid());
2534 /* Allocate this separately, since it might be filled in elsewhere */
2535 cmpl_state->user_home_dir = g_strdup (pwd_ptr->pw_dir);
2540 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2541 cmpl_state->user_directories = g_new(CompletionUserDir, count);
2542 cmpl_state->user_directories_len = count;
2544 buf_ptr = cmpl_state->user_dir_name_buffer;
2546 for(i = 0; i < count; i += 1)
2548 pwd_ptr = getpwent();
2555 strcpy(buf_ptr, pwd_ptr->pw_name);
2556 cmpl_state->user_directories[i].login = buf_ptr;
2557 buf_ptr += strlen(buf_ptr);
2559 strcpy(buf_ptr, pwd_ptr->pw_dir);
2560 cmpl_state->user_directories[i].homedir = buf_ptr;
2561 buf_ptr += strlen(buf_ptr);
2565 qsort(cmpl_state->user_directories,
2566 cmpl_state->user_directories_len,
2567 sizeof(CompletionUserDir),
2576 if(cmpl_state->user_dir_name_buffer)
2577 g_free(cmpl_state->user_dir_name_buffer);
2578 if(cmpl_state->user_directories)
2579 g_free(cmpl_state->user_directories);
2581 cmpl_state->user_dir_name_buffer = NULL;
2582 cmpl_state->user_directories = NULL;
2588 compare_user_dir(const void* a, const void* b)
2590 return strcmp((((CompletionUserDir*)a))->login,
2591 (((CompletionUserDir*)b))->login);
2595 compare_cmpl_dir(const void* a, const void* b)
2597 return strcmp((((CompletionDirEntry*)a))->entry_name,
2598 (((CompletionDirEntry*)b))->entry_name);
2602 cmpl_state_okay(CompletionState* cmpl_state)
2604 return cmpl_state && cmpl_state->reference_dir;
2608 cmpl_strerror(gint err)
2610 if(err == CMPL_ERRNO_TOO_LONG)
2611 return "Name too long";
2613 return g_strerror (err);