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"
37 #include "gtklistitem.h"
39 #include "gtkscrolledwindow.h"
40 #include "gtksignal.h"
43 #include "gtkmenuitem.h"
44 #include "gtkoptionmenu.h"
46 #include "gtkdialog.h"
48 #define DIR_LIST_WIDTH 180
49 #define DIR_LIST_HEIGHT 180
50 #define FILE_LIST_WIDTH 180
51 #define FILE_LIST_HEIGHT 180
53 /* I've put this here so it doesn't get confused with the
54 * file completion interface */
55 typedef struct _HistoryCallbackArg HistoryCallbackArg;
57 struct _HistoryCallbackArg
64 typedef struct _CompletionState CompletionState;
65 typedef struct _CompletionDir CompletionDir;
66 typedef struct _CompletionDirSent CompletionDirSent;
67 typedef struct _CompletionDirEntry CompletionDirEntry;
68 typedef struct _CompletionUserDir CompletionUserDir;
69 typedef struct _PossibleCompletion PossibleCompletion;
71 /* Non-external file completion decls and structures */
73 /* A contant telling PRCS how many directories to cache. Its actually
74 * kept in a list, so the geometry isn't important. */
75 #define CMPL_DIRECTORY_CACHE_SIZE 10
77 /* A constant used to determine whether a substring was an exact
78 * match by first_diff_index()
80 #define PATTERN_MATCH -1
81 /* The arguments used by all fnmatch() calls below
83 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
85 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
87 /* This structure contains all the useful information about a directory
88 * for the purposes of filename completion. These structures are cached
89 * in the CompletionState struct. CompletionDir's are reference counted.
91 struct _CompletionDirSent
97 gchar *name_buffer; /* memory segment containing names of all entries */
99 struct _CompletionDirEntry *entries;
102 struct _CompletionDir
104 CompletionDirSent *sent;
109 struct _CompletionDir *cmpl_parent;
114 /* This structure contains pairs of directory entry names with a flag saying
115 * whether or not they are a valid directory. NOTE: This information is used
116 * to provide the caller with information about whether to update its completions
117 * or try to open a file. Since directories are cached by the directory mtime,
118 * a symlink which points to an invalid file (which will not be a directory),
119 * will not be reevaluated if that file is created, unless the containing
120 * directory is touched. I consider this case to be worth ignoring (josh).
122 struct _CompletionDirEntry
128 struct _CompletionUserDir
134 struct _PossibleCompletion
136 /* accessible fields, all are accessed externally by functions
140 gint is_a_completion;
148 struct _CompletionState
150 gint last_valid_char;
152 gint updated_text_len;
153 gint updated_text_alloc;
156 gchar *user_dir_name_buffer;
157 gint user_directories_len;
158 gchar *user_home_dir;
160 gchar *last_completion_text;
162 gint user_completion_index; /* if >= 0, currently completing ~user */
164 struct _CompletionDir *completion_dir; /* directory completing from */
165 struct _CompletionDir *active_completion_dir;
167 struct _PossibleCompletion the_completion;
169 struct _CompletionDir *reference_dir; /* initial directory */
171 GList* directory_storage;
172 GList* directory_sent_storage;
174 struct _CompletionUserDir *user_directories;
178 /* File completion functions which would be external, were they used
179 * outside of this file.
182 static CompletionState* cmpl_init_state (void);
183 static void cmpl_free_state (CompletionState *cmpl_state);
184 static gint cmpl_state_okay (CompletionState* cmpl_state);
185 static gchar* cmpl_strerror (gint);
187 static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete,
188 gchar **remaining_text,
189 CompletionState *cmpl_state);
191 /* Returns a name for consideration, possibly a completion, this name
192 * will be invalid after the next call to cmpl_next_completion.
194 static char* cmpl_this_completion (PossibleCompletion*);
196 /* True if this completion matches the given text. Otherwise, this
197 * output can be used to have a list of non-completions.
199 static gint cmpl_is_a_completion (PossibleCompletion*);
201 /* True if the completion is a directory
203 static gint cmpl_is_directory (PossibleCompletion*);
205 /* Obtains the next completion, or NULL
207 static PossibleCompletion* cmpl_next_completion (CompletionState*);
209 /* Updating completions: the return value of cmpl_updated_text() will
210 * be text_to_complete completed as much as possible after the most
211 * recent call to cmpl_completion_matches. For the present
212 * application, this is the suggested replacement for the user's input
213 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
216 static gchar* cmpl_updated_text (CompletionState* cmpl_state);
218 /* After updating, to see if the completion was a directory, call
219 * this. If it was, you should consider re-calling completion_matches.
221 static gint cmpl_updated_dir (CompletionState* cmpl_state);
223 /* Current location: if using file completion, return the current
224 * directory, from which file completion begins. More specifically,
225 * the cwd concatenated with all exact completions up to the last
226 * directory delimiter('/').
228 static gchar* cmpl_reference_position (CompletionState* cmpl_state);
230 /* backing up: if cmpl_completion_matches returns NULL, you may query
231 * the index of the last completable character into cmpl_updated_text.
233 static gint cmpl_last_valid_char (CompletionState* cmpl_state);
235 /* When the user selects a non-directory, call cmpl_completion_fullname
236 * to get the full name of the selected file.
238 static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
241 /* Directory operations. */
242 static CompletionDir* open_ref_dir (gchar* text_to_complete,
243 gchar** remaining_text,
244 CompletionState* cmpl_state);
245 static CompletionDir* open_dir (gchar* dir_name,
246 CompletionState* cmpl_state);
247 static CompletionDir* open_user_dir (gchar* text_to_complete,
248 CompletionState *cmpl_state);
249 static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
250 CompletionState *cmpl_state);
251 static CompletionDirSent* open_new_dir (gchar* dir_name, struct stat* sbuf);
252 static gint correct_dir_fullname (CompletionDir* cmpl_dir);
253 static gint correct_parent (CompletionDir* cmpl_dir,
255 static gchar* find_parent_dir_fullname (gchar* dirname);
256 static CompletionDir* attach_dir (CompletionDirSent* sent,
258 CompletionState *cmpl_state);
259 static void free_dir_sent (CompletionDirSent* sent);
260 static void free_dir (CompletionDir *dir);
261 static void prune_memory_usage(CompletionState *cmpl_state);
263 /* Completion operations */
264 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
265 CompletionState *cmpl_state);
266 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
267 static CompletionDir* find_completion_dir(gchar* text_to_complete,
268 gchar** remaining_text,
269 CompletionState* cmpl_state);
270 static PossibleCompletion* append_completion_text(gchar* text,
271 CompletionState* cmpl_state);
272 static gint get_pwdb(CompletionState* cmpl_state);
273 static gint first_diff_index(gchar* pat, gchar* text);
274 static gint compare_user_dir(const void* a, const void* b);
275 static gint compare_cmpl_dir(const void* a, const void* b);
276 static void update_cmpl(PossibleCompletion* poss,
277 CompletionState* cmpl_state);
279 static void gtk_file_selection_class_init (GtkFileSelectionClass *klass);
280 static void gtk_file_selection_init (GtkFileSelection *filesel);
281 static void gtk_file_selection_destroy (GtkObject *object);
282 static gint gtk_file_selection_key_press (GtkWidget *widget,
286 static void gtk_file_selection_file_button (GtkWidget *widget,
289 GdkEventButton *bevent,
292 static void gtk_file_selection_dir_button (GtkWidget *widget,
295 GdkEventButton *bevent,
298 static void gtk_file_selection_populate (GtkFileSelection *fs,
301 static void gtk_file_selection_abort (GtkFileSelection *fs);
303 static void gtk_file_selection_update_history_menu (GtkFileSelection *fs,
306 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
307 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
308 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
312 static GtkWindowClass *parent_class = NULL;
314 /* Saves errno when something cmpl does fails. */
315 static gint cmpl_errno;
318 gtk_file_selection_get_type ()
320 static guint file_selection_type = 0;
322 if (!file_selection_type)
324 GtkTypeInfo filesel_info =
327 sizeof (GtkFileSelection),
328 sizeof (GtkFileSelectionClass),
329 (GtkClassInitFunc) gtk_file_selection_class_init,
330 (GtkObjectInitFunc) gtk_file_selection_init,
331 (GtkArgSetFunc) NULL,
332 (GtkArgGetFunc) NULL,
335 file_selection_type = gtk_type_unique (gtk_window_get_type (), &filesel_info);
338 return file_selection_type;
342 gtk_file_selection_class_init (GtkFileSelectionClass *class)
344 GtkObjectClass *object_class;
346 object_class = (GtkObjectClass*) class;
348 parent_class = gtk_type_class (gtk_window_get_type ());
350 object_class->destroy = gtk_file_selection_destroy;
354 gtk_file_selection_init (GtkFileSelection *filesel)
356 GtkWidget *entry_vbox;
358 GtkWidget *list_hbox;
359 GtkWidget *action_area;
360 GtkWidget *pulldown_hbox;
361 GtkWidget *button_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 button_hbox = gtk_hbox_new (TRUE, 0);
376 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), button_hbox,
378 gtk_widget_show (button_hbox);
380 /* delete, create directory, and rename */
381 button = gtk_button_new_with_label ("Create Dir");
382 gtk_signal_connect (GTK_OBJECT (button), "clicked",
383 (GtkSignalFunc) gtk_file_selection_create_dir,
385 gtk_box_pack_start (GTK_BOX (button_hbox), button, TRUE, TRUE, 0);
386 gtk_widget_show (button);
388 button = gtk_button_new_with_label ("Delete File");
389 gtk_signal_connect (GTK_OBJECT (button), "clicked",
390 (GtkSignalFunc) gtk_file_selection_delete_file,
392 gtk_box_pack_start (GTK_BOX (button_hbox), button, TRUE, TRUE, 0);
393 gtk_widget_show (button);
395 button = gtk_button_new_with_label ("Rename File");
396 gtk_signal_connect (GTK_OBJECT (button), "clicked",
397 (GtkSignalFunc) gtk_file_selection_rename_file,
399 gtk_box_pack_start (GTK_BOX (button_hbox), button, TRUE, TRUE, 0);
400 gtk_widget_show (button);
402 /* The Help button */
403 filesel->help_button = gtk_button_new_with_label ("Help");
404 gtk_box_pack_start (GTK_BOX (button_hbox), filesel->help_button,
406 gtk_widget_show (filesel->help_button);
409 /* hbox for pulldown menu */
410 pulldown_hbox = gtk_hbox_new (TRUE, 5);
411 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
412 gtk_widget_show (pulldown_hbox);
415 filesel->history_pulldown = gtk_option_menu_new ();
416 gtk_widget_show (filesel->history_pulldown);
417 gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown,
420 /* The horizontal box containing the directory and file listboxes */
421 list_hbox = gtk_hbox_new (FALSE, 5);
422 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
423 gtk_widget_show (list_hbox);
425 /* The directories clist */
426 filesel->dir_list = gtk_clist_new_with_titles (1, dir_title);
427 gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
428 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
429 (GtkSignalFunc) gtk_file_selection_dir_button,
431 gtk_clist_set_policy (GTK_CLIST (filesel->dir_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
432 gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
433 gtk_container_border_width (GTK_CONTAINER (filesel->dir_list), 5);
434 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->dir_list, TRUE, TRUE, 0);
435 gtk_widget_show (filesel->dir_list);
437 /* The files clist */
438 filesel->file_list = gtk_clist_new_with_titles (1, file_title);
439 gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
440 gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
441 (GtkSignalFunc) gtk_file_selection_file_button,
443 gtk_clist_set_policy (GTK_CLIST (filesel->file_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
444 gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
445 gtk_container_border_width (GTK_CONTAINER (filesel->file_list), 5);
446 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->file_list, TRUE, TRUE, 0);
447 gtk_widget_show (filesel->file_list);
449 /* The action area */
450 action_area = gtk_hbox_new (TRUE, 10);
451 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), action_area, FALSE, FALSE, 0);
452 gtk_widget_show (action_area);
455 filesel->ok_button = gtk_button_new_with_label ("OK");
456 GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
457 gtk_box_pack_start (GTK_BOX (action_area), filesel->ok_button, TRUE, TRUE, 0);
458 gtk_widget_grab_default (filesel->ok_button);
459 gtk_widget_show (filesel->ok_button);
461 /* The Cancel button */
462 filesel->cancel_button = gtk_button_new_with_label ("Cancel");
463 GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
464 gtk_box_pack_start (GTK_BOX (action_area), filesel->cancel_button, TRUE, TRUE, 0);
465 gtk_widget_show (filesel->cancel_button);
467 /* The selection entry widget */
468 entry_vbox = gtk_vbox_new (FALSE, 2);
469 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
470 gtk_widget_show (entry_vbox);
472 filesel->selection_text = label = gtk_label_new ("");
473 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
474 gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
475 gtk_widget_show (label);
477 filesel->selection_entry = gtk_entry_new ();
478 gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
479 (GtkSignalFunc) gtk_file_selection_key_press, filesel);
480 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
481 (GtkSignalFunc) gtk_widget_grab_default,
482 GTK_OBJECT (filesel->ok_button));
483 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
484 (GtkSignalFunc) gtk_button_clicked,
485 GTK_OBJECT (filesel->ok_button));
486 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
487 gtk_widget_show (filesel->selection_entry);
489 if (!cmpl_state_okay (filesel->cmpl_state))
493 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
495 gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
499 gtk_file_selection_populate (filesel, "", FALSE);
502 gtk_widget_grab_focus (filesel->selection_entry);
506 gtk_file_selection_new (const gchar *title)
508 GtkFileSelection *filesel;
510 filesel = gtk_type_new (gtk_file_selection_get_type ());
511 gtk_window_set_title (GTK_WINDOW (filesel), title);
513 return GTK_WIDGET (filesel);
517 gtk_file_selection_set_filename (GtkFileSelection *filesel,
518 const gchar *filename)
520 char buf[MAXPATHLEN];
521 const char *name, *last_slash;
523 g_return_if_fail (filesel != NULL);
524 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
525 g_return_if_fail (filename != NULL);
527 last_slash = strrchr (filename, '/');
536 gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
538 strncpy (buf, filename, len);
541 name = last_slash + 1;
544 gtk_file_selection_populate (filesel, buf, FALSE);
546 if (filesel->selection_entry)
547 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
551 gtk_file_selection_get_filename (GtkFileSelection *filesel)
553 static char nothing[2] = "";
557 g_return_val_if_fail (filesel != NULL, nothing);
558 g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
560 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
563 filename = cmpl_completion_fullname (text, filesel->cmpl_state);
571 gtk_file_selection_destroy (GtkObject *object)
573 GtkFileSelection *filesel;
575 HistoryCallbackArg *callback_arg;
577 g_return_if_fail (object != NULL);
578 g_return_if_fail (GTK_IS_FILE_SELECTION (object));
580 filesel = GTK_FILE_SELECTION (object);
582 if (filesel->fileop_dialog)
583 gtk_widget_destroy (filesel->fileop_dialog);
585 if (filesel->history_list)
587 list = filesel->history_list;
590 callback_arg = list->data;
591 g_free (callback_arg->directory);
594 g_list_free (filesel->history_list);
595 filesel->history_list = NULL;
598 cmpl_free_state (filesel->cmpl_state);
599 filesel->cmpl_state = NULL;
601 if (GTK_OBJECT_CLASS (parent_class)->destroy)
602 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
605 /* Begin file operations callbacks */
608 gtk_file_selection_fileop_error (gchar *error_message)
615 g_return_if_fail (error_message != NULL);
618 dialog = gtk_dialog_new ();
620 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
621 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
624 gtk_window_set_title (GTK_WINDOW (dialog), "Error");
625 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
627 vbox = gtk_vbox_new(FALSE, 0);
628 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
629 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
631 gtk_widget_show(vbox);
633 label = gtk_label_new(error_message);
634 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
635 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
636 gtk_widget_show(label);
638 /* yes, we free it */
639 g_free (error_message);
642 button = gtk_button_new_with_label ("Close");
643 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
644 (GtkSignalFunc) gtk_widget_destroy,
646 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
647 button, TRUE, TRUE, 0);
648 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
649 gtk_widget_grab_default(button);
650 gtk_widget_show (button);
652 gtk_widget_show (dialog);
656 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
658 GtkFileSelection *fs = data;
660 g_return_if_fail (fs != NULL);
661 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
663 fs->fileop_dialog = NULL;
668 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
670 GtkFileSelection *fs = data;
675 CompletionState *cmpl_state;
677 g_return_if_fail (fs != NULL);
678 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
680 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
681 cmpl_state = (CompletionState*) fs->cmpl_state;
682 path = cmpl_reference_position (cmpl_state);
684 full_path = g_strconcat (path, "/", dirname, NULL);
685 if ( (mkdir (full_path, 0755) < 0) )
687 buf = g_strconcat ("Error creating directory \"", dirname, "\": ",
688 g_strerror(errno), NULL);
689 gtk_file_selection_fileop_error (buf);
693 gtk_widget_destroy (fs->fileop_dialog);
694 gtk_file_selection_populate (fs, "", FALSE);
698 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
700 GtkFileSelection *fs = data;
706 g_return_if_fail (fs != NULL);
707 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
709 if (fs->fileop_dialog)
713 fs->fileop_dialog = dialog = gtk_dialog_new ();
714 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
715 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
717 gtk_window_set_title (GTK_WINDOW (dialog), "Create Directory");
718 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
719 gtk_widget_show (dialog);
721 vbox = gtk_vbox_new(FALSE, 0);
722 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
723 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
725 gtk_widget_show(vbox);
727 label = gtk_label_new("Directory name:");
728 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
729 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
730 gtk_widget_show(label);
732 /* The directory entry widget */
733 fs->fileop_entry = gtk_entry_new ();
734 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
736 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
737 gtk_widget_show (fs->fileop_entry);
740 button = gtk_button_new_with_label ("Create");
741 gtk_signal_connect (GTK_OBJECT (button), "clicked",
742 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
744 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
745 button, TRUE, TRUE, 0);
746 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
747 gtk_widget_show(button);
749 button = gtk_button_new_with_label ("Cancel");
750 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
751 (GtkSignalFunc) gtk_widget_destroy,
753 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
754 button, TRUE, TRUE, 0);
755 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
756 gtk_widget_grab_default(button);
757 gtk_widget_show (button);
761 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
763 GtkFileSelection *fs = data;
764 CompletionState *cmpl_state;
769 g_return_if_fail (fs != NULL);
770 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
772 cmpl_state = (CompletionState*) fs->cmpl_state;
773 path = cmpl_reference_position (cmpl_state);
775 full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
776 if ( (unlink (full_path) < 0) )
778 buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ",
779 g_strerror(errno), NULL);
780 gtk_file_selection_fileop_error (buf);
784 gtk_widget_destroy (fs->fileop_dialog);
785 gtk_file_selection_populate (fs, "", FALSE);
789 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
791 GtkFileSelection *fs = data;
799 g_return_if_fail (fs != NULL);
800 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
802 if (fs->fileop_dialog)
805 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
806 if (strlen(filename) < 1)
809 fs->fileop_file = filename;
812 fs->fileop_dialog = dialog = gtk_dialog_new ();
813 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
814 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
816 gtk_window_set_title (GTK_WINDOW (dialog), "Delete File");
817 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
819 vbox = gtk_vbox_new(FALSE, 0);
820 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
821 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
823 gtk_widget_show(vbox);
825 buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
826 label = gtk_label_new(buf);
827 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
828 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
829 gtk_widget_show(label);
833 button = gtk_button_new_with_label ("Delete");
834 gtk_signal_connect (GTK_OBJECT (button), "clicked",
835 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
837 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
838 button, TRUE, TRUE, 0);
839 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
840 gtk_widget_show(button);
842 button = gtk_button_new_with_label ("Cancel");
843 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
844 (GtkSignalFunc) gtk_widget_destroy,
846 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
847 button, TRUE, TRUE, 0);
848 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
849 gtk_widget_grab_default(button);
850 gtk_widget_show (button);
852 gtk_widget_show (dialog);
856 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
858 GtkFileSelection *fs = data;
864 CompletionState *cmpl_state;
866 g_return_if_fail (fs != NULL);
867 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
869 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
870 cmpl_state = (CompletionState*) fs->cmpl_state;
871 path = cmpl_reference_position (cmpl_state);
873 new_filename = g_strconcat (path, "/", file, NULL);
874 old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
876 if ( (rename (old_filename, new_filename)) < 0)
878 buf = g_strconcat ("Error renaming file \"", file, "\": ",
879 g_strerror(errno), NULL);
880 gtk_file_selection_fileop_error (buf);
882 g_free (new_filename);
883 g_free (old_filename);
885 gtk_widget_destroy (fs->fileop_dialog);
886 gtk_file_selection_populate (fs, "", FALSE);
890 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
892 GtkFileSelection *fs = data;
899 g_return_if_fail (fs != NULL);
900 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
902 if (fs->fileop_dialog)
905 fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
906 if (strlen(fs->fileop_file) < 1)
910 fs->fileop_dialog = dialog = gtk_dialog_new ();
911 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
912 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
914 gtk_window_set_title (GTK_WINDOW (dialog), "Rename File");
915 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
916 gtk_widget_show (dialog);
918 vbox = gtk_vbox_new(FALSE, 0);
919 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
920 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
922 gtk_widget_show(vbox);
924 buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
925 label = gtk_label_new(buf);
926 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
927 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
928 gtk_widget_show(label);
931 /* New filename entry */
932 fs->fileop_entry = gtk_entry_new ();
933 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
935 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
936 gtk_widget_show (fs->fileop_entry);
938 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
939 gtk_entry_select_region (GTK_ENTRY (fs->fileop_entry),
940 0, strlen (fs->fileop_file));
943 button = gtk_button_new_with_label ("Rename");
944 gtk_signal_connect (GTK_OBJECT (button), "clicked",
945 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
947 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
948 button, TRUE, TRUE, 0);
949 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
950 gtk_widget_show(button);
952 button = gtk_button_new_with_label ("Cancel");
953 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
954 (GtkSignalFunc) gtk_widget_destroy,
956 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
957 button, TRUE, TRUE, 0);
958 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
959 gtk_widget_grab_default(button);
960 gtk_widget_show (button);
965 gtk_file_selection_key_press (GtkWidget *widget,
969 GtkFileSelection *fs;
972 g_return_val_if_fail (widget != NULL, FALSE);
973 g_return_val_if_fail (event != NULL, FALSE);
975 if (event->keyval == GDK_Tab)
977 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
979 fs = GTK_FILE_SELECTION (user_data);
980 text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
981 gtk_file_selection_populate (fs, text, TRUE);
991 gtk_file_selection_history_callback (GtkWidget *widget, gpointer data)
993 GtkFileSelection *fs = data;
994 HistoryCallbackArg *callback_arg;
997 g_return_if_fail (fs != NULL);
998 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1000 list = fs->history_list;
1003 callback_arg = list->data;
1005 if (callback_arg->menu_item == widget)
1007 gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1016 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1017 gchar *current_directory)
1019 HistoryCallbackArg *callback_arg;
1020 GtkWidget *menu_item;
1027 g_return_if_fail (fs != NULL);
1028 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1029 g_return_if_fail (current_directory != NULL);
1031 list = fs->history_list;
1033 if (fs->history_menu)
1036 callback_arg = list->data;
1037 g_free (callback_arg->directory);
1040 g_list_free (fs->history_list);
1041 fs->history_list = NULL;
1043 gtk_widget_destroy (fs->history_menu);
1046 fs->history_menu = gtk_menu_new();
1048 current_dir = g_strdup (current_directory);
1050 dir_len = strlen (current_dir);
1052 for (i = dir_len; i >= 0; i--)
1054 /* the i == dir_len is to catch the full path for the first
1056 if ( (current_dir[i] == '/') || (i == dir_len))
1058 /* another small hack to catch the full path */
1060 current_dir[i + 1] = '\0';
1061 menu_item = gtk_menu_item_new_with_label (current_dir);
1062 directory = g_strdup (current_dir);
1064 callback_arg = g_new (HistoryCallbackArg, 1);
1065 callback_arg->menu_item = menu_item;
1067 /* since the autocompletion gets confused if you don't
1068 * supply a trailing '/' on a dir entry, set the full
1069 * (current) path to "" which just refreshes the filesel */
1071 callback_arg->directory = g_strdup ("");
1073 callback_arg->directory = directory;
1076 fs->history_list = g_list_append (fs->history_list, callback_arg);
1078 gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1079 (GtkSignalFunc) gtk_file_selection_history_callback,
1081 gtk_menu_append (GTK_MENU (fs->history_menu), menu_item);
1082 gtk_widget_show (menu_item);
1086 gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown),
1088 g_free (current_dir);
1094 gtk_file_selection_file_button (GtkWidget *widget,
1097 GdkEventButton *bevent,
1100 GtkFileSelection *fs = NULL;
1103 g_return_if_fail (GTK_IS_CLIST (widget));
1106 g_return_if_fail (fs != NULL);
1107 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1109 filename = gtk_clist_get_row_data (GTK_CLIST (fs->file_list), row);
1111 if (bevent && filename)
1113 switch (bevent->type)
1115 case GDK_BUTTON_PRESS:
1116 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1119 case GDK_2BUTTON_PRESS:
1120 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1130 gtk_file_selection_dir_button (GtkWidget *widget,
1133 GdkEventButton *bevent,
1136 GtkFileSelection *fs = NULL;
1139 g_return_if_fail (GTK_IS_CLIST (widget));
1141 fs = GTK_FILE_SELECTION (user_data);
1142 g_return_if_fail (fs != NULL);
1143 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1145 filename = gtk_clist_get_row_data (GTK_CLIST (fs->dir_list), row);
1147 if (bevent && filename) {
1149 switch (bevent->type)
1151 case GDK_BUTTON_PRESS:
1152 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1155 case GDK_2BUTTON_PRESS:
1156 gtk_file_selection_populate (fs, filename, FALSE);
1166 gtk_file_selection_populate (GtkFileSelection *fs,
1170 CompletionState *cmpl_state;
1171 PossibleCompletion* poss;
1174 gchar* rem_path = rel_path;
1177 gint did_recurse = FALSE;
1178 gint possible_count = 0;
1179 gint selection_index = -1;
1181 g_return_if_fail (fs != NULL);
1182 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1184 cmpl_state = (CompletionState*) fs->cmpl_state;
1185 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1187 if (!cmpl_state_okay (cmpl_state))
1189 /* Something went wrong. */
1190 gtk_file_selection_abort (fs);
1194 g_assert (cmpl_state->reference_dir);
1196 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1197 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1198 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1199 gtk_clist_clear (GTK_CLIST (fs->file_list));
1201 /* Set the dir_list to include ./ and ../ */
1204 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1205 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row, "./");
1208 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1209 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row, "../");
1213 if (cmpl_is_a_completion (poss))
1215 possible_count += 1;
1217 filename = g_strdup (cmpl_this_completion (poss));
1221 if (cmpl_is_directory (poss))
1223 if (strcmp (filename, "./") != 0 &&
1224 strcmp (filename, "../") != 0)
1226 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1227 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row,
1233 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1234 gtk_clist_set_row_data (GTK_CLIST (fs->file_list), row,
1239 poss = cmpl_next_completion (cmpl_state);
1242 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1243 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1245 /* File lists are set. */
1247 g_assert (cmpl_state->reference_dir);
1252 /* User is trying to complete filenames, so advance the user's input
1253 * string to the updated_text, which is the common leading substring
1254 * of all possible completions, and if its a directory attempt
1255 * attempt completions in it. */
1257 if (cmpl_updated_text (cmpl_state)[0])
1260 if (cmpl_updated_dir (cmpl_state))
1262 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1266 gtk_file_selection_populate (fs, dir_name, TRUE);
1272 if (fs->selection_entry)
1273 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1274 cmpl_updated_text (cmpl_state));
1279 selection_index = cmpl_last_valid_char (cmpl_state) -
1280 (strlen (rel_path) - strlen (rem_path));
1281 if (fs->selection_entry)
1282 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1287 if (fs->selection_entry)
1288 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1293 if (fs->selection_entry)
1294 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1296 if (fs->selection_entry)
1298 sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
1299 sizeof ("Selection: "));
1300 strcpy (sel_text, "Selection: ");
1301 strcat (sel_text, cmpl_reference_position (cmpl_state));
1303 gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
1307 if (fs->history_pulldown)
1309 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1316 gtk_file_selection_abort (GtkFileSelection *fs)
1320 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
1322 /* BEEP gdk_beep(); */
1324 if (fs->selection_entry)
1325 gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
1328 /**********************************************************************/
1329 /* External Interface */
1330 /**********************************************************************/
1332 /* The four completion state selectors
1335 cmpl_updated_text (CompletionState* cmpl_state)
1337 return cmpl_state->updated_text;
1341 cmpl_updated_dir (CompletionState* cmpl_state)
1343 return cmpl_state->re_complete;
1347 cmpl_reference_position (CompletionState* cmpl_state)
1349 return cmpl_state->reference_dir->fullname;
1353 cmpl_last_valid_char (CompletionState* cmpl_state)
1355 return cmpl_state->last_valid_char;
1359 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1363 strcpy (cmpl_state->updated_text, text);
1365 else if (text[0] == '~')
1370 dir = open_user_dir (text, cmpl_state);
1374 /* spencer says just return ~something, so
1375 * for now just do it. */
1376 strcpy (cmpl_state->updated_text, text);
1381 strcpy (cmpl_state->updated_text, dir->fullname);
1383 slash = strchr (text, '/');
1386 strcat (cmpl_state->updated_text, slash);
1391 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1392 strcat (cmpl_state->updated_text, "/");
1393 strcat (cmpl_state->updated_text, text);
1396 return cmpl_state->updated_text;
1399 /* The three completion selectors
1402 cmpl_this_completion (PossibleCompletion* pc)
1408 cmpl_is_directory (PossibleCompletion* pc)
1410 return pc->is_directory;
1414 cmpl_is_a_completion (PossibleCompletion* pc)
1416 return pc->is_a_completion;
1419 /**********************************************************************/
1420 /* Construction, deletion */
1421 /**********************************************************************/
1423 static CompletionState*
1424 cmpl_init_state (void)
1426 gchar getcwd_buf[2*MAXPATHLEN];
1427 CompletionState *new_state;
1429 new_state = g_new (CompletionState, 1);
1431 if (!getcwd (getcwd_buf, MAXPATHLEN))
1437 new_state->reference_dir = NULL;
1438 new_state->completion_dir = NULL;
1439 new_state->active_completion_dir = NULL;
1441 if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1443 /* if this fails, get_pwdb will fill it in. */
1444 new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1447 new_state->directory_storage = NULL;
1448 new_state->directory_sent_storage = NULL;
1449 new_state->last_valid_char = 0;
1450 new_state->updated_text = g_new (gchar, MAXPATHLEN);
1451 new_state->updated_text_alloc = MAXPATHLEN;
1452 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1453 new_state->the_completion.text_alloc = MAXPATHLEN;
1454 new_state->user_dir_name_buffer = NULL;
1455 new_state->user_directories = NULL;
1457 new_state->reference_dir = open_dir (getcwd_buf, new_state);
1459 if (!new_state->reference_dir)
1466 cmpl_free_dir_list(GList* dp0)
1471 free_dir (dp->data);
1479 cmpl_free_dir_sent_list(GList* dp0)
1484 free_dir_sent (dp->data);
1492 cmpl_free_state (CompletionState* cmpl_state)
1494 cmpl_free_dir_list (cmpl_state->directory_storage);
1495 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
1497 if (cmpl_state->user_dir_name_buffer)
1498 g_free (cmpl_state->user_dir_name_buffer);
1499 if (cmpl_state->user_directories)
1500 g_free (cmpl_state->user_directories);
1501 if (cmpl_state->the_completion.text)
1502 g_free (cmpl_state->the_completion.text);
1503 if (cmpl_state->updated_text)
1504 g_free (cmpl_state->updated_text);
1506 g_free (cmpl_state);
1510 free_dir(CompletionDir* dir)
1512 g_free(dir->fullname);
1517 free_dir_sent(CompletionDirSent* sent)
1519 g_free(sent->name_buffer);
1520 g_free(sent->entries);
1525 prune_memory_usage(CompletionState *cmpl_state)
1527 GList* cdsl = cmpl_state->directory_sent_storage;
1528 GList* cdl = cmpl_state->directory_storage;
1532 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1536 cmpl_free_dir_sent_list(cdsl->next);
1540 cmpl_state->directory_storage = NULL;
1542 if (cdl->data == cmpl_state->reference_dir)
1543 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1545 free_dir (cdl->data);
1552 /**********************************************************************/
1553 /* The main entrances. */
1554 /**********************************************************************/
1556 static PossibleCompletion*
1557 cmpl_completion_matches (gchar* text_to_complete,
1558 gchar** remaining_text,
1559 CompletionState* cmpl_state)
1562 PossibleCompletion *poss;
1564 prune_memory_usage(cmpl_state);
1566 g_assert(text_to_complete);
1568 cmpl_state->user_completion_index = -1;
1569 cmpl_state->last_completion_text = text_to_complete;
1570 cmpl_state->the_completion.text[0] = 0;
1571 cmpl_state->last_valid_char = 0;
1572 cmpl_state->updated_text_len = -1;
1573 cmpl_state->updated_text[0] = 0;
1574 cmpl_state->re_complete = FALSE;
1576 first_slash = strchr(text_to_complete, '/');
1578 if(text_to_complete[0] == '~' && !first_slash)
1580 /* Text starts with ~ and there is no slash, show all the
1581 * home directory completions.
1583 poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1585 update_cmpl(poss, cmpl_state);
1590 cmpl_state->reference_dir =
1591 open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1593 if(!cmpl_state->reference_dir)
1596 cmpl_state->completion_dir =
1597 find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1599 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1601 if(!cmpl_state->completion_dir)
1604 cmpl_state->completion_dir->cmpl_index = -1;
1605 cmpl_state->completion_dir->cmpl_parent = NULL;
1606 cmpl_state->completion_dir->cmpl_text = *remaining_text;
1608 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1610 cmpl_state->reference_dir = cmpl_state->completion_dir;
1612 poss = attempt_file_completion(cmpl_state);
1614 update_cmpl(poss, cmpl_state);
1619 static PossibleCompletion*
1620 cmpl_next_completion (CompletionState* cmpl_state)
1622 PossibleCompletion* poss = NULL;
1624 cmpl_state->the_completion.text[0] = 0;
1626 if(cmpl_state->user_completion_index >= 0)
1627 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1629 poss = attempt_file_completion(cmpl_state);
1631 update_cmpl(poss, cmpl_state);
1636 /**********************************************************************/
1637 /* Directory Operations */
1638 /**********************************************************************/
1640 /* Open the directory where completion will begin from, if possible. */
1641 static CompletionDir*
1642 open_ref_dir(gchar* text_to_complete,
1643 gchar** remaining_text,
1644 CompletionState* cmpl_state)
1647 CompletionDir *new_dir;
1649 first_slash = strchr(text_to_complete, '/');
1651 if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1653 new_dir = open_dir("/", cmpl_state);
1656 *remaining_text = text_to_complete + 1;
1658 else if (text_to_complete[0] == '~')
1660 new_dir = open_user_dir(text_to_complete, cmpl_state);
1665 *remaining_text = first_slash + 1;
1667 *remaining_text = text_to_complete + strlen(text_to_complete);
1676 *remaining_text = text_to_complete;
1678 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1683 new_dir->cmpl_index = -1;
1684 new_dir->cmpl_parent = NULL;
1690 /* open a directory by user name */
1691 static CompletionDir*
1692 open_user_dir(gchar* text_to_complete,
1693 CompletionState *cmpl_state)
1698 g_assert(text_to_complete && text_to_complete[0] == '~');
1700 first_slash = strchr(text_to_complete, '/');
1703 cmp_len = first_slash - text_to_complete - 1;
1705 cmp_len = strlen(text_to_complete + 1);
1710 if (!cmpl_state->user_home_dir &&
1711 !get_pwdb(cmpl_state))
1713 return open_dir(cmpl_state->user_home_dir, cmpl_state);
1718 char* copy = g_new(char, cmp_len + 1);
1720 strncpy(copy, text_to_complete + 1, cmp_len);
1722 pwd = getpwnam(copy);
1730 return open_dir(pwd->pw_dir, cmpl_state);
1734 /* open a directory relative the the current relative directory */
1735 static CompletionDir*
1736 open_relative_dir(gchar* dir_name,
1738 CompletionState *cmpl_state)
1740 gchar path_buf[2*MAXPATHLEN];
1742 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1744 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1748 strcpy(path_buf, dir->fullname);
1750 if(dir->fullname_len > 1)
1752 path_buf[dir->fullname_len] = '/';
1753 strcpy(path_buf + dir->fullname_len + 1, dir_name);
1757 strcpy(path_buf + dir->fullname_len, dir_name);
1760 return open_dir(path_buf, cmpl_state);
1763 /* after the cache lookup fails, really open a new directory */
1764 static CompletionDirSent*
1765 open_new_dir(gchar* dir_name, struct stat* sbuf)
1767 CompletionDirSent* sent;
1770 struct dirent *dirent_ptr;
1771 gint buffer_size = 0;
1772 gint entry_count = 0;
1774 struct stat ent_sbuf;
1775 char path_buf[MAXPATHLEN*2];
1778 sent = g_new(CompletionDirSent, 1);
1779 sent->mtime = sbuf->st_mtime;
1780 sent->inode = sbuf->st_ino;
1782 path_buf_len = strlen(dir_name);
1784 if (path_buf_len > MAXPATHLEN)
1786 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1790 strcpy(path_buf, dir_name);
1792 directory = opendir(dir_name);
1800 while((dirent_ptr = readdir(directory)) != NULL)
1802 int entry_len = strlen(dirent_ptr->d_name);
1803 buffer_size += entry_len + 1;
1806 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1808 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1809 closedir(directory);
1814 sent->name_buffer = g_new(gchar, buffer_size);
1815 sent->entries = g_new(CompletionDirEntry, entry_count);
1816 sent->entry_count = entry_count;
1818 buffer_ptr = sent->name_buffer;
1820 rewinddir(directory);
1822 for(i = 0; i < entry_count; i += 1)
1824 dirent_ptr = readdir(directory);
1829 closedir(directory);
1833 strcpy(buffer_ptr, dirent_ptr->d_name);
1834 sent->entries[i].entry_name = buffer_ptr;
1835 buffer_ptr += strlen(dirent_ptr->d_name);
1839 path_buf[path_buf_len] = '/';
1840 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1842 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1843 sent->entries[i].is_dir = 1;
1845 /* stat may fail, and we don't mind, since it could be a
1846 * dangling symlink. */
1847 sent->entries[i].is_dir = 0;
1850 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1852 closedir(directory);
1857 /* open a directory by absolute pathname */
1858 static CompletionDir*
1859 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1862 CompletionDirSent *sent;
1865 if(stat(dir_name, &sbuf) < 0)
1871 cdsl = cmpl_state->directory_sent_storage;
1877 if(sent->inode == sbuf.st_ino &&
1878 sent->mtime == sbuf.st_mtime)
1879 return attach_dir(sent, dir_name, cmpl_state);
1884 sent = open_new_dir(dir_name, &sbuf);
1887 cmpl_state->directory_sent_storage =
1888 g_list_prepend(cmpl_state->directory_sent_storage, sent);
1890 return attach_dir(sent, dir_name, cmpl_state);
1896 static CompletionDir*
1897 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1899 CompletionDir* new_dir;
1901 new_dir = g_new(CompletionDir, 1);
1903 cmpl_state->directory_storage =
1904 g_list_prepend(cmpl_state->directory_storage, new_dir);
1906 new_dir->sent = sent;
1907 new_dir->fullname = g_strdup(dir_name);
1908 new_dir->fullname_len = strlen(dir_name);
1914 correct_dir_fullname(CompletionDir* cmpl_dir)
1916 gint length = strlen(cmpl_dir->fullname);
1919 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
1923 strcpy(cmpl_dir->fullname, "/");
1924 cmpl_dir->fullname_len = 1;
1927 cmpl_dir->fullname[length - 2] = 0;
1930 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
1931 cmpl_dir->fullname[length - 2] = 0;
1932 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
1936 strcpy(cmpl_dir->fullname, "/");
1937 cmpl_dir->fullname_len = 1;
1941 if(stat(cmpl_dir->fullname, &sbuf) < 0)
1947 cmpl_dir->fullname[length - 2] = 0;
1949 if(!correct_parent(cmpl_dir, &sbuf))
1952 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
1956 strcpy(cmpl_dir->fullname, "/");
1957 cmpl_dir->fullname_len = 1;
1961 if(stat(cmpl_dir->fullname, &sbuf) < 0)
1967 cmpl_dir->fullname[length - 3] = 0;
1969 if(!correct_parent(cmpl_dir, &sbuf))
1973 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
1979 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
1986 last_slash = strrchr(cmpl_dir->fullname, '/');
1988 g_assert(last_slash);
1990 if(last_slash != cmpl_dir->fullname)
1991 { /* last_slash[0] = 0; */ }
1998 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2004 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2005 /* it wasn't a link */
2011 last_slash[0] = '/'; */
2013 /* it was a link, have to figure it out the hard way */
2015 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2020 g_free(cmpl_dir->fullname);
2022 cmpl_dir->fullname = new_name;
2028 find_parent_dir_fullname(gchar* dirname)
2030 gchar buffer[MAXPATHLEN];
2031 gchar buffer2[MAXPATHLEN];
2033 if(!getcwd(buffer, MAXPATHLEN))
2039 if(chdir(dirname) != 0 || chdir("..") != 0)
2045 if(!getcwd(buffer2, MAXPATHLEN))
2053 if(chdir(buffer) != 0)
2059 return g_strdup(buffer2);
2062 /**********************************************************************/
2063 /* Completion Operations */
2064 /**********************************************************************/
2066 static PossibleCompletion*
2067 attempt_homedir_completion(gchar* text_to_complete,
2068 CompletionState *cmpl_state)
2072 if (!cmpl_state->user_dir_name_buffer &&
2073 !get_pwdb(cmpl_state))
2075 length = strlen(text_to_complete) - 1;
2077 cmpl_state->user_completion_index += 1;
2079 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2081 index = first_diff_index(text_to_complete + 1,
2082 cmpl_state->user_directories
2083 [cmpl_state->user_completion_index].login);
2090 if(cmpl_state->last_valid_char < (index + 1))
2091 cmpl_state->last_valid_char = index + 1;
2092 cmpl_state->user_completion_index += 1;
2096 cmpl_state->the_completion.is_a_completion = 1;
2097 cmpl_state->the_completion.is_directory = 1;
2099 append_completion_text("~", cmpl_state);
2101 append_completion_text(cmpl_state->
2102 user_directories[cmpl_state->user_completion_index].login,
2105 return append_completion_text("/", cmpl_state);
2108 if(text_to_complete[1] ||
2109 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2111 cmpl_state->user_completion_index = -1;
2116 cmpl_state->user_completion_index += 1;
2117 cmpl_state->the_completion.is_a_completion = 1;
2118 cmpl_state->the_completion.is_directory = 1;
2120 return append_completion_text("~/", cmpl_state);
2124 /* returns the index (>= 0) of the first differing character,
2125 * PATTERN_MATCH if the completion matches */
2127 first_diff_index(gchar* pat, gchar* text)
2131 while(*pat && *text && *text == *pat)
2141 return PATTERN_MATCH;
2144 static PossibleCompletion*
2145 append_completion_text(gchar* text, CompletionState* cmpl_state)
2149 if(!cmpl_state->the_completion.text)
2152 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2154 if(cmpl_state->the_completion.text_alloc > len)
2156 strcat(cmpl_state->the_completion.text, text);
2157 return &cmpl_state->the_completion;
2160 while(i < len) { i <<= 1; }
2162 cmpl_state->the_completion.text_alloc = i;
2164 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2166 if(!cmpl_state->the_completion.text)
2170 strcat(cmpl_state->the_completion.text, text);
2171 return &cmpl_state->the_completion;
2175 static CompletionDir*
2176 find_completion_dir(gchar* text_to_complete,
2177 gchar** remaining_text,
2178 CompletionState* cmpl_state)
2180 gchar* first_slash = strchr(text_to_complete, '/');
2181 CompletionDir* dir = cmpl_state->reference_dir;
2182 *remaining_text = text_to_complete;
2186 gint len = first_slash - *remaining_text;
2188 gint found_index = -1;
2190 gchar* pat_buf = g_new (gchar, len + 1);
2192 strncpy(pat_buf, *remaining_text, len);
2195 for(i = 0; i < dir->sent->entry_count; i += 1)
2197 if(dir->sent->entries[i].is_dir &&
2198 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2199 FNMATCH_FLAGS)!= FNM_NOMATCH)
2216 CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
2225 next->cmpl_parent = dir;
2229 if(!correct_dir_fullname(dir))
2235 *remaining_text = first_slash + 1;
2236 first_slash = strchr(*remaining_text, '/');
2251 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
2255 if(!poss || !cmpl_is_a_completion(poss))
2258 cmpl_len = strlen(cmpl_this_completion(poss));
2260 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
2262 cmpl_state->updated_text =
2263 (gchar*)g_realloc(cmpl_state->updated_text,
2264 cmpl_state->updated_text_alloc);
2265 cmpl_state->updated_text_alloc = 2*cmpl_len;
2268 if(cmpl_state->updated_text_len < 0)
2270 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2271 cmpl_state->updated_text_len = cmpl_len;
2272 cmpl_state->re_complete = cmpl_is_directory(poss);
2274 else if(cmpl_state->updated_text_len == 0)
2276 cmpl_state->re_complete = FALSE;
2281 first_diff_index(cmpl_state->updated_text,
2282 cmpl_this_completion(poss));
2284 cmpl_state->re_complete = FALSE;
2286 if(first_diff == PATTERN_MATCH)
2289 if(first_diff > cmpl_state->updated_text_len)
2290 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2292 cmpl_state->updated_text_len = first_diff;
2293 cmpl_state->updated_text[first_diff] = 0;
2297 static PossibleCompletion*
2298 attempt_file_completion(CompletionState *cmpl_state)
2300 gchar *pat_buf, *first_slash;
2301 CompletionDir *dir = cmpl_state->active_completion_dir;
2303 dir->cmpl_index += 1;
2305 if(dir->cmpl_index == dir->sent->entry_count)
2307 if(dir->cmpl_parent == NULL)
2309 cmpl_state->active_completion_dir = NULL;
2315 cmpl_state->active_completion_dir = dir->cmpl_parent;
2317 return attempt_file_completion(cmpl_state);
2321 g_assert(dir->cmpl_text);
2323 first_slash = strchr(dir->cmpl_text, '/');
2327 gint len = first_slash - dir->cmpl_text;
2329 pat_buf = g_new (gchar, len + 1);
2330 strncpy(pat_buf, dir->cmpl_text, len);
2335 gint len = strlen(dir->cmpl_text);
2337 pat_buf = g_new (gchar, len + 2);
2338 strcpy(pat_buf, dir->cmpl_text);
2339 strcpy(pat_buf + len, "*");
2344 if(dir->sent->entries[dir->cmpl_index].is_dir)
2346 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2347 FNMATCH_FLAGS) != FNM_NOMATCH)
2349 CompletionDir* new_dir;
2351 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
2360 new_dir->cmpl_parent = dir;
2362 new_dir->cmpl_index = -1;
2363 new_dir->cmpl_text = first_slash + 1;
2365 cmpl_state->active_completion_dir = new_dir;
2368 return attempt_file_completion(cmpl_state);
2373 return attempt_file_completion(cmpl_state);
2379 return attempt_file_completion(cmpl_state);
2384 if(dir->cmpl_parent != NULL)
2386 append_completion_text(dir->fullname +
2387 strlen(cmpl_state->completion_dir->fullname) + 1,
2389 append_completion_text("/", cmpl_state);
2392 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2394 cmpl_state->the_completion.is_a_completion =
2395 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2396 FNMATCH_FLAGS) != FNM_NOMATCH);
2398 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2399 if(dir->sent->entries[dir->cmpl_index].is_dir)
2400 append_completion_text("/", cmpl_state);
2403 return &cmpl_state->the_completion;
2409 get_pwdb(CompletionState* cmpl_state)
2411 struct passwd *pwd_ptr;
2412 gchar* buf_ptr, *home_dir = NULL;
2413 gint len = 0, i, count = 0;
2415 if(cmpl_state->user_dir_name_buffer)
2419 while ((pwd_ptr = getpwent()) != NULL)
2421 len += strlen(pwd_ptr->pw_name);
2422 len += strlen(pwd_ptr->pw_dir);
2427 if (!cmpl_state->user_home_dir)
2429 /* the loser doesn't have $HOME set */
2432 pwd_ptr = getpwuid(getuid());
2438 home_dir = pwd_ptr->pw_dir;
2440 len += strlen(home_dir);
2446 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2447 cmpl_state->user_directories = g_new(CompletionUserDir, count);
2448 cmpl_state->user_directories_len = count;
2450 buf_ptr = cmpl_state->user_dir_name_buffer;
2452 if (!cmpl_state->user_home_dir)
2454 strcpy(buf_ptr, home_dir);
2455 cmpl_state->user_home_dir = buf_ptr;
2456 buf_ptr += strlen(buf_ptr);
2460 for(i = 0; i < count; i += 1)
2462 pwd_ptr = getpwent();
2469 strcpy(buf_ptr, pwd_ptr->pw_name);
2470 cmpl_state->user_directories[i].login = buf_ptr;
2471 buf_ptr += strlen(buf_ptr);
2473 strcpy(buf_ptr, pwd_ptr->pw_dir);
2474 cmpl_state->user_directories[i].homedir = buf_ptr;
2475 buf_ptr += strlen(buf_ptr);
2479 qsort(cmpl_state->user_directories,
2480 cmpl_state->user_directories_len,
2481 sizeof(CompletionUserDir),
2490 if(cmpl_state->user_dir_name_buffer)
2491 g_free(cmpl_state->user_dir_name_buffer);
2492 if(cmpl_state->user_directories)
2493 g_free(cmpl_state->user_directories);
2495 cmpl_state->user_dir_name_buffer = NULL;
2496 cmpl_state->user_directories = NULL;
2502 compare_user_dir(const void* a, const void* b)
2504 return strcmp((((CompletionUserDir*)a))->login,
2505 (((CompletionUserDir*)b))->login);
2509 compare_cmpl_dir(const void* a, const void* b)
2511 return strcmp((((CompletionDirEntry*)a))->entry_name,
2512 (((CompletionDirEntry*)b))->entry_name);
2516 cmpl_state_okay(CompletionState* cmpl_state)
2518 return cmpl_state && cmpl_state->reference_dir;
2522 cmpl_strerror(gint err)
2524 if(err == CMPL_ERRNO_TOO_LONG)
2525 return "Name too long";
2527 return g_strerror (err);