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;
1144 gchar *filename, *temp = 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 gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1153 filename = g_strdup (temp);
1155 if (bevent && filename)
1157 switch (bevent->type)
1159 case GDK_BUTTON_PRESS:
1160 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1163 case GDK_2BUTTON_PRESS:
1164 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1177 gtk_file_selection_dir_button (GtkWidget *widget,
1180 GdkEventButton *bevent,
1183 GtkFileSelection *fs = NULL;
1184 gchar *filename, *temp = NULL;
1186 g_return_if_fail (GTK_IS_CLIST (widget));
1188 fs = GTK_FILE_SELECTION (user_data);
1189 g_return_if_fail (fs != NULL);
1190 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1192 gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1193 filename = g_strdup (temp);
1195 if (bevent && filename)
1197 switch (bevent->type)
1199 case GDK_BUTTON_PRESS:
1200 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1203 case GDK_2BUTTON_PRESS:
1204 gtk_file_selection_populate (fs, filename, FALSE);
1217 gtk_file_selection_populate (GtkFileSelection *fs,
1221 CompletionState *cmpl_state;
1222 PossibleCompletion* poss;
1225 gchar* rem_path = rel_path;
1228 gint did_recurse = FALSE;
1229 gint possible_count = 0;
1230 gint selection_index = -1;
1231 gint file_list_width;
1232 gint dir_list_width;
1234 g_return_if_fail (fs != NULL);
1235 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1237 cmpl_state = (CompletionState*) fs->cmpl_state;
1238 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1240 if (!cmpl_state_okay (cmpl_state))
1242 /* Something went wrong. */
1243 gtk_file_selection_abort (fs);
1247 g_assert (cmpl_state->reference_dir);
1249 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1250 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1251 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1252 gtk_clist_clear (GTK_CLIST (fs->file_list));
1254 /* Set the dir_list to include ./ and ../ */
1257 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1260 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1262 /*reset the max widths of the lists*/
1263 dir_list_width = gdk_string_width(fs->dir_list->style->font,"../");
1264 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width);
1265 file_list_width = 1;
1266 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width);
1270 if (cmpl_is_a_completion (poss))
1272 possible_count += 1;
1274 filename = g_strdup (cmpl_this_completion (poss));
1278 if (cmpl_is_directory (poss))
1280 if (strcmp (filename, "./") != 0 &&
1281 strcmp (filename, "../") != 0)
1283 int width = gdk_string_width(fs->dir_list->style->font,
1285 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1286 if(width > dir_list_width)
1288 dir_list_width = width;
1289 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,
1296 int width = gdk_string_width(fs->file_list->style->font,
1298 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1299 if(width > file_list_width)
1301 file_list_width = width;
1302 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1308 poss = cmpl_next_completion (cmpl_state);
1311 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1312 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1314 /* File lists are set. */
1316 g_assert (cmpl_state->reference_dir);
1321 /* User is trying to complete filenames, so advance the user's input
1322 * string to the updated_text, which is the common leading substring
1323 * of all possible completions, and if its a directory attempt
1324 * attempt completions in it. */
1326 if (cmpl_updated_text (cmpl_state)[0])
1329 if (cmpl_updated_dir (cmpl_state))
1331 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1335 gtk_file_selection_populate (fs, dir_name, TRUE);
1341 if (fs->selection_entry)
1342 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1343 cmpl_updated_text (cmpl_state));
1348 selection_index = cmpl_last_valid_char (cmpl_state) -
1349 (strlen (rel_path) - strlen (rem_path));
1350 if (fs->selection_entry)
1351 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1356 if (fs->selection_entry)
1357 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1362 if (fs->selection_entry)
1363 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1365 if (fs->selection_entry)
1367 sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
1368 sizeof ("Selection: "));
1369 strcpy (sel_text, "Selection: ");
1370 strcat (sel_text, cmpl_reference_position (cmpl_state));
1372 gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
1376 if (fs->history_pulldown)
1378 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1385 gtk_file_selection_abort (GtkFileSelection *fs)
1389 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
1391 /* BEEP gdk_beep(); */
1393 if (fs->selection_entry)
1394 gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
1397 /**********************************************************************/
1398 /* External Interface */
1399 /**********************************************************************/
1401 /* The four completion state selectors
1404 cmpl_updated_text (CompletionState* cmpl_state)
1406 return cmpl_state->updated_text;
1410 cmpl_updated_dir (CompletionState* cmpl_state)
1412 return cmpl_state->re_complete;
1416 cmpl_reference_position (CompletionState* cmpl_state)
1418 return cmpl_state->reference_dir->fullname;
1422 cmpl_last_valid_char (CompletionState* cmpl_state)
1424 return cmpl_state->last_valid_char;
1428 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1432 strcpy (cmpl_state->updated_text, text);
1434 else if (text[0] == '~')
1439 dir = open_user_dir (text, cmpl_state);
1443 /* spencer says just return ~something, so
1444 * for now just do it. */
1445 strcpy (cmpl_state->updated_text, text);
1450 strcpy (cmpl_state->updated_text, dir->fullname);
1452 slash = strchr (text, '/');
1455 strcat (cmpl_state->updated_text, slash);
1460 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1461 strcat (cmpl_state->updated_text, "/");
1462 strcat (cmpl_state->updated_text, text);
1465 return cmpl_state->updated_text;
1468 /* The three completion selectors
1471 cmpl_this_completion (PossibleCompletion* pc)
1477 cmpl_is_directory (PossibleCompletion* pc)
1479 return pc->is_directory;
1483 cmpl_is_a_completion (PossibleCompletion* pc)
1485 return pc->is_a_completion;
1488 /**********************************************************************/
1489 /* Construction, deletion */
1490 /**********************************************************************/
1492 static CompletionState*
1493 cmpl_init_state (void)
1495 gchar getcwd_buf[2*MAXPATHLEN];
1496 CompletionState *new_state;
1498 new_state = g_new (CompletionState, 1);
1500 if (!getcwd (getcwd_buf, MAXPATHLEN))
1506 new_state->reference_dir = NULL;
1507 new_state->completion_dir = NULL;
1508 new_state->active_completion_dir = NULL;
1510 if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1512 /* if this fails, get_pwdb will fill it in. */
1513 new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1516 new_state->directory_storage = NULL;
1517 new_state->directory_sent_storage = NULL;
1518 new_state->last_valid_char = 0;
1519 new_state->updated_text = g_new (gchar, MAXPATHLEN);
1520 new_state->updated_text_alloc = MAXPATHLEN;
1521 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1522 new_state->the_completion.text_alloc = MAXPATHLEN;
1523 new_state->user_dir_name_buffer = NULL;
1524 new_state->user_directories = NULL;
1526 new_state->reference_dir = open_dir (getcwd_buf, new_state);
1528 if (!new_state->reference_dir)
1535 cmpl_free_dir_list(GList* dp0)
1540 free_dir (dp->data);
1548 cmpl_free_dir_sent_list(GList* dp0)
1553 free_dir_sent (dp->data);
1561 cmpl_free_state (CompletionState* cmpl_state)
1563 cmpl_free_dir_list (cmpl_state->directory_storage);
1564 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
1566 if (cmpl_state->user_dir_name_buffer)
1567 g_free (cmpl_state->user_dir_name_buffer);
1568 if (cmpl_state->user_directories)
1569 g_free (cmpl_state->user_directories);
1570 if (cmpl_state->the_completion.text)
1571 g_free (cmpl_state->the_completion.text);
1572 if (cmpl_state->updated_text)
1573 g_free (cmpl_state->updated_text);
1575 g_free (cmpl_state);
1579 free_dir(CompletionDir* dir)
1581 g_free(dir->fullname);
1586 free_dir_sent(CompletionDirSent* sent)
1588 g_free(sent->name_buffer);
1589 g_free(sent->entries);
1594 prune_memory_usage(CompletionState *cmpl_state)
1596 GList* cdsl = cmpl_state->directory_sent_storage;
1597 GList* cdl = cmpl_state->directory_storage;
1601 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1605 cmpl_free_dir_sent_list(cdsl->next);
1609 cmpl_state->directory_storage = NULL;
1611 if (cdl->data == cmpl_state->reference_dir)
1612 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1614 free_dir (cdl->data);
1621 /**********************************************************************/
1622 /* The main entrances. */
1623 /**********************************************************************/
1625 static PossibleCompletion*
1626 cmpl_completion_matches (gchar* text_to_complete,
1627 gchar** remaining_text,
1628 CompletionState* cmpl_state)
1631 PossibleCompletion *poss;
1633 prune_memory_usage(cmpl_state);
1635 g_assert(text_to_complete);
1637 cmpl_state->user_completion_index = -1;
1638 cmpl_state->last_completion_text = text_to_complete;
1639 cmpl_state->the_completion.text[0] = 0;
1640 cmpl_state->last_valid_char = 0;
1641 cmpl_state->updated_text_len = -1;
1642 cmpl_state->updated_text[0] = 0;
1643 cmpl_state->re_complete = FALSE;
1645 first_slash = strchr(text_to_complete, '/');
1647 if(text_to_complete[0] == '~' && !first_slash)
1649 /* Text starts with ~ and there is no slash, show all the
1650 * home directory completions.
1652 poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1654 update_cmpl(poss, cmpl_state);
1659 cmpl_state->reference_dir =
1660 open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1662 if(!cmpl_state->reference_dir)
1665 cmpl_state->completion_dir =
1666 find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1668 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1670 if(!cmpl_state->completion_dir)
1673 cmpl_state->completion_dir->cmpl_index = -1;
1674 cmpl_state->completion_dir->cmpl_parent = NULL;
1675 cmpl_state->completion_dir->cmpl_text = *remaining_text;
1677 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1679 cmpl_state->reference_dir = cmpl_state->completion_dir;
1681 poss = attempt_file_completion(cmpl_state);
1683 update_cmpl(poss, cmpl_state);
1688 static PossibleCompletion*
1689 cmpl_next_completion (CompletionState* cmpl_state)
1691 PossibleCompletion* poss = NULL;
1693 cmpl_state->the_completion.text[0] = 0;
1695 if(cmpl_state->user_completion_index >= 0)
1696 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1698 poss = attempt_file_completion(cmpl_state);
1700 update_cmpl(poss, cmpl_state);
1705 /**********************************************************************/
1706 /* Directory Operations */
1707 /**********************************************************************/
1709 /* Open the directory where completion will begin from, if possible. */
1710 static CompletionDir*
1711 open_ref_dir(gchar* text_to_complete,
1712 gchar** remaining_text,
1713 CompletionState* cmpl_state)
1716 CompletionDir *new_dir;
1718 first_slash = strchr(text_to_complete, '/');
1720 if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1722 new_dir = open_dir("/", cmpl_state);
1725 *remaining_text = text_to_complete + 1;
1727 else if (text_to_complete[0] == '~')
1729 new_dir = open_user_dir(text_to_complete, cmpl_state);
1734 *remaining_text = first_slash + 1;
1736 *remaining_text = text_to_complete + strlen(text_to_complete);
1745 *remaining_text = text_to_complete;
1747 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1752 new_dir->cmpl_index = -1;
1753 new_dir->cmpl_parent = NULL;
1759 /* open a directory by user name */
1760 static CompletionDir*
1761 open_user_dir(gchar* text_to_complete,
1762 CompletionState *cmpl_state)
1767 g_assert(text_to_complete && text_to_complete[0] == '~');
1769 first_slash = strchr(text_to_complete, '/');
1772 cmp_len = first_slash - text_to_complete - 1;
1774 cmp_len = strlen(text_to_complete + 1);
1779 if (!cmpl_state->user_home_dir &&
1780 !get_pwdb(cmpl_state))
1782 return open_dir(cmpl_state->user_home_dir, cmpl_state);
1787 char* copy = g_new(char, cmp_len + 1);
1789 strncpy(copy, text_to_complete + 1, cmp_len);
1791 pwd = getpwnam(copy);
1799 return open_dir(pwd->pw_dir, cmpl_state);
1803 /* open a directory relative the the current relative directory */
1804 static CompletionDir*
1805 open_relative_dir(gchar* dir_name,
1807 CompletionState *cmpl_state)
1809 gchar path_buf[2*MAXPATHLEN];
1811 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1813 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1817 strcpy(path_buf, dir->fullname);
1819 if(dir->fullname_len > 1)
1821 path_buf[dir->fullname_len] = '/';
1822 strcpy(path_buf + dir->fullname_len + 1, dir_name);
1826 strcpy(path_buf + dir->fullname_len, dir_name);
1829 return open_dir(path_buf, cmpl_state);
1832 /* after the cache lookup fails, really open a new directory */
1833 static CompletionDirSent*
1834 open_new_dir(gchar* dir_name, struct stat* sbuf)
1836 CompletionDirSent* sent;
1839 struct dirent *dirent_ptr;
1840 gint buffer_size = 0;
1841 gint entry_count = 0;
1843 struct stat ent_sbuf;
1844 char path_buf[MAXPATHLEN*2];
1847 sent = g_new(CompletionDirSent, 1);
1848 sent->mtime = sbuf->st_mtime;
1849 sent->inode = sbuf->st_ino;
1851 path_buf_len = strlen(dir_name);
1853 if (path_buf_len > MAXPATHLEN)
1855 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1859 strcpy(path_buf, dir_name);
1861 directory = opendir(dir_name);
1869 while((dirent_ptr = readdir(directory)) != NULL)
1871 int entry_len = strlen(dirent_ptr->d_name);
1872 buffer_size += entry_len + 1;
1875 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1877 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1878 closedir(directory);
1883 sent->name_buffer = g_new(gchar, buffer_size);
1884 sent->entries = g_new(CompletionDirEntry, entry_count);
1885 sent->entry_count = entry_count;
1887 buffer_ptr = sent->name_buffer;
1889 rewinddir(directory);
1891 for(i = 0; i < entry_count; i += 1)
1893 dirent_ptr = readdir(directory);
1898 closedir(directory);
1902 strcpy(buffer_ptr, dirent_ptr->d_name);
1903 sent->entries[i].entry_name = buffer_ptr;
1904 buffer_ptr += strlen(dirent_ptr->d_name);
1908 path_buf[path_buf_len] = '/';
1909 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1911 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1912 sent->entries[i].is_dir = 1;
1914 /* stat may fail, and we don't mind, since it could be a
1915 * dangling symlink. */
1916 sent->entries[i].is_dir = 0;
1919 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1921 closedir(directory);
1926 /* open a directory by absolute pathname */
1927 static CompletionDir*
1928 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1931 CompletionDirSent *sent;
1934 if(stat(dir_name, &sbuf) < 0)
1940 cdsl = cmpl_state->directory_sent_storage;
1946 if(sent->inode == sbuf.st_ino &&
1947 sent->mtime == sbuf.st_mtime)
1948 return attach_dir(sent, dir_name, cmpl_state);
1953 sent = open_new_dir(dir_name, &sbuf);
1956 cmpl_state->directory_sent_storage =
1957 g_list_prepend(cmpl_state->directory_sent_storage, sent);
1959 return attach_dir(sent, dir_name, cmpl_state);
1965 static CompletionDir*
1966 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1968 CompletionDir* new_dir;
1970 new_dir = g_new(CompletionDir, 1);
1972 cmpl_state->directory_storage =
1973 g_list_prepend(cmpl_state->directory_storage, new_dir);
1975 new_dir->sent = sent;
1976 new_dir->fullname = g_strdup(dir_name);
1977 new_dir->fullname_len = strlen(dir_name);
1983 correct_dir_fullname(CompletionDir* cmpl_dir)
1985 gint length = strlen(cmpl_dir->fullname);
1988 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
1992 strcpy(cmpl_dir->fullname, "/");
1993 cmpl_dir->fullname_len = 1;
1996 cmpl_dir->fullname[length - 2] = 0;
1999 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
2000 cmpl_dir->fullname[length - 2] = 0;
2001 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
2005 strcpy(cmpl_dir->fullname, "/");
2006 cmpl_dir->fullname_len = 1;
2010 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2016 cmpl_dir->fullname[length - 2] = 0;
2018 if(!correct_parent(cmpl_dir, &sbuf))
2021 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
2025 strcpy(cmpl_dir->fullname, "/");
2026 cmpl_dir->fullname_len = 1;
2030 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2036 cmpl_dir->fullname[length - 3] = 0;
2038 if(!correct_parent(cmpl_dir, &sbuf))
2042 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
2048 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
2055 last_slash = strrchr(cmpl_dir->fullname, '/');
2057 g_assert(last_slash);
2059 if(last_slash != cmpl_dir->fullname)
2060 { /* last_slash[0] = 0; */ }
2067 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2073 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2074 /* it wasn't a link */
2080 last_slash[0] = '/'; */
2082 /* it was a link, have to figure it out the hard way */
2084 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2089 g_free(cmpl_dir->fullname);
2091 cmpl_dir->fullname = new_name;
2097 find_parent_dir_fullname(gchar* dirname)
2099 gchar buffer[MAXPATHLEN];
2100 gchar buffer2[MAXPATHLEN];
2102 if(!getcwd(buffer, MAXPATHLEN))
2108 if(chdir(dirname) != 0 || chdir("..") != 0)
2114 if(!getcwd(buffer2, MAXPATHLEN))
2122 if(chdir(buffer) != 0)
2128 return g_strdup(buffer2);
2131 /**********************************************************************/
2132 /* Completion Operations */
2133 /**********************************************************************/
2135 static PossibleCompletion*
2136 attempt_homedir_completion(gchar* text_to_complete,
2137 CompletionState *cmpl_state)
2141 if (!cmpl_state->user_dir_name_buffer &&
2142 !get_pwdb(cmpl_state))
2144 length = strlen(text_to_complete) - 1;
2146 cmpl_state->user_completion_index += 1;
2148 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2150 index = first_diff_index(text_to_complete + 1,
2151 cmpl_state->user_directories
2152 [cmpl_state->user_completion_index].login);
2159 if(cmpl_state->last_valid_char < (index + 1))
2160 cmpl_state->last_valid_char = index + 1;
2161 cmpl_state->user_completion_index += 1;
2165 cmpl_state->the_completion.is_a_completion = 1;
2166 cmpl_state->the_completion.is_directory = 1;
2168 append_completion_text("~", cmpl_state);
2170 append_completion_text(cmpl_state->
2171 user_directories[cmpl_state->user_completion_index].login,
2174 return append_completion_text("/", cmpl_state);
2177 if(text_to_complete[1] ||
2178 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2180 cmpl_state->user_completion_index = -1;
2185 cmpl_state->user_completion_index += 1;
2186 cmpl_state->the_completion.is_a_completion = 1;
2187 cmpl_state->the_completion.is_directory = 1;
2189 return append_completion_text("~/", cmpl_state);
2193 /* returns the index (>= 0) of the first differing character,
2194 * PATTERN_MATCH if the completion matches */
2196 first_diff_index(gchar* pat, gchar* text)
2200 while(*pat && *text && *text == *pat)
2210 return PATTERN_MATCH;
2213 static PossibleCompletion*
2214 append_completion_text(gchar* text, CompletionState* cmpl_state)
2218 if(!cmpl_state->the_completion.text)
2221 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2223 if(cmpl_state->the_completion.text_alloc > len)
2225 strcat(cmpl_state->the_completion.text, text);
2226 return &cmpl_state->the_completion;
2229 while(i < len) { i <<= 1; }
2231 cmpl_state->the_completion.text_alloc = i;
2233 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2235 if(!cmpl_state->the_completion.text)
2239 strcat(cmpl_state->the_completion.text, text);
2240 return &cmpl_state->the_completion;
2244 static CompletionDir*
2245 find_completion_dir(gchar* text_to_complete,
2246 gchar** remaining_text,
2247 CompletionState* cmpl_state)
2249 gchar* first_slash = strchr(text_to_complete, '/');
2250 CompletionDir* dir = cmpl_state->reference_dir;
2251 *remaining_text = text_to_complete;
2255 gint len = first_slash - *remaining_text;
2257 gint found_index = -1;
2259 gchar* pat_buf = g_new (gchar, len + 1);
2261 strncpy(pat_buf, *remaining_text, len);
2264 for(i = 0; i < dir->sent->entry_count; i += 1)
2266 if(dir->sent->entries[i].is_dir &&
2267 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2268 FNMATCH_FLAGS)!= FNM_NOMATCH)
2285 CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
2294 next->cmpl_parent = dir;
2298 if(!correct_dir_fullname(dir))
2304 *remaining_text = first_slash + 1;
2305 first_slash = strchr(*remaining_text, '/');
2320 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
2324 if(!poss || !cmpl_is_a_completion(poss))
2327 cmpl_len = strlen(cmpl_this_completion(poss));
2329 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
2331 cmpl_state->updated_text =
2332 (gchar*)g_realloc(cmpl_state->updated_text,
2333 cmpl_state->updated_text_alloc);
2334 cmpl_state->updated_text_alloc = 2*cmpl_len;
2337 if(cmpl_state->updated_text_len < 0)
2339 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2340 cmpl_state->updated_text_len = cmpl_len;
2341 cmpl_state->re_complete = cmpl_is_directory(poss);
2343 else if(cmpl_state->updated_text_len == 0)
2345 cmpl_state->re_complete = FALSE;
2350 first_diff_index(cmpl_state->updated_text,
2351 cmpl_this_completion(poss));
2353 cmpl_state->re_complete = FALSE;
2355 if(first_diff == PATTERN_MATCH)
2358 if(first_diff > cmpl_state->updated_text_len)
2359 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2361 cmpl_state->updated_text_len = first_diff;
2362 cmpl_state->updated_text[first_diff] = 0;
2366 static PossibleCompletion*
2367 attempt_file_completion(CompletionState *cmpl_state)
2369 gchar *pat_buf, *first_slash;
2370 CompletionDir *dir = cmpl_state->active_completion_dir;
2372 dir->cmpl_index += 1;
2374 if(dir->cmpl_index == dir->sent->entry_count)
2376 if(dir->cmpl_parent == NULL)
2378 cmpl_state->active_completion_dir = NULL;
2384 cmpl_state->active_completion_dir = dir->cmpl_parent;
2386 return attempt_file_completion(cmpl_state);
2390 g_assert(dir->cmpl_text);
2392 first_slash = strchr(dir->cmpl_text, '/');
2396 gint len = first_slash - dir->cmpl_text;
2398 pat_buf = g_new (gchar, len + 1);
2399 strncpy(pat_buf, dir->cmpl_text, len);
2404 gint len = strlen(dir->cmpl_text);
2406 pat_buf = g_new (gchar, len + 2);
2407 strcpy(pat_buf, dir->cmpl_text);
2408 strcpy(pat_buf + len, "*");
2413 if(dir->sent->entries[dir->cmpl_index].is_dir)
2415 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2416 FNMATCH_FLAGS) != FNM_NOMATCH)
2418 CompletionDir* new_dir;
2420 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
2429 new_dir->cmpl_parent = dir;
2431 new_dir->cmpl_index = -1;
2432 new_dir->cmpl_text = first_slash + 1;
2434 cmpl_state->active_completion_dir = new_dir;
2437 return attempt_file_completion(cmpl_state);
2442 return attempt_file_completion(cmpl_state);
2448 return attempt_file_completion(cmpl_state);
2453 if(dir->cmpl_parent != NULL)
2455 append_completion_text(dir->fullname +
2456 strlen(cmpl_state->completion_dir->fullname) + 1,
2458 append_completion_text("/", cmpl_state);
2461 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2463 cmpl_state->the_completion.is_a_completion =
2464 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2465 FNMATCH_FLAGS) != FNM_NOMATCH);
2467 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2468 if(dir->sent->entries[dir->cmpl_index].is_dir)
2469 append_completion_text("/", cmpl_state);
2472 return &cmpl_state->the_completion;
2478 get_pwdb(CompletionState* cmpl_state)
2480 struct passwd *pwd_ptr;
2481 gchar* buf_ptr, *home_dir = NULL;
2482 gint len = 0, i, count = 0;
2484 if(cmpl_state->user_dir_name_buffer)
2488 while ((pwd_ptr = getpwent()) != NULL)
2490 len += strlen(pwd_ptr->pw_name);
2491 len += strlen(pwd_ptr->pw_dir);
2496 if (!cmpl_state->user_home_dir)
2498 /* the loser doesn't have $HOME set */
2501 pwd_ptr = getpwuid(getuid());
2507 home_dir = pwd_ptr->pw_dir;
2509 len += strlen(home_dir);
2515 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2516 cmpl_state->user_directories = g_new(CompletionUserDir, count);
2517 cmpl_state->user_directories_len = count;
2519 buf_ptr = cmpl_state->user_dir_name_buffer;
2521 if (!cmpl_state->user_home_dir)
2523 strcpy(buf_ptr, home_dir);
2524 cmpl_state->user_home_dir = buf_ptr;
2525 buf_ptr += strlen(buf_ptr);
2529 for(i = 0; i < count; i += 1)
2531 pwd_ptr = getpwent();
2538 strcpy(buf_ptr, pwd_ptr->pw_name);
2539 cmpl_state->user_directories[i].login = buf_ptr;
2540 buf_ptr += strlen(buf_ptr);
2542 strcpy(buf_ptr, pwd_ptr->pw_dir);
2543 cmpl_state->user_directories[i].homedir = buf_ptr;
2544 buf_ptr += strlen(buf_ptr);
2548 qsort(cmpl_state->user_directories,
2549 cmpl_state->user_directories_len,
2550 sizeof(CompletionUserDir),
2559 if(cmpl_state->user_dir_name_buffer)
2560 g_free(cmpl_state->user_dir_name_buffer);
2561 if(cmpl_state->user_directories)
2562 g_free(cmpl_state->user_directories);
2564 cmpl_state->user_dir_name_buffer = NULL;
2565 cmpl_state->user_directories = NULL;
2571 compare_user_dir(const void* a, const void* b)
2573 return strcmp((((CompletionUserDir*)a))->login,
2574 (((CompletionUserDir*)b))->login);
2578 compare_cmpl_dir(const void* a, const void* b)
2580 return strcmp((((CompletionDirEntry*)a))->entry_name,
2581 (((CompletionDirEntry*)b))->entry_name);
2585 cmpl_state_okay(CompletionState* cmpl_state)
2587 return cmpl_state && cmpl_state->reference_dir;
2591 cmpl_strerror(gint err)
2593 if(err == CMPL_ERRNO_TOO_LONG)
2594 return "Name too long";
2596 return g_strerror (err);