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 *confirm_area;
360 GtkWidget *pulldown_hbox;
361 char *dir_title [] = { "Directories", };
362 char *file_title [] = { "Files", };
364 filesel->cmpl_state = cmpl_init_state ();
366 /* The dialog-sized vertical box */
367 filesel->main_vbox = gtk_vbox_new (FALSE, 10);
368 gtk_container_border_width (GTK_CONTAINER (filesel), 10);
369 gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
370 gtk_widget_show (filesel->main_vbox);
372 /* The horizontal box containing create, rename etc. buttons */
373 filesel->button_area = gtk_hbox_new (TRUE, 0);
374 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area,
376 gtk_widget_show (filesel->button_area);
378 gtk_file_selection_show_fileop_buttons(filesel);
380 /* hbox for pulldown menu */
381 pulldown_hbox = gtk_hbox_new (TRUE, 5);
382 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
383 gtk_widget_show (pulldown_hbox);
386 filesel->history_pulldown = gtk_option_menu_new ();
387 gtk_widget_show (filesel->history_pulldown);
388 gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown,
391 /* The horizontal box containing the directory and file listboxes */
392 list_hbox = gtk_hbox_new (FALSE, 5);
393 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
394 gtk_widget_show (list_hbox);
396 /* The directories clist */
397 filesel->dir_list = gtk_clist_new_with_titles (1, dir_title);
398 gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
399 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
400 (GtkSignalFunc) gtk_file_selection_dir_button,
402 gtk_clist_set_policy (GTK_CLIST (filesel->dir_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
403 gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
404 gtk_container_border_width (GTK_CONTAINER (filesel->dir_list), 5);
405 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->dir_list, TRUE, TRUE, 0);
406 gtk_widget_show (filesel->dir_list);
408 /* The files clist */
409 filesel->file_list = gtk_clist_new_with_titles (1, file_title);
410 gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
411 gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
412 (GtkSignalFunc) gtk_file_selection_file_button,
414 gtk_clist_set_policy (GTK_CLIST (filesel->file_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
415 gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
416 gtk_container_border_width (GTK_CONTAINER (filesel->file_list), 5);
417 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->file_list, TRUE, TRUE, 0);
418 gtk_widget_show (filesel->file_list);
420 /* action area for packing buttons into. */
421 filesel->action_area = gtk_hbox_new (TRUE, 0);
422 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area,
424 gtk_widget_show (filesel->action_area);
426 /* The OK/Cancel button area */
427 confirm_area = gtk_hbox_new (TRUE, 10);
428 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0);
429 gtk_widget_show (confirm_area);
432 filesel->ok_button = gtk_button_new_with_label ("OK");
433 GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
434 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0);
435 gtk_widget_grab_default (filesel->ok_button);
436 gtk_widget_show (filesel->ok_button);
438 /* The Cancel button */
439 filesel->cancel_button = gtk_button_new_with_label ("Cancel");
440 GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
441 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0);
442 gtk_widget_show (filesel->cancel_button);
444 /* The selection entry widget */
445 entry_vbox = gtk_vbox_new (FALSE, 2);
446 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
447 gtk_widget_show (entry_vbox);
449 filesel->selection_text = label = gtk_label_new ("");
450 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
451 gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
452 gtk_widget_show (label);
454 filesel->selection_entry = gtk_entry_new ();
455 gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
456 (GtkSignalFunc) gtk_file_selection_key_press, filesel);
457 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
458 (GtkSignalFunc) gtk_widget_grab_default,
459 GTK_OBJECT (filesel->ok_button));
460 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
461 (GtkSignalFunc) gtk_button_clicked,
462 GTK_OBJECT (filesel->ok_button));
463 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
464 gtk_widget_show (filesel->selection_entry);
466 if (!cmpl_state_okay (filesel->cmpl_state))
470 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
472 gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
476 gtk_file_selection_populate (filesel, "", FALSE);
479 gtk_widget_grab_focus (filesel->selection_entry);
483 gtk_file_selection_new (const gchar *title)
485 GtkFileSelection *filesel;
487 filesel = gtk_type_new (gtk_file_selection_get_type ());
488 gtk_window_set_title (GTK_WINDOW (filesel), title);
490 return GTK_WIDGET (filesel);
494 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
496 g_return_if_fail (filesel != NULL);
497 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
499 /* delete, create directory, and rename */
500 if (!filesel->fileop_c_dir)
502 filesel->fileop_c_dir = gtk_button_new_with_label ("Create Dir");
503 gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
504 (GtkSignalFunc) gtk_file_selection_create_dir,
506 gtk_box_pack_start (GTK_BOX (filesel->button_area),
507 filesel->fileop_c_dir, TRUE, TRUE, 0);
508 gtk_widget_show (filesel->fileop_c_dir);
511 if (!filesel->fileop_del_file)
513 filesel->fileop_del_file = gtk_button_new_with_label ("Delete File");
514 gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
515 (GtkSignalFunc) gtk_file_selection_delete_file,
517 gtk_box_pack_start (GTK_BOX (filesel->button_area),
518 filesel->fileop_del_file, TRUE, TRUE, 0);
519 gtk_widget_show (filesel->fileop_del_file);
522 if (!filesel->fileop_ren_file)
524 filesel->fileop_ren_file = gtk_button_new_with_label ("Rename File");
525 gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
526 (GtkSignalFunc) gtk_file_selection_rename_file,
528 gtk_box_pack_start (GTK_BOX (filesel->button_area),
529 filesel->fileop_ren_file, TRUE, TRUE, 0);
530 gtk_widget_show (filesel->fileop_ren_file);
535 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
537 g_return_if_fail (filesel != NULL);
538 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
540 if (filesel->fileop_ren_file)
542 gtk_widget_destroy (filesel->fileop_ren_file);
543 filesel->fileop_ren_file = NULL;
546 if (filesel->fileop_del_file)
548 gtk_widget_destroy (filesel->fileop_del_file);
549 filesel->fileop_del_file = NULL;
552 if (filesel->fileop_c_dir)
554 gtk_widget_destroy (filesel->fileop_c_dir);
555 filesel->fileop_c_dir = NULL;
562 gtk_file_selection_set_filename (GtkFileSelection *filesel,
563 const gchar *filename)
565 char buf[MAXPATHLEN];
566 const char *name, *last_slash;
568 g_return_if_fail (filesel != NULL);
569 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
570 g_return_if_fail (filename != NULL);
572 last_slash = strrchr (filename, '/');
581 gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
583 strncpy (buf, filename, len);
586 name = last_slash + 1;
589 gtk_file_selection_populate (filesel, buf, FALSE);
591 if (filesel->selection_entry)
592 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
596 gtk_file_selection_get_filename (GtkFileSelection *filesel)
598 static char nothing[2] = "";
602 g_return_val_if_fail (filesel != NULL, nothing);
603 g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
605 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
608 filename = cmpl_completion_fullname (text, filesel->cmpl_state);
616 gtk_file_selection_destroy (GtkObject *object)
618 GtkFileSelection *filesel;
620 HistoryCallbackArg *callback_arg;
622 g_return_if_fail (object != NULL);
623 g_return_if_fail (GTK_IS_FILE_SELECTION (object));
625 filesel = GTK_FILE_SELECTION (object);
627 if (filesel->fileop_dialog)
628 gtk_widget_destroy (filesel->fileop_dialog);
630 if (filesel->history_list)
632 list = filesel->history_list;
635 callback_arg = list->data;
636 g_free (callback_arg->directory);
639 g_list_free (filesel->history_list);
640 filesel->history_list = NULL;
643 cmpl_free_state (filesel->cmpl_state);
644 filesel->cmpl_state = NULL;
646 if (GTK_OBJECT_CLASS (parent_class)->destroy)
647 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
650 /* Begin file operations callbacks */
653 gtk_file_selection_fileop_error (gchar *error_message)
660 g_return_if_fail (error_message != NULL);
663 dialog = gtk_dialog_new ();
665 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
666 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
669 gtk_window_set_title (GTK_WINDOW (dialog), "Error");
670 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
672 vbox = gtk_vbox_new(FALSE, 0);
673 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
674 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
676 gtk_widget_show(vbox);
678 label = gtk_label_new(error_message);
679 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
680 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
681 gtk_widget_show(label);
683 /* yes, we free it */
684 g_free (error_message);
687 button = gtk_button_new_with_label ("Close");
688 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
689 (GtkSignalFunc) gtk_widget_destroy,
691 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
692 button, TRUE, TRUE, 0);
693 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
694 gtk_widget_grab_default(button);
695 gtk_widget_show (button);
697 gtk_widget_show (dialog);
701 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
703 GtkFileSelection *fs = data;
705 g_return_if_fail (fs != NULL);
706 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
708 fs->fileop_dialog = NULL;
713 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
715 GtkFileSelection *fs = data;
720 CompletionState *cmpl_state;
722 g_return_if_fail (fs != NULL);
723 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
725 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
726 cmpl_state = (CompletionState*) fs->cmpl_state;
727 path = cmpl_reference_position (cmpl_state);
729 full_path = g_strconcat (path, "/", dirname, NULL);
730 if ( (mkdir (full_path, 0755) < 0) )
732 buf = g_strconcat ("Error creating directory \"", dirname, "\": ",
733 g_strerror(errno), NULL);
734 gtk_file_selection_fileop_error (buf);
738 gtk_widget_destroy (fs->fileop_dialog);
739 gtk_file_selection_populate (fs, "", FALSE);
743 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
745 GtkFileSelection *fs = data;
751 g_return_if_fail (fs != NULL);
752 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
754 if (fs->fileop_dialog)
758 fs->fileop_dialog = dialog = gtk_dialog_new ();
759 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
760 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
762 gtk_window_set_title (GTK_WINDOW (dialog), "Create Directory");
763 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
764 gtk_widget_show (dialog);
766 vbox = gtk_vbox_new(FALSE, 0);
767 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
768 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
770 gtk_widget_show(vbox);
772 label = gtk_label_new("Directory name:");
773 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
774 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
775 gtk_widget_show(label);
777 /* The directory entry widget */
778 fs->fileop_entry = gtk_entry_new ();
779 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
781 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
782 gtk_widget_show (fs->fileop_entry);
785 button = gtk_button_new_with_label ("Create");
786 gtk_signal_connect (GTK_OBJECT (button), "clicked",
787 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
789 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
790 button, TRUE, TRUE, 0);
791 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
792 gtk_widget_show(button);
794 button = gtk_button_new_with_label ("Cancel");
795 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
796 (GtkSignalFunc) gtk_widget_destroy,
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_grab_default(button);
802 gtk_widget_show (button);
806 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
808 GtkFileSelection *fs = data;
809 CompletionState *cmpl_state;
814 g_return_if_fail (fs != NULL);
815 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
817 cmpl_state = (CompletionState*) fs->cmpl_state;
818 path = cmpl_reference_position (cmpl_state);
820 full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
821 if ( (unlink (full_path) < 0) )
823 buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ",
824 g_strerror(errno), NULL);
825 gtk_file_selection_fileop_error (buf);
829 gtk_widget_destroy (fs->fileop_dialog);
830 gtk_file_selection_populate (fs, "", FALSE);
834 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
836 GtkFileSelection *fs = data;
844 g_return_if_fail (fs != NULL);
845 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
847 if (fs->fileop_dialog)
850 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
851 if (strlen(filename) < 1)
854 fs->fileop_file = filename;
857 fs->fileop_dialog = dialog = gtk_dialog_new ();
858 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
859 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
861 gtk_window_set_title (GTK_WINDOW (dialog), "Delete File");
862 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
864 vbox = gtk_vbox_new(FALSE, 0);
865 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
866 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
868 gtk_widget_show(vbox);
870 buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
871 label = gtk_label_new(buf);
872 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
873 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
874 gtk_widget_show(label);
878 button = gtk_button_new_with_label ("Delete");
879 gtk_signal_connect (GTK_OBJECT (button), "clicked",
880 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
882 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
883 button, TRUE, TRUE, 0);
884 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
885 gtk_widget_show(button);
887 button = gtk_button_new_with_label ("Cancel");
888 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
889 (GtkSignalFunc) gtk_widget_destroy,
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_grab_default(button);
895 gtk_widget_show (button);
897 gtk_widget_show (dialog);
901 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
903 GtkFileSelection *fs = data;
909 CompletionState *cmpl_state;
911 g_return_if_fail (fs != NULL);
912 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
914 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
915 cmpl_state = (CompletionState*) fs->cmpl_state;
916 path = cmpl_reference_position (cmpl_state);
918 new_filename = g_strconcat (path, "/", file, NULL);
919 old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
921 if ( (rename (old_filename, new_filename)) < 0)
923 buf = g_strconcat ("Error renaming file \"", file, "\": ",
924 g_strerror(errno), NULL);
925 gtk_file_selection_fileop_error (buf);
927 g_free (new_filename);
928 g_free (old_filename);
930 gtk_widget_destroy (fs->fileop_dialog);
931 gtk_file_selection_populate (fs, "", FALSE);
935 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
937 GtkFileSelection *fs = data;
944 g_return_if_fail (fs != NULL);
945 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
947 if (fs->fileop_dialog)
950 fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
951 if (strlen(fs->fileop_file) < 1)
955 fs->fileop_dialog = dialog = gtk_dialog_new ();
956 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
957 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
959 gtk_window_set_title (GTK_WINDOW (dialog), "Rename File");
960 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
961 gtk_widget_show (dialog);
963 vbox = gtk_vbox_new(FALSE, 0);
964 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
965 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
967 gtk_widget_show(vbox);
969 buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
970 label = gtk_label_new(buf);
971 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
972 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
973 gtk_widget_show(label);
976 /* New filename entry */
977 fs->fileop_entry = gtk_entry_new ();
978 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
980 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
981 gtk_widget_show (fs->fileop_entry);
983 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
984 gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
985 0, strlen (fs->fileop_file));
988 button = gtk_button_new_with_label ("Rename");
989 gtk_signal_connect (GTK_OBJECT (button), "clicked",
990 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
992 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
993 button, TRUE, TRUE, 0);
994 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
995 gtk_widget_show(button);
997 button = gtk_button_new_with_label ("Cancel");
998 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
999 (GtkSignalFunc) gtk_widget_destroy,
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_grab_default(button);
1005 gtk_widget_show (button);
1010 gtk_file_selection_key_press (GtkWidget *widget,
1014 GtkFileSelection *fs;
1017 g_return_val_if_fail (widget != NULL, FALSE);
1018 g_return_val_if_fail (event != NULL, FALSE);
1020 if (event->keyval == GDK_Tab)
1022 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1024 fs = GTK_FILE_SELECTION (user_data);
1025 text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1026 gtk_file_selection_populate (fs, text, TRUE);
1036 gtk_file_selection_history_callback (GtkWidget *widget, gpointer data)
1038 GtkFileSelection *fs = data;
1039 HistoryCallbackArg *callback_arg;
1042 g_return_if_fail (fs != NULL);
1043 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1045 list = fs->history_list;
1048 callback_arg = list->data;
1050 if (callback_arg->menu_item == widget)
1052 gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1061 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1062 gchar *current_directory)
1064 HistoryCallbackArg *callback_arg;
1065 GtkWidget *menu_item;
1072 g_return_if_fail (fs != NULL);
1073 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1074 g_return_if_fail (current_directory != NULL);
1076 list = fs->history_list;
1078 if (fs->history_menu)
1081 callback_arg = list->data;
1082 g_free (callback_arg->directory);
1085 g_list_free (fs->history_list);
1086 fs->history_list = NULL;
1088 gtk_widget_destroy (fs->history_menu);
1091 fs->history_menu = gtk_menu_new();
1093 current_dir = g_strdup (current_directory);
1095 dir_len = strlen (current_dir);
1097 for (i = dir_len; i >= 0; i--)
1099 /* the i == dir_len is to catch the full path for the first
1101 if ( (current_dir[i] == '/') || (i == dir_len))
1103 /* another small hack to catch the full path */
1105 current_dir[i + 1] = '\0';
1106 menu_item = gtk_menu_item_new_with_label (current_dir);
1107 directory = g_strdup (current_dir);
1109 callback_arg = g_new (HistoryCallbackArg, 1);
1110 callback_arg->menu_item = menu_item;
1112 /* since the autocompletion gets confused if you don't
1113 * supply a trailing '/' on a dir entry, set the full
1114 * (current) path to "" which just refreshes the filesel */
1116 callback_arg->directory = g_strdup ("");
1118 callback_arg->directory = directory;
1121 fs->history_list = g_list_append (fs->history_list, callback_arg);
1123 gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1124 (GtkSignalFunc) gtk_file_selection_history_callback,
1126 gtk_menu_append (GTK_MENU (fs->history_menu), menu_item);
1127 gtk_widget_show (menu_item);
1131 gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown),
1133 g_free (current_dir);
1137 gtk_file_selection_file_button (GtkWidget *widget,
1140 GdkEventButton *bevent,
1143 GtkFileSelection *fs = NULL;
1146 g_return_if_fail (GTK_IS_CLIST (widget));
1149 g_return_if_fail (fs != NULL);
1150 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1152 filename = gtk_clist_get_row_data (GTK_CLIST (fs->file_list), row);
1154 if (bevent && filename)
1156 switch (bevent->type)
1158 case GDK_BUTTON_PRESS:
1159 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1162 case GDK_2BUTTON_PRESS:
1163 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1173 gtk_file_selection_dir_button (GtkWidget *widget,
1176 GdkEventButton *bevent,
1179 GtkFileSelection *fs = NULL;
1182 g_return_if_fail (GTK_IS_CLIST (widget));
1184 fs = GTK_FILE_SELECTION (user_data);
1185 g_return_if_fail (fs != NULL);
1186 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1188 filename = gtk_clist_get_row_data (GTK_CLIST (fs->dir_list), row);
1190 if (bevent && filename) {
1192 switch (bevent->type)
1194 case GDK_BUTTON_PRESS:
1195 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1198 case GDK_2BUTTON_PRESS:
1199 gtk_file_selection_populate (fs, filename, FALSE);
1209 gtk_file_selection_populate (GtkFileSelection *fs,
1213 CompletionState *cmpl_state;
1214 PossibleCompletion* poss;
1217 gchar* rem_path = rel_path;
1220 gint did_recurse = FALSE;
1221 gint possible_count = 0;
1222 gint selection_index = -1;
1224 g_return_if_fail (fs != NULL);
1225 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1227 cmpl_state = (CompletionState*) fs->cmpl_state;
1228 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1230 if (!cmpl_state_okay (cmpl_state))
1232 /* Something went wrong. */
1233 gtk_file_selection_abort (fs);
1237 g_assert (cmpl_state->reference_dir);
1239 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1240 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1241 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1242 gtk_clist_clear (GTK_CLIST (fs->file_list));
1244 /* Set the dir_list to include ./ and ../ */
1247 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1248 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row, "./");
1251 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1252 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row, "../");
1256 if (cmpl_is_a_completion (poss))
1258 possible_count += 1;
1260 filename = g_strdup (cmpl_this_completion (poss));
1264 if (cmpl_is_directory (poss))
1266 if (strcmp (filename, "./") != 0 &&
1267 strcmp (filename, "../") != 0)
1269 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1270 gtk_clist_set_row_data_full (GTK_CLIST (fs->dir_list), row,
1271 filename, (GtkDestroyNotify) g_free);
1276 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1277 gtk_clist_set_row_data_full (GTK_CLIST (fs->file_list), row,
1278 filename ,(GtkDestroyNotify) g_free);
1282 poss = cmpl_next_completion (cmpl_state);
1285 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1286 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1288 /* File lists are set. */
1290 g_assert (cmpl_state->reference_dir);
1295 /* User is trying to complete filenames, so advance the user's input
1296 * string to the updated_text, which is the common leading substring
1297 * of all possible completions, and if its a directory attempt
1298 * attempt completions in it. */
1300 if (cmpl_updated_text (cmpl_state)[0])
1303 if (cmpl_updated_dir (cmpl_state))
1305 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1309 gtk_file_selection_populate (fs, dir_name, TRUE);
1315 if (fs->selection_entry)
1316 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1317 cmpl_updated_text (cmpl_state));
1322 selection_index = cmpl_last_valid_char (cmpl_state) -
1323 (strlen (rel_path) - strlen (rem_path));
1324 if (fs->selection_entry)
1325 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1330 if (fs->selection_entry)
1331 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1336 if (fs->selection_entry)
1337 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1339 if (fs->selection_entry)
1341 sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
1342 sizeof ("Selection: "));
1343 strcpy (sel_text, "Selection: ");
1344 strcat (sel_text, cmpl_reference_position (cmpl_state));
1346 gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
1350 if (fs->history_pulldown)
1352 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1359 gtk_file_selection_abort (GtkFileSelection *fs)
1363 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
1365 /* BEEP gdk_beep(); */
1367 if (fs->selection_entry)
1368 gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
1371 /**********************************************************************/
1372 /* External Interface */
1373 /**********************************************************************/
1375 /* The four completion state selectors
1378 cmpl_updated_text (CompletionState* cmpl_state)
1380 return cmpl_state->updated_text;
1384 cmpl_updated_dir (CompletionState* cmpl_state)
1386 return cmpl_state->re_complete;
1390 cmpl_reference_position (CompletionState* cmpl_state)
1392 return cmpl_state->reference_dir->fullname;
1396 cmpl_last_valid_char (CompletionState* cmpl_state)
1398 return cmpl_state->last_valid_char;
1402 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1406 strcpy (cmpl_state->updated_text, text);
1408 else if (text[0] == '~')
1413 dir = open_user_dir (text, cmpl_state);
1417 /* spencer says just return ~something, so
1418 * for now just do it. */
1419 strcpy (cmpl_state->updated_text, text);
1424 strcpy (cmpl_state->updated_text, dir->fullname);
1426 slash = strchr (text, '/');
1429 strcat (cmpl_state->updated_text, slash);
1434 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1435 strcat (cmpl_state->updated_text, "/");
1436 strcat (cmpl_state->updated_text, text);
1439 return cmpl_state->updated_text;
1442 /* The three completion selectors
1445 cmpl_this_completion (PossibleCompletion* pc)
1451 cmpl_is_directory (PossibleCompletion* pc)
1453 return pc->is_directory;
1457 cmpl_is_a_completion (PossibleCompletion* pc)
1459 return pc->is_a_completion;
1462 /**********************************************************************/
1463 /* Construction, deletion */
1464 /**********************************************************************/
1466 static CompletionState*
1467 cmpl_init_state (void)
1469 gchar getcwd_buf[2*MAXPATHLEN];
1470 CompletionState *new_state;
1472 new_state = g_new (CompletionState, 1);
1474 if (!getcwd (getcwd_buf, MAXPATHLEN))
1480 new_state->reference_dir = NULL;
1481 new_state->completion_dir = NULL;
1482 new_state->active_completion_dir = NULL;
1484 if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1486 /* if this fails, get_pwdb will fill it in. */
1487 new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1490 new_state->directory_storage = NULL;
1491 new_state->directory_sent_storage = NULL;
1492 new_state->last_valid_char = 0;
1493 new_state->updated_text = g_new (gchar, MAXPATHLEN);
1494 new_state->updated_text_alloc = MAXPATHLEN;
1495 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1496 new_state->the_completion.text_alloc = MAXPATHLEN;
1497 new_state->user_dir_name_buffer = NULL;
1498 new_state->user_directories = NULL;
1500 new_state->reference_dir = open_dir (getcwd_buf, new_state);
1502 if (!new_state->reference_dir)
1509 cmpl_free_dir_list(GList* dp0)
1514 free_dir (dp->data);
1522 cmpl_free_dir_sent_list(GList* dp0)
1527 free_dir_sent (dp->data);
1535 cmpl_free_state (CompletionState* cmpl_state)
1537 cmpl_free_dir_list (cmpl_state->directory_storage);
1538 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
1540 if (cmpl_state->user_dir_name_buffer)
1541 g_free (cmpl_state->user_dir_name_buffer);
1542 if (cmpl_state->user_directories)
1543 g_free (cmpl_state->user_directories);
1544 if (cmpl_state->the_completion.text)
1545 g_free (cmpl_state->the_completion.text);
1546 if (cmpl_state->updated_text)
1547 g_free (cmpl_state->updated_text);
1549 g_free (cmpl_state);
1553 free_dir(CompletionDir* dir)
1555 g_free(dir->fullname);
1560 free_dir_sent(CompletionDirSent* sent)
1562 g_free(sent->name_buffer);
1563 g_free(sent->entries);
1568 prune_memory_usage(CompletionState *cmpl_state)
1570 GList* cdsl = cmpl_state->directory_sent_storage;
1571 GList* cdl = cmpl_state->directory_storage;
1575 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1579 cmpl_free_dir_sent_list(cdsl->next);
1583 cmpl_state->directory_storage = NULL;
1585 if (cdl->data == cmpl_state->reference_dir)
1586 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1588 free_dir (cdl->data);
1595 /**********************************************************************/
1596 /* The main entrances. */
1597 /**********************************************************************/
1599 static PossibleCompletion*
1600 cmpl_completion_matches (gchar* text_to_complete,
1601 gchar** remaining_text,
1602 CompletionState* cmpl_state)
1605 PossibleCompletion *poss;
1607 prune_memory_usage(cmpl_state);
1609 g_assert(text_to_complete);
1611 cmpl_state->user_completion_index = -1;
1612 cmpl_state->last_completion_text = text_to_complete;
1613 cmpl_state->the_completion.text[0] = 0;
1614 cmpl_state->last_valid_char = 0;
1615 cmpl_state->updated_text_len = -1;
1616 cmpl_state->updated_text[0] = 0;
1617 cmpl_state->re_complete = FALSE;
1619 first_slash = strchr(text_to_complete, '/');
1621 if(text_to_complete[0] == '~' && !first_slash)
1623 /* Text starts with ~ and there is no slash, show all the
1624 * home directory completions.
1626 poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1628 update_cmpl(poss, cmpl_state);
1633 cmpl_state->reference_dir =
1634 open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1636 if(!cmpl_state->reference_dir)
1639 cmpl_state->completion_dir =
1640 find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1642 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1644 if(!cmpl_state->completion_dir)
1647 cmpl_state->completion_dir->cmpl_index = -1;
1648 cmpl_state->completion_dir->cmpl_parent = NULL;
1649 cmpl_state->completion_dir->cmpl_text = *remaining_text;
1651 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1653 cmpl_state->reference_dir = cmpl_state->completion_dir;
1655 poss = attempt_file_completion(cmpl_state);
1657 update_cmpl(poss, cmpl_state);
1662 static PossibleCompletion*
1663 cmpl_next_completion (CompletionState* cmpl_state)
1665 PossibleCompletion* poss = NULL;
1667 cmpl_state->the_completion.text[0] = 0;
1669 if(cmpl_state->user_completion_index >= 0)
1670 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1672 poss = attempt_file_completion(cmpl_state);
1674 update_cmpl(poss, cmpl_state);
1679 /**********************************************************************/
1680 /* Directory Operations */
1681 /**********************************************************************/
1683 /* Open the directory where completion will begin from, if possible. */
1684 static CompletionDir*
1685 open_ref_dir(gchar* text_to_complete,
1686 gchar** remaining_text,
1687 CompletionState* cmpl_state)
1690 CompletionDir *new_dir;
1692 first_slash = strchr(text_to_complete, '/');
1694 if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1696 new_dir = open_dir("/", cmpl_state);
1699 *remaining_text = text_to_complete + 1;
1701 else if (text_to_complete[0] == '~')
1703 new_dir = open_user_dir(text_to_complete, cmpl_state);
1708 *remaining_text = first_slash + 1;
1710 *remaining_text = text_to_complete + strlen(text_to_complete);
1719 *remaining_text = text_to_complete;
1721 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1726 new_dir->cmpl_index = -1;
1727 new_dir->cmpl_parent = NULL;
1733 /* open a directory by user name */
1734 static CompletionDir*
1735 open_user_dir(gchar* text_to_complete,
1736 CompletionState *cmpl_state)
1741 g_assert(text_to_complete && text_to_complete[0] == '~');
1743 first_slash = strchr(text_to_complete, '/');
1746 cmp_len = first_slash - text_to_complete - 1;
1748 cmp_len = strlen(text_to_complete + 1);
1753 if (!cmpl_state->user_home_dir &&
1754 !get_pwdb(cmpl_state))
1756 return open_dir(cmpl_state->user_home_dir, cmpl_state);
1761 char* copy = g_new(char, cmp_len + 1);
1763 strncpy(copy, text_to_complete + 1, cmp_len);
1765 pwd = getpwnam(copy);
1773 return open_dir(pwd->pw_dir, cmpl_state);
1777 /* open a directory relative the the current relative directory */
1778 static CompletionDir*
1779 open_relative_dir(gchar* dir_name,
1781 CompletionState *cmpl_state)
1783 gchar path_buf[2*MAXPATHLEN];
1785 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1787 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1791 strcpy(path_buf, dir->fullname);
1793 if(dir->fullname_len > 1)
1795 path_buf[dir->fullname_len] = '/';
1796 strcpy(path_buf + dir->fullname_len + 1, dir_name);
1800 strcpy(path_buf + dir->fullname_len, dir_name);
1803 return open_dir(path_buf, cmpl_state);
1806 /* after the cache lookup fails, really open a new directory */
1807 static CompletionDirSent*
1808 open_new_dir(gchar* dir_name, struct stat* sbuf)
1810 CompletionDirSent* sent;
1813 struct dirent *dirent_ptr;
1814 gint buffer_size = 0;
1815 gint entry_count = 0;
1817 struct stat ent_sbuf;
1818 char path_buf[MAXPATHLEN*2];
1821 sent = g_new(CompletionDirSent, 1);
1822 sent->mtime = sbuf->st_mtime;
1823 sent->inode = sbuf->st_ino;
1825 path_buf_len = strlen(dir_name);
1827 if (path_buf_len > MAXPATHLEN)
1829 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1833 strcpy(path_buf, dir_name);
1835 directory = opendir(dir_name);
1843 while((dirent_ptr = readdir(directory)) != NULL)
1845 int entry_len = strlen(dirent_ptr->d_name);
1846 buffer_size += entry_len + 1;
1849 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1851 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1852 closedir(directory);
1857 sent->name_buffer = g_new(gchar, buffer_size);
1858 sent->entries = g_new(CompletionDirEntry, entry_count);
1859 sent->entry_count = entry_count;
1861 buffer_ptr = sent->name_buffer;
1863 rewinddir(directory);
1865 for(i = 0; i < entry_count; i += 1)
1867 dirent_ptr = readdir(directory);
1872 closedir(directory);
1876 strcpy(buffer_ptr, dirent_ptr->d_name);
1877 sent->entries[i].entry_name = buffer_ptr;
1878 buffer_ptr += strlen(dirent_ptr->d_name);
1882 path_buf[path_buf_len] = '/';
1883 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1885 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1886 sent->entries[i].is_dir = 1;
1888 /* stat may fail, and we don't mind, since it could be a
1889 * dangling symlink. */
1890 sent->entries[i].is_dir = 0;
1893 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1895 closedir(directory);
1900 /* open a directory by absolute pathname */
1901 static CompletionDir*
1902 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1905 CompletionDirSent *sent;
1908 if(stat(dir_name, &sbuf) < 0)
1914 cdsl = cmpl_state->directory_sent_storage;
1920 if(sent->inode == sbuf.st_ino &&
1921 sent->mtime == sbuf.st_mtime)
1922 return attach_dir(sent, dir_name, cmpl_state);
1927 sent = open_new_dir(dir_name, &sbuf);
1930 cmpl_state->directory_sent_storage =
1931 g_list_prepend(cmpl_state->directory_sent_storage, sent);
1933 return attach_dir(sent, dir_name, cmpl_state);
1939 static CompletionDir*
1940 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1942 CompletionDir* new_dir;
1944 new_dir = g_new(CompletionDir, 1);
1946 cmpl_state->directory_storage =
1947 g_list_prepend(cmpl_state->directory_storage, new_dir);
1949 new_dir->sent = sent;
1950 new_dir->fullname = g_strdup(dir_name);
1951 new_dir->fullname_len = strlen(dir_name);
1957 correct_dir_fullname(CompletionDir* cmpl_dir)
1959 gint length = strlen(cmpl_dir->fullname);
1962 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
1966 strcpy(cmpl_dir->fullname, "/");
1967 cmpl_dir->fullname_len = 1;
1970 cmpl_dir->fullname[length - 2] = 0;
1973 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
1974 cmpl_dir->fullname[length - 2] = 0;
1975 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
1979 strcpy(cmpl_dir->fullname, "/");
1980 cmpl_dir->fullname_len = 1;
1984 if(stat(cmpl_dir->fullname, &sbuf) < 0)
1990 cmpl_dir->fullname[length - 2] = 0;
1992 if(!correct_parent(cmpl_dir, &sbuf))
1995 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
1999 strcpy(cmpl_dir->fullname, "/");
2000 cmpl_dir->fullname_len = 1;
2004 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2010 cmpl_dir->fullname[length - 3] = 0;
2012 if(!correct_parent(cmpl_dir, &sbuf))
2016 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
2022 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
2029 last_slash = strrchr(cmpl_dir->fullname, '/');
2031 g_assert(last_slash);
2033 if(last_slash != cmpl_dir->fullname)
2034 { /* last_slash[0] = 0; */ }
2041 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2047 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2048 /* it wasn't a link */
2054 last_slash[0] = '/'; */
2056 /* it was a link, have to figure it out the hard way */
2058 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2063 g_free(cmpl_dir->fullname);
2065 cmpl_dir->fullname = new_name;
2071 find_parent_dir_fullname(gchar* dirname)
2073 gchar buffer[MAXPATHLEN];
2074 gchar buffer2[MAXPATHLEN];
2076 if(!getcwd(buffer, MAXPATHLEN))
2082 if(chdir(dirname) != 0 || chdir("..") != 0)
2088 if(!getcwd(buffer2, MAXPATHLEN))
2096 if(chdir(buffer) != 0)
2102 return g_strdup(buffer2);
2105 /**********************************************************************/
2106 /* Completion Operations */
2107 /**********************************************************************/
2109 static PossibleCompletion*
2110 attempt_homedir_completion(gchar* text_to_complete,
2111 CompletionState *cmpl_state)
2115 if (!cmpl_state->user_dir_name_buffer &&
2116 !get_pwdb(cmpl_state))
2118 length = strlen(text_to_complete) - 1;
2120 cmpl_state->user_completion_index += 1;
2122 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2124 index = first_diff_index(text_to_complete + 1,
2125 cmpl_state->user_directories
2126 [cmpl_state->user_completion_index].login);
2133 if(cmpl_state->last_valid_char < (index + 1))
2134 cmpl_state->last_valid_char = index + 1;
2135 cmpl_state->user_completion_index += 1;
2139 cmpl_state->the_completion.is_a_completion = 1;
2140 cmpl_state->the_completion.is_directory = 1;
2142 append_completion_text("~", cmpl_state);
2144 append_completion_text(cmpl_state->
2145 user_directories[cmpl_state->user_completion_index].login,
2148 return append_completion_text("/", cmpl_state);
2151 if(text_to_complete[1] ||
2152 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2154 cmpl_state->user_completion_index = -1;
2159 cmpl_state->user_completion_index += 1;
2160 cmpl_state->the_completion.is_a_completion = 1;
2161 cmpl_state->the_completion.is_directory = 1;
2163 return append_completion_text("~/", cmpl_state);
2167 /* returns the index (>= 0) of the first differing character,
2168 * PATTERN_MATCH if the completion matches */
2170 first_diff_index(gchar* pat, gchar* text)
2174 while(*pat && *text && *text == *pat)
2184 return PATTERN_MATCH;
2187 static PossibleCompletion*
2188 append_completion_text(gchar* text, CompletionState* cmpl_state)
2192 if(!cmpl_state->the_completion.text)
2195 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2197 if(cmpl_state->the_completion.text_alloc > len)
2199 strcat(cmpl_state->the_completion.text, text);
2200 return &cmpl_state->the_completion;
2203 while(i < len) { i <<= 1; }
2205 cmpl_state->the_completion.text_alloc = i;
2207 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2209 if(!cmpl_state->the_completion.text)
2213 strcat(cmpl_state->the_completion.text, text);
2214 return &cmpl_state->the_completion;
2218 static CompletionDir*
2219 find_completion_dir(gchar* text_to_complete,
2220 gchar** remaining_text,
2221 CompletionState* cmpl_state)
2223 gchar* first_slash = strchr(text_to_complete, '/');
2224 CompletionDir* dir = cmpl_state->reference_dir;
2225 *remaining_text = text_to_complete;
2229 gint len = first_slash - *remaining_text;
2231 gint found_index = -1;
2233 gchar* pat_buf = g_new (gchar, len + 1);
2235 strncpy(pat_buf, *remaining_text, len);
2238 for(i = 0; i < dir->sent->entry_count; i += 1)
2240 if(dir->sent->entries[i].is_dir &&
2241 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2242 FNMATCH_FLAGS)!= FNM_NOMATCH)
2259 CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
2268 next->cmpl_parent = dir;
2272 if(!correct_dir_fullname(dir))
2278 *remaining_text = first_slash + 1;
2279 first_slash = strchr(*remaining_text, '/');
2294 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
2298 if(!poss || !cmpl_is_a_completion(poss))
2301 cmpl_len = strlen(cmpl_this_completion(poss));
2303 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
2305 cmpl_state->updated_text =
2306 (gchar*)g_realloc(cmpl_state->updated_text,
2307 cmpl_state->updated_text_alloc);
2308 cmpl_state->updated_text_alloc = 2*cmpl_len;
2311 if(cmpl_state->updated_text_len < 0)
2313 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2314 cmpl_state->updated_text_len = cmpl_len;
2315 cmpl_state->re_complete = cmpl_is_directory(poss);
2317 else if(cmpl_state->updated_text_len == 0)
2319 cmpl_state->re_complete = FALSE;
2324 first_diff_index(cmpl_state->updated_text,
2325 cmpl_this_completion(poss));
2327 cmpl_state->re_complete = FALSE;
2329 if(first_diff == PATTERN_MATCH)
2332 if(first_diff > cmpl_state->updated_text_len)
2333 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2335 cmpl_state->updated_text_len = first_diff;
2336 cmpl_state->updated_text[first_diff] = 0;
2340 static PossibleCompletion*
2341 attempt_file_completion(CompletionState *cmpl_state)
2343 gchar *pat_buf, *first_slash;
2344 CompletionDir *dir = cmpl_state->active_completion_dir;
2346 dir->cmpl_index += 1;
2348 if(dir->cmpl_index == dir->sent->entry_count)
2350 if(dir->cmpl_parent == NULL)
2352 cmpl_state->active_completion_dir = NULL;
2358 cmpl_state->active_completion_dir = dir->cmpl_parent;
2360 return attempt_file_completion(cmpl_state);
2364 g_assert(dir->cmpl_text);
2366 first_slash = strchr(dir->cmpl_text, '/');
2370 gint len = first_slash - dir->cmpl_text;
2372 pat_buf = g_new (gchar, len + 1);
2373 strncpy(pat_buf, dir->cmpl_text, len);
2378 gint len = strlen(dir->cmpl_text);
2380 pat_buf = g_new (gchar, len + 2);
2381 strcpy(pat_buf, dir->cmpl_text);
2382 strcpy(pat_buf + len, "*");
2387 if(dir->sent->entries[dir->cmpl_index].is_dir)
2389 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2390 FNMATCH_FLAGS) != FNM_NOMATCH)
2392 CompletionDir* new_dir;
2394 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
2403 new_dir->cmpl_parent = dir;
2405 new_dir->cmpl_index = -1;
2406 new_dir->cmpl_text = first_slash + 1;
2408 cmpl_state->active_completion_dir = new_dir;
2411 return attempt_file_completion(cmpl_state);
2416 return attempt_file_completion(cmpl_state);
2422 return attempt_file_completion(cmpl_state);
2427 if(dir->cmpl_parent != NULL)
2429 append_completion_text(dir->fullname +
2430 strlen(cmpl_state->completion_dir->fullname) + 1,
2432 append_completion_text("/", cmpl_state);
2435 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2437 cmpl_state->the_completion.is_a_completion =
2438 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2439 FNMATCH_FLAGS) != FNM_NOMATCH);
2441 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2442 if(dir->sent->entries[dir->cmpl_index].is_dir)
2443 append_completion_text("/", cmpl_state);
2446 return &cmpl_state->the_completion;
2452 get_pwdb(CompletionState* cmpl_state)
2454 struct passwd *pwd_ptr;
2455 gchar* buf_ptr, *home_dir = NULL;
2456 gint len = 0, i, count = 0;
2458 if(cmpl_state->user_dir_name_buffer)
2462 while ((pwd_ptr = getpwent()) != NULL)
2464 len += strlen(pwd_ptr->pw_name);
2465 len += strlen(pwd_ptr->pw_dir);
2470 if (!cmpl_state->user_home_dir)
2472 /* the loser doesn't have $HOME set */
2475 pwd_ptr = getpwuid(getuid());
2481 home_dir = pwd_ptr->pw_dir;
2483 len += strlen(home_dir);
2489 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2490 cmpl_state->user_directories = g_new(CompletionUserDir, count);
2491 cmpl_state->user_directories_len = count;
2493 buf_ptr = cmpl_state->user_dir_name_buffer;
2495 if (!cmpl_state->user_home_dir)
2497 strcpy(buf_ptr, home_dir);
2498 cmpl_state->user_home_dir = buf_ptr;
2499 buf_ptr += strlen(buf_ptr);
2503 for(i = 0; i < count; i += 1)
2505 pwd_ptr = getpwent();
2512 strcpy(buf_ptr, pwd_ptr->pw_name);
2513 cmpl_state->user_directories[i].login = buf_ptr;
2514 buf_ptr += strlen(buf_ptr);
2516 strcpy(buf_ptr, pwd_ptr->pw_dir);
2517 cmpl_state->user_directories[i].homedir = buf_ptr;
2518 buf_ptr += strlen(buf_ptr);
2522 qsort(cmpl_state->user_directories,
2523 cmpl_state->user_directories_len,
2524 sizeof(CompletionUserDir),
2533 if(cmpl_state->user_dir_name_buffer)
2534 g_free(cmpl_state->user_dir_name_buffer);
2535 if(cmpl_state->user_directories)
2536 g_free(cmpl_state->user_directories);
2538 cmpl_state->user_dir_name_buffer = NULL;
2539 cmpl_state->user_directories = NULL;
2545 compare_user_dir(const void* a, const void* b)
2547 return strcmp((((CompletionUserDir*)a))->login,
2548 (((CompletionUserDir*)b))->login);
2552 compare_cmpl_dir(const void* a, const void* b)
2554 return strcmp((((CompletionDirEntry*)a))->entry_name,
2555 (((CompletionDirEntry*)b))->entry_name);
2559 cmpl_state_okay(CompletionState* cmpl_state)
2561 return cmpl_state && cmpl_state->reference_dir;
2565 cmpl_strerror(gint err)
2567 if(err == CMPL_ERRNO_TOO_LONG)
2568 return "Name too long";
2570 return g_strerror (err);