1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 #include <sys/types.h>
21 #include <sys/param.h>
30 #include "gdk/gdkkeysyms.h"
31 #include "gtkbutton.h"
33 #include "gtkfilesel.h"
37 #include "gtklistitem.h"
39 #include "gtkscrolledwindow.h"
40 #include "gtksignal.h"
43 #include "gtkmenuitem.h"
44 #include "gtkoptionmenu.h"
46 #include "gtkdialog.h"
48 #define DIR_LIST_WIDTH 180
49 #define DIR_LIST_HEIGHT 180
50 #define FILE_LIST_WIDTH 180
51 #define FILE_LIST_HEIGHT 180
53 /* I've put this here so it doesn't get confused with the
54 * file completion interface */
55 typedef struct _HistoryCallbackArg HistoryCallbackArg;
57 struct _HistoryCallbackArg
64 typedef struct _CompletionState CompletionState;
65 typedef struct _CompletionDir CompletionDir;
66 typedef struct _CompletionDirSent CompletionDirSent;
67 typedef struct _CompletionDirEntry CompletionDirEntry;
68 typedef struct _CompletionUserDir CompletionUserDir;
69 typedef struct _PossibleCompletion PossibleCompletion;
71 /* Non-external file completion decls and structures */
73 /* A contant telling PRCS how many directories to cache. Its actually
74 * kept in a list, so the geometry isn't important. */
75 #define CMPL_DIRECTORY_CACHE_SIZE 10
77 /* A constant used to determine whether a substring was an exact
78 * match by first_diff_index()
80 #define PATTERN_MATCH -1
81 /* The arguments used by all fnmatch() calls below
83 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
85 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
87 /* This structure contains all the useful information about a directory
88 * for the purposes of filename completion. These structures are cached
89 * in the CompletionState struct. CompletionDir's are reference counted.
91 struct _CompletionDirSent
97 gchar *name_buffer; /* memory segment containing names of all entries */
99 struct _CompletionDirEntry *entries;
102 struct _CompletionDir
104 CompletionDirSent *sent;
109 struct _CompletionDir *cmpl_parent;
114 /* This structure contains pairs of directory entry names with a flag saying
115 * whether or not they are a valid directory. NOTE: This information is used
116 * to provide the caller with information about whether to update its completions
117 * or try to open a file. Since directories are cached by the directory mtime,
118 * a symlink which points to an invalid file (which will not be a directory),
119 * will not be reevaluated if that file is created, unless the containing
120 * directory is touched. I consider this case to be worth ignoring (josh).
122 struct _CompletionDirEntry
128 struct _CompletionUserDir
134 struct _PossibleCompletion
136 /* accessible fields, all are accessed externally by functions
140 gint is_a_completion;
148 struct _CompletionState
150 gint last_valid_char;
152 gint updated_text_len;
153 gint updated_text_alloc;
156 gchar *user_dir_name_buffer;
157 gint user_directories_len;
158 gchar *user_home_dir;
160 gchar *last_completion_text;
162 gint user_completion_index; /* if >= 0, currently completing ~user */
164 struct _CompletionDir *completion_dir; /* directory completing from */
165 struct _CompletionDir *active_completion_dir;
167 struct _PossibleCompletion the_completion;
169 struct _CompletionDir *reference_dir; /* initial directory */
171 GList* directory_storage;
172 GList* directory_sent_storage;
174 struct _CompletionUserDir *user_directories;
178 /* File completion functions which would be external, were they used
179 * outside of this file.
182 static CompletionState* cmpl_init_state (void);
183 static void cmpl_free_state (CompletionState *cmpl_state);
184 static gint cmpl_state_okay (CompletionState* cmpl_state);
185 static gchar* cmpl_strerror (gint);
187 static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete,
188 gchar **remaining_text,
189 CompletionState *cmpl_state);
191 /* Returns a name for consideration, possibly a completion, this name
192 * will be invalid after the next call to cmpl_next_completion.
194 static char* cmpl_this_completion (PossibleCompletion*);
196 /* True if this completion matches the given text. Otherwise, this
197 * output can be used to have a list of non-completions.
199 static gint cmpl_is_a_completion (PossibleCompletion*);
201 /* True if the completion is a directory
203 static gint cmpl_is_directory (PossibleCompletion*);
205 /* Obtains the next completion, or NULL
207 static PossibleCompletion* cmpl_next_completion (CompletionState*);
209 /* Updating completions: the return value of cmpl_updated_text() will
210 * be text_to_complete completed as much as possible after the most
211 * recent call to cmpl_completion_matches. For the present
212 * application, this is the suggested replacement for the user's input
213 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
216 static gchar* cmpl_updated_text (CompletionState* cmpl_state);
218 /* After updating, to see if the completion was a directory, call
219 * this. If it was, you should consider re-calling completion_matches.
221 static gint cmpl_updated_dir (CompletionState* cmpl_state);
223 /* Current location: if using file completion, return the current
224 * directory, from which file completion begins. More specifically,
225 * the cwd concatenated with all exact completions up to the last
226 * directory delimiter('/').
228 static gchar* cmpl_reference_position (CompletionState* cmpl_state);
230 /* backing up: if cmpl_completion_matches returns NULL, you may query
231 * the index of the last completable character into cmpl_updated_text.
233 static gint cmpl_last_valid_char (CompletionState* cmpl_state);
235 /* When the user selects a non-directory, call cmpl_completion_fullname
236 * to get the full name of the selected file.
238 static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
241 /* Directory operations. */
242 static CompletionDir* open_ref_dir (gchar* text_to_complete,
243 gchar** remaining_text,
244 CompletionState* cmpl_state);
245 static CompletionDir* open_dir (gchar* dir_name,
246 CompletionState* cmpl_state);
247 static CompletionDir* open_user_dir (gchar* text_to_complete,
248 CompletionState *cmpl_state);
249 static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
250 CompletionState *cmpl_state);
251 static CompletionDirSent* open_new_dir (gchar* dir_name, struct stat* sbuf);
252 static gint correct_dir_fullname (CompletionDir* cmpl_dir);
253 static gint correct_parent (CompletionDir* cmpl_dir,
255 static gchar* find_parent_dir_fullname (gchar* dirname);
256 static CompletionDir* attach_dir (CompletionDirSent* sent,
258 CompletionState *cmpl_state);
259 static void free_dir_sent (CompletionDirSent* sent);
260 static void free_dir (CompletionDir *dir);
261 static void prune_memory_usage(CompletionState *cmpl_state);
263 /* Completion operations */
264 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
265 CompletionState *cmpl_state);
266 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
267 static CompletionDir* find_completion_dir(gchar* text_to_complete,
268 gchar** remaining_text,
269 CompletionState* cmpl_state);
270 static PossibleCompletion* append_completion_text(gchar* text,
271 CompletionState* cmpl_state);
272 static gint get_pwdb(CompletionState* cmpl_state);
273 static gint first_diff_index(gchar* pat, gchar* text);
274 static gint compare_user_dir(const void* a, const void* b);
275 static gint compare_cmpl_dir(const void* a, const void* b);
276 static void update_cmpl(PossibleCompletion* poss,
277 CompletionState* cmpl_state);
279 static void gtk_file_selection_class_init (GtkFileSelectionClass *klass);
280 static void gtk_file_selection_init (GtkFileSelection *filesel);
281 static void gtk_file_selection_destroy (GtkObject *object);
282 static gint gtk_file_selection_key_press (GtkWidget *widget,
286 static void gtk_file_selection_file_button (GtkWidget *widget,
289 GdkEventButton *bevent,
292 static void gtk_file_selection_dir_button (GtkWidget *widget,
295 GdkEventButton *bevent,
298 static void gtk_file_selection_populate (GtkFileSelection *fs,
301 static void gtk_file_selection_abort (GtkFileSelection *fs);
303 static void gtk_file_selection_update_history_menu (GtkFileSelection *fs,
306 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
307 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
308 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
312 static GtkWindowClass *parent_class = NULL;
314 /* Saves errno when something cmpl does fails. */
315 static gint cmpl_errno;
318 gtk_file_selection_get_type ()
320 static guint file_selection_type = 0;
322 if (!file_selection_type)
324 GtkTypeInfo filesel_info =
327 sizeof (GtkFileSelection),
328 sizeof (GtkFileSelectionClass),
329 (GtkClassInitFunc) gtk_file_selection_class_init,
330 (GtkObjectInitFunc) gtk_file_selection_init,
331 (GtkArgSetFunc) NULL,
332 (GtkArgGetFunc) NULL,
335 file_selection_type = gtk_type_unique (gtk_window_get_type (), &filesel_info);
338 return file_selection_type;
342 gtk_file_selection_class_init (GtkFileSelectionClass *class)
344 GtkObjectClass *object_class;
346 object_class = (GtkObjectClass*) class;
348 parent_class = gtk_type_class (gtk_window_get_type ());
350 object_class->destroy = gtk_file_selection_destroy;
354 gtk_file_selection_init (GtkFileSelection *filesel)
356 GtkWidget *entry_vbox;
358 GtkWidget *list_hbox;
359 GtkWidget *action_area;
360 GtkWidget *pulldown_hbox;
361 GtkWidget *button_hbox;
363 char *dir_title [] = { "Directories", };
364 char *file_title [] = { "Files", };
366 filesel->cmpl_state = cmpl_init_state ();
368 /* The dialog-sized vertical box */
369 filesel->main_vbox = gtk_vbox_new (FALSE, 10);
370 gtk_container_border_width (GTK_CONTAINER (filesel), 10);
371 gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
372 gtk_widget_show (filesel->main_vbox);
374 /* The horizontal box containing create, rename etc. buttons */
375 button_hbox = gtk_hbox_new (TRUE, 0);
376 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), button_hbox,
378 gtk_widget_show (button_hbox);
380 /* delete, create directory, and rename */
381 button = gtk_button_new_with_label ("Create Dir");
382 gtk_signal_connect (GTK_OBJECT (button), "clicked",
383 (GtkSignalFunc) gtk_file_selection_create_dir,
385 gtk_box_pack_start (GTK_BOX (button_hbox), button, TRUE, TRUE, 0);
386 gtk_widget_show (button);
388 button = gtk_button_new_with_label ("Delete File");
389 gtk_signal_connect (GTK_OBJECT (button), "clicked",
390 (GtkSignalFunc) gtk_file_selection_delete_file,
392 gtk_box_pack_start (GTK_BOX (button_hbox), button, TRUE, TRUE, 0);
393 gtk_widget_show (button);
395 button = gtk_button_new_with_label ("Rename File");
396 gtk_signal_connect (GTK_OBJECT (button), "clicked",
397 (GtkSignalFunc) gtk_file_selection_rename_file,
399 gtk_box_pack_start (GTK_BOX (button_hbox), button, TRUE, TRUE, 0);
400 gtk_widget_show (button);
402 /* The Help button */
403 filesel->help_button = gtk_button_new_with_label ("Help");
404 gtk_box_pack_start (GTK_BOX (button_hbox), filesel->help_button,
406 gtk_widget_show (filesel->help_button);
409 /* hbox for pulldown menu */
410 pulldown_hbox = gtk_hbox_new (TRUE, 5);
411 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
412 gtk_widget_show (pulldown_hbox);
415 filesel->history_pulldown = gtk_option_menu_new ();
416 gtk_widget_show (filesel->history_pulldown);
417 gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown,
420 /* The horizontal box containing the directory and file listboxes */
421 list_hbox = gtk_hbox_new (FALSE, 5);
422 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
423 gtk_widget_show (list_hbox);
425 /* The directories clist */
426 filesel->dir_list = gtk_clist_new_with_titles (1, dir_title);
427 gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
428 gtk_clist_set_column_width (GTK_CLIST (filesel->dir_list), 0, 150);
429 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
430 (GtkSignalFunc) gtk_file_selection_dir_button,
432 gtk_clist_set_policy (GTK_CLIST (filesel->dir_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
433 gtk_container_border_width (GTK_CONTAINER (filesel->dir_list), 5);
434 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->dir_list, TRUE, TRUE, 0);
435 gtk_widget_show (filesel->dir_list);
437 /* The files clist */
438 filesel->file_list = gtk_clist_new_with_titles (1, file_title);
439 gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
440 gtk_clist_set_column_width (GTK_CLIST (filesel->file_list), 0, 150);
441 gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
442 (GtkSignalFunc) gtk_file_selection_file_button,
444 gtk_clist_set_policy (GTK_CLIST (filesel->file_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
445 gtk_container_border_width (GTK_CONTAINER (filesel->file_list), 5);
446 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->file_list, TRUE, TRUE, 0);
447 gtk_widget_show (filesel->file_list);
449 /* The action area */
450 action_area = gtk_hbox_new (TRUE, 10);
451 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), action_area, FALSE, FALSE, 0);
452 gtk_widget_show (action_area);
455 filesel->ok_button = gtk_button_new_with_label ("OK");
456 GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
457 gtk_box_pack_start (GTK_BOX (action_area), filesel->ok_button, TRUE, TRUE, 0);
458 gtk_widget_grab_default (filesel->ok_button);
459 gtk_widget_show (filesel->ok_button);
461 /* The Cancel button */
462 filesel->cancel_button = gtk_button_new_with_label ("Cancel");
463 GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
464 gtk_box_pack_start (GTK_BOX (action_area), filesel->cancel_button, TRUE, TRUE, 0);
465 gtk_widget_show (filesel->cancel_button);
467 /* The selection entry widget */
468 entry_vbox = gtk_vbox_new (FALSE, 2);
469 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
470 gtk_widget_show (entry_vbox);
472 filesel->selection_text = label = gtk_label_new ("");
473 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
474 gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
475 gtk_widget_show (label);
477 filesel->selection_entry = gtk_entry_new ();
478 gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
479 (GtkSignalFunc) gtk_file_selection_key_press, filesel);
480 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
481 (GtkSignalFunc) gtk_widget_grab_default,
482 GTK_OBJECT (filesel->ok_button));
483 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
484 (GtkSignalFunc) gtk_button_clicked,
485 GTK_OBJECT (filesel->ok_button));
486 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
487 gtk_widget_show (filesel->selection_entry);
489 if (!cmpl_state_okay (filesel->cmpl_state))
493 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
495 gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
499 gtk_file_selection_populate (filesel, "", FALSE);
502 gtk_widget_grab_focus (filesel->selection_entry);
506 gtk_file_selection_new (const gchar *title)
508 GtkFileSelection *filesel;
510 filesel = gtk_type_new (gtk_file_selection_get_type ());
511 gtk_window_set_title (GTK_WINDOW (filesel), title);
513 return GTK_WIDGET (filesel);
517 gtk_file_selection_set_filename (GtkFileSelection *filesel,
518 const gchar *filename)
520 char buf[MAXPATHLEN];
521 const char *name, *last_slash;
523 g_return_if_fail (filesel != NULL);
524 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
525 g_return_if_fail (filename != NULL);
527 last_slash = strrchr (filename, '/');
536 gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
538 strncpy (buf, filename, len);
541 name = last_slash + 1;
544 gtk_file_selection_populate (filesel, buf, FALSE);
546 if (filesel->selection_entry)
547 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
551 gtk_file_selection_get_filename (GtkFileSelection *filesel)
553 static char nothing[2] = "";
557 g_return_val_if_fail (filesel != NULL, nothing);
558 g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
560 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
563 filename = cmpl_completion_fullname (text, filesel->cmpl_state);
571 gtk_file_selection_destroy (GtkObject *object)
573 GtkFileSelection *filesel;
575 HistoryCallbackArg *callback_arg;
577 g_return_if_fail (object != NULL);
578 g_return_if_fail (GTK_IS_FILE_SELECTION (object));
580 filesel = GTK_FILE_SELECTION (object);
582 if (filesel->fileop_dialog)
583 gtk_widget_destroy (filesel->fileop_dialog);
585 if (filesel->history_list) {
586 list = filesel->history_list;
588 callback_arg = list->data;
589 g_free (callback_arg->directory);
592 g_list_free (filesel->history_list);
595 cmpl_free_state (filesel->cmpl_state);
597 if (GTK_OBJECT_CLASS (parent_class)->destroy)
598 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
601 /* Begin file operations callbacks */
604 gtk_file_selection_fileop_error (gchar *error_message)
611 g_return_if_fail (error_message != NULL);
614 dialog = gtk_dialog_new ();
616 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
617 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
620 gtk_window_set_title (GTK_WINDOW (dialog), "Error");
621 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
623 vbox = gtk_vbox_new(FALSE, 0);
624 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
625 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
627 gtk_widget_show(vbox);
629 label = gtk_label_new(error_message);
630 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
631 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
632 gtk_widget_show(label);
634 /* yes, we free it */
635 g_free (error_message);
638 button = gtk_button_new_with_label ("Close");
639 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
640 (GtkSignalFunc) gtk_widget_destroy,
642 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
643 button, TRUE, TRUE, 0);
644 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
645 gtk_widget_grab_default(button);
646 gtk_widget_show (button);
648 gtk_widget_show (dialog);
652 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
654 GtkFileSelection *fs = data;
656 g_return_if_fail (fs != NULL);
657 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
659 fs->fileop_dialog = NULL;
664 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
666 GtkFileSelection *fs = data;
671 CompletionState *cmpl_state;
673 g_return_if_fail (fs != NULL);
674 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
676 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
677 cmpl_state = (CompletionState*) fs->cmpl_state;
678 path = cmpl_reference_position (cmpl_state);
680 full_path = g_strconcat (path, "/", dirname, NULL);
681 if ( (mkdir (full_path, 0755) < 0) )
683 buf = g_strconcat ("Error creating directory \"", dirname, "\": ",
684 g_strerror(errno), NULL);
685 gtk_file_selection_fileop_error (buf);
689 gtk_widget_destroy (fs->fileop_dialog);
690 gtk_file_selection_populate (fs, "", FALSE);
694 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
696 GtkFileSelection *fs = data;
702 g_return_if_fail (fs != NULL);
703 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
705 if (fs->fileop_dialog)
709 fs->fileop_dialog = dialog = gtk_dialog_new ();
710 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
711 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
713 gtk_window_set_title (GTK_WINDOW (dialog), "Create Directory");
714 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
715 gtk_widget_show (dialog);
717 vbox = gtk_vbox_new(FALSE, 0);
718 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
719 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
721 gtk_widget_show(vbox);
723 label = gtk_label_new("Directory name:");
724 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
725 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
726 gtk_widget_show(label);
728 /* The directory entry widget */
729 fs->fileop_entry = gtk_entry_new ();
730 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
732 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
733 gtk_widget_show (fs->fileop_entry);
736 button = gtk_button_new_with_label ("Create");
737 gtk_signal_connect (GTK_OBJECT (button), "clicked",
738 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
740 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
741 button, TRUE, TRUE, 0);
742 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
743 gtk_widget_show(button);
745 button = gtk_button_new_with_label ("Cancel");
746 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
747 (GtkSignalFunc) gtk_widget_destroy,
749 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
750 button, TRUE, TRUE, 0);
751 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
752 gtk_widget_grab_default(button);
753 gtk_widget_show (button);
757 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
759 GtkFileSelection *fs = data;
760 CompletionState *cmpl_state;
765 g_return_if_fail (fs != NULL);
766 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
768 cmpl_state = (CompletionState*) fs->cmpl_state;
769 path = cmpl_reference_position (cmpl_state);
771 full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
772 if ( (unlink (full_path) < 0) )
774 buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ",
775 g_strerror(errno), NULL);
776 gtk_file_selection_fileop_error (buf);
780 gtk_widget_destroy (fs->fileop_dialog);
781 gtk_file_selection_populate (fs, "", FALSE);
785 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
787 GtkFileSelection *fs = data;
795 g_return_if_fail (fs != NULL);
796 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
798 if (fs->fileop_dialog)
801 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
802 if (strlen(filename) < 1)
805 fs->fileop_file = filename;
808 fs->fileop_dialog = dialog = gtk_dialog_new ();
809 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
810 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
812 gtk_window_set_title (GTK_WINDOW (dialog), "Delete File");
813 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
815 vbox = gtk_vbox_new(FALSE, 0);
816 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
817 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
819 gtk_widget_show(vbox);
821 buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
822 label = gtk_label_new(buf);
823 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
824 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
825 gtk_widget_show(label);
829 button = gtk_button_new_with_label ("Delete");
830 gtk_signal_connect (GTK_OBJECT (button), "clicked",
831 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
833 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
834 button, TRUE, TRUE, 0);
835 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
836 gtk_widget_show(button);
838 button = gtk_button_new_with_label ("Cancel");
839 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
840 (GtkSignalFunc) gtk_widget_destroy,
842 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
843 button, TRUE, TRUE, 0);
844 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
845 gtk_widget_grab_default(button);
846 gtk_widget_show (button);
848 gtk_widget_show (dialog);
852 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
854 GtkFileSelection *fs = data;
860 CompletionState *cmpl_state;
862 g_return_if_fail (fs != NULL);
863 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
865 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
866 cmpl_state = (CompletionState*) fs->cmpl_state;
867 path = cmpl_reference_position (cmpl_state);
869 new_filename = g_strconcat (path, "/", file, NULL);
870 old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
872 if ( (rename (old_filename, new_filename)) < 0)
874 buf = g_strconcat ("Error renaming file \"", file, "\": ",
875 g_strerror(errno), NULL);
876 gtk_file_selection_fileop_error (buf);
878 g_free (new_filename);
879 g_free (old_filename);
881 gtk_widget_destroy (fs->fileop_dialog);
882 gtk_file_selection_populate (fs, "", FALSE);
886 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
888 GtkFileSelection *fs = data;
895 g_return_if_fail (fs != NULL);
896 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
898 if (fs->fileop_dialog)
901 fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
902 if (strlen(fs->fileop_file) < 1)
906 fs->fileop_dialog = dialog = gtk_dialog_new ();
907 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
908 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
910 gtk_window_set_title (GTK_WINDOW (dialog), "Rename File");
911 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
912 gtk_widget_show (dialog);
914 vbox = gtk_vbox_new(FALSE, 0);
915 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
916 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
918 gtk_widget_show(vbox);
920 buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
921 label = gtk_label_new(buf);
922 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
923 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
924 gtk_widget_show(label);
927 /* New filename entry */
928 fs->fileop_entry = gtk_entry_new ();
929 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
931 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
932 gtk_widget_show (fs->fileop_entry);
934 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
935 gtk_entry_select_region (GTK_ENTRY (fs->fileop_entry),
936 0, strlen (fs->fileop_file));
939 button = gtk_button_new_with_label ("Rename");
940 gtk_signal_connect (GTK_OBJECT (button), "clicked",
941 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
943 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
944 button, TRUE, TRUE, 0);
945 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
946 gtk_widget_show(button);
948 button = gtk_button_new_with_label ("Cancel");
949 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
950 (GtkSignalFunc) gtk_widget_destroy,
952 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
953 button, TRUE, TRUE, 0);
954 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
955 gtk_widget_grab_default(button);
956 gtk_widget_show (button);
961 gtk_file_selection_key_press (GtkWidget *widget,
965 GtkFileSelection *fs;
968 g_return_val_if_fail (widget != NULL, FALSE);
969 g_return_val_if_fail (event != NULL, FALSE);
971 if (event->keyval == GDK_Tab)
973 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
975 fs = GTK_FILE_SELECTION (user_data);
976 text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
977 gtk_file_selection_populate (fs, text, TRUE);
987 gtk_file_selection_history_callback (GtkWidget *widget, gpointer data)
989 GtkFileSelection *fs = data;
990 HistoryCallbackArg *callback_arg;
993 g_return_if_fail (fs != NULL);
994 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
996 list = fs->history_list;
999 callback_arg = list->data;
1001 if (callback_arg->menu_item == widget)
1003 gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1012 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1013 gchar *current_directory)
1015 HistoryCallbackArg *callback_arg;
1016 GtkWidget *menu_item;
1023 g_return_if_fail (fs != NULL);
1024 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1025 g_return_if_fail (current_directory != NULL);
1027 list = fs->history_list;
1029 if (fs->history_menu)
1032 callback_arg = list->data;
1033 g_free (callback_arg->directory);
1036 g_list_free (fs->history_list);
1037 fs->history_list = NULL;
1039 gtk_widget_destroy (fs->history_menu);
1042 fs->history_menu = gtk_menu_new();
1044 current_dir = g_strdup(current_directory);
1046 dir_len = strlen (current_dir);
1048 for (i = dir_len; i >= 0; i--)
1050 /* the i == dir_len is to catch the full path for the first
1052 if ( (current_dir[i] == '/') || (i == dir_len))
1054 /* another small hack to catch the full path */
1056 current_dir[i + 1] = '\0';
1057 menu_item = gtk_menu_item_new_with_label (current_dir);
1058 directory = g_strdup (current_dir);
1060 callback_arg = g_new (HistoryCallbackArg, 1);
1061 callback_arg->menu_item = menu_item;
1063 /* since the autocompletion gets confused if you don't
1064 * supply a trailing '/' on a dir entry, set the full
1065 * (current) path to "" which just refreshes the filesel */
1067 callback_arg->directory = g_strdup ("");
1069 callback_arg->directory = directory;
1072 fs->history_list = g_list_append (fs->history_list, callback_arg);
1074 gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1075 (GtkSignalFunc) gtk_file_selection_history_callback,
1077 gtk_menu_append (GTK_MENU (fs->history_menu), menu_item);
1078 gtk_widget_show (menu_item);
1082 gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown),
1084 g_free (current_dir);
1090 gtk_file_selection_file_button (GtkWidget *widget,
1093 GdkEventButton *bevent,
1096 GtkFileSelection *fs = NULL;
1099 g_return_if_fail (GTK_IS_CLIST (widget));
1101 fs = GTK_FILE_SELECTION (user_data);
1102 g_return_if_fail (fs != NULL);
1103 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1105 filename = gtk_clist_get_row_data (GTK_CLIST (fs->file_list), row);
1107 if (bevent && filename) {
1109 switch (bevent->type)
1111 case GDK_BUTTON_PRESS:
1112 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1115 case GDK_2BUTTON_PRESS:
1116 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1126 gtk_file_selection_dir_button (GtkWidget *widget,
1129 GdkEventButton *bevent,
1132 GtkFileSelection *fs = NULL;
1135 g_return_if_fail (GTK_IS_CLIST (widget));
1137 fs = GTK_FILE_SELECTION (user_data);
1138 g_return_if_fail (fs != NULL);
1139 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1141 filename = gtk_clist_get_row_data (GTK_CLIST (fs->dir_list), row);
1143 if (bevent && filename) {
1145 switch (bevent->type)
1147 case GDK_BUTTON_PRESS:
1148 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1151 case GDK_2BUTTON_PRESS:
1152 gtk_file_selection_populate (fs, filename, FALSE);
1162 gtk_file_selection_populate (GtkFileSelection *fs,
1166 CompletionState *cmpl_state;
1167 PossibleCompletion* poss;
1170 gchar* rem_path = rel_path;
1173 gint did_recurse = FALSE;
1174 gint possible_count = 0;
1175 gint selection_index = -1;
1177 g_return_if_fail (fs != NULL);
1178 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1180 cmpl_state = (CompletionState*) fs->cmpl_state;
1181 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1183 if (!cmpl_state_okay (cmpl_state))
1185 /* Something went wrong. */
1186 gtk_file_selection_abort (fs);
1190 g_assert (cmpl_state->reference_dir);
1192 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1193 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1194 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1195 gtk_clist_clear (GTK_CLIST (fs->file_list));
1197 /* Set the dir_list to include ./ and ../ */
1200 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1201 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row, "./");
1204 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1205 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row, "../");
1209 if (cmpl_is_a_completion (poss))
1211 possible_count += 1;
1213 filename = g_strdup (cmpl_this_completion (poss));
1217 if (cmpl_is_directory (poss))
1219 if (strcmp (filename, "./") != 0 &&
1220 strcmp (filename, "../") != 0)
1222 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1223 gtk_clist_set_row_data (GTK_CLIST (fs->dir_list), row,
1229 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1230 gtk_clist_set_row_data (GTK_CLIST (fs->file_list), row,
1235 poss = cmpl_next_completion (cmpl_state);
1238 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1239 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1241 /* File lists are set. */
1243 g_assert (cmpl_state->reference_dir);
1248 /* User is trying to complete filenames, so advance the user's input
1249 * string to the updated_text, which is the common leading substring
1250 * of all possible completions, and if its a directory attempt
1251 * attempt completions in it. */
1253 if (cmpl_updated_text (cmpl_state)[0])
1256 if (cmpl_updated_dir (cmpl_state))
1258 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1262 gtk_file_selection_populate (fs, dir_name, TRUE);
1268 if (fs->selection_entry)
1269 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1270 cmpl_updated_text (cmpl_state));
1275 selection_index = cmpl_last_valid_char (cmpl_state) -
1276 (strlen (rel_path) - strlen (rem_path));
1277 if (fs->selection_entry)
1278 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1283 if (fs->selection_entry)
1284 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1289 if (fs->selection_entry)
1290 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1292 if (fs->selection_entry)
1294 sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
1295 sizeof ("Selection: "));
1296 strcpy (sel_text, "Selection: ");
1297 strcat (sel_text, cmpl_reference_position (cmpl_state));
1299 gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
1303 if (fs->history_pulldown)
1305 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1312 gtk_file_selection_abort (GtkFileSelection *fs)
1316 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
1318 /* BEEP gdk_beep(); */
1320 if (fs->selection_entry)
1321 gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
1324 /**********************************************************************/
1325 /* External Interface */
1326 /**********************************************************************/
1328 /* The four completion state selectors
1331 cmpl_updated_text (CompletionState* cmpl_state)
1333 return cmpl_state->updated_text;
1337 cmpl_updated_dir (CompletionState* cmpl_state)
1339 return cmpl_state->re_complete;
1343 cmpl_reference_position (CompletionState* cmpl_state)
1345 return cmpl_state->reference_dir->fullname;
1349 cmpl_last_valid_char (CompletionState* cmpl_state)
1351 return cmpl_state->last_valid_char;
1355 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1359 strcpy (cmpl_state->updated_text, text);
1361 else if (text[0] == '~')
1366 dir = open_user_dir (text, cmpl_state);
1370 /* spencer says just return ~something, so
1371 * for now just do it. */
1372 strcpy (cmpl_state->updated_text, text);
1377 strcpy (cmpl_state->updated_text, dir->fullname);
1379 slash = strchr (text, '/');
1382 strcat (cmpl_state->updated_text, slash);
1387 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1388 strcat (cmpl_state->updated_text, "/");
1389 strcat (cmpl_state->updated_text, text);
1392 return cmpl_state->updated_text;
1395 /* The three completion selectors
1398 cmpl_this_completion (PossibleCompletion* pc)
1404 cmpl_is_directory (PossibleCompletion* pc)
1406 return pc->is_directory;
1410 cmpl_is_a_completion (PossibleCompletion* pc)
1412 return pc->is_a_completion;
1415 /**********************************************************************/
1416 /* Construction, deletion */
1417 /**********************************************************************/
1419 static CompletionState*
1420 cmpl_init_state (void)
1422 gchar getcwd_buf[2*MAXPATHLEN];
1423 CompletionState *new_state;
1425 new_state = g_new (CompletionState, 1);
1427 if (!getcwd (getcwd_buf, MAXPATHLEN))
1433 new_state->reference_dir = NULL;
1434 new_state->completion_dir = NULL;
1435 new_state->active_completion_dir = NULL;
1437 if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1439 /* if this fails, get_pwdb will fill it in. */
1440 new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1443 new_state->directory_storage = NULL;
1444 new_state->directory_sent_storage = NULL;
1445 new_state->last_valid_char = 0;
1446 new_state->updated_text = g_new (gchar, MAXPATHLEN);
1447 new_state->updated_text_alloc = MAXPATHLEN;
1448 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1449 new_state->the_completion.text_alloc = MAXPATHLEN;
1450 new_state->user_dir_name_buffer = NULL;
1451 new_state->user_directories = NULL;
1453 new_state->reference_dir = open_dir (getcwd_buf, new_state);
1455 if (!new_state->reference_dir)
1462 cmpl_free_dir_list(GList* dp0)
1467 free_dir (dp->data);
1475 cmpl_free_dir_sent_list(GList* dp0)
1480 free_dir_sent (dp->data);
1488 cmpl_free_state (CompletionState* cmpl_state)
1490 cmpl_free_dir_list(cmpl_state->directory_storage);
1491 cmpl_free_dir_sent_list(cmpl_state->directory_sent_storage);
1493 if (cmpl_state->user_dir_name_buffer)
1494 g_free (cmpl_state->user_dir_name_buffer);
1495 if (cmpl_state->user_directories)
1496 g_free (cmpl_state->user_directories);
1497 if (cmpl_state->the_completion.text)
1498 g_free (cmpl_state->the_completion.text);
1499 if (cmpl_state->updated_text)
1500 g_free (cmpl_state->updated_text);
1502 g_free (cmpl_state);
1506 free_dir(CompletionDir* dir)
1508 g_free(dir->fullname);
1513 free_dir_sent(CompletionDirSent* sent)
1515 g_free(sent->name_buffer);
1516 g_free(sent->entries);
1521 prune_memory_usage(CompletionState *cmpl_state)
1523 GList* cdsl = cmpl_state->directory_sent_storage;
1524 GList* cdl = cmpl_state->directory_storage;
1528 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1532 cmpl_free_dir_sent_list(cdsl->next);
1536 cmpl_state->directory_storage = NULL;
1538 if (cdl->data == cmpl_state->reference_dir)
1539 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1541 free_dir (cdl->data);
1548 /**********************************************************************/
1549 /* The main entrances. */
1550 /**********************************************************************/
1552 static PossibleCompletion*
1553 cmpl_completion_matches (gchar* text_to_complete,
1554 gchar** remaining_text,
1555 CompletionState* cmpl_state)
1558 PossibleCompletion *poss;
1560 prune_memory_usage(cmpl_state);
1562 g_assert(text_to_complete);
1564 cmpl_state->user_completion_index = -1;
1565 cmpl_state->last_completion_text = text_to_complete;
1566 cmpl_state->the_completion.text[0] = 0;
1567 cmpl_state->last_valid_char = 0;
1568 cmpl_state->updated_text_len = -1;
1569 cmpl_state->updated_text[0] = 0;
1570 cmpl_state->re_complete = FALSE;
1572 first_slash = strchr(text_to_complete, '/');
1574 if(text_to_complete[0] == '~' && !first_slash)
1576 /* Text starts with ~ and there is no slash, show all the
1577 * home directory completions.
1579 poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1581 update_cmpl(poss, cmpl_state);
1586 cmpl_state->reference_dir =
1587 open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1589 if(!cmpl_state->reference_dir)
1592 cmpl_state->completion_dir =
1593 find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1595 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1597 if(!cmpl_state->completion_dir)
1600 cmpl_state->completion_dir->cmpl_index = -1;
1601 cmpl_state->completion_dir->cmpl_parent = NULL;
1602 cmpl_state->completion_dir->cmpl_text = *remaining_text;
1604 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1606 cmpl_state->reference_dir = cmpl_state->completion_dir;
1608 poss = attempt_file_completion(cmpl_state);
1610 update_cmpl(poss, cmpl_state);
1615 static PossibleCompletion*
1616 cmpl_next_completion (CompletionState* cmpl_state)
1618 PossibleCompletion* poss = NULL;
1620 cmpl_state->the_completion.text[0] = 0;
1622 if(cmpl_state->user_completion_index >= 0)
1623 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1625 poss = attempt_file_completion(cmpl_state);
1627 update_cmpl(poss, cmpl_state);
1632 /**********************************************************************/
1633 /* Directory Operations */
1634 /**********************************************************************/
1636 /* Open the directory where completion will begin from, if possible. */
1637 static CompletionDir*
1638 open_ref_dir(gchar* text_to_complete,
1639 gchar** remaining_text,
1640 CompletionState* cmpl_state)
1643 CompletionDir *new_dir;
1645 first_slash = strchr(text_to_complete, '/');
1647 if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1649 new_dir = open_dir("/", cmpl_state);
1652 *remaining_text = text_to_complete + 1;
1654 else if (text_to_complete[0] == '~')
1656 new_dir = open_user_dir(text_to_complete, cmpl_state);
1661 *remaining_text = first_slash + 1;
1663 *remaining_text = text_to_complete + strlen(text_to_complete);
1672 *remaining_text = text_to_complete;
1674 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1679 new_dir->cmpl_index = -1;
1680 new_dir->cmpl_parent = NULL;
1686 /* open a directory by user name */
1687 static CompletionDir*
1688 open_user_dir(gchar* text_to_complete,
1689 CompletionState *cmpl_state)
1694 g_assert(text_to_complete && text_to_complete[0] == '~');
1696 first_slash = strchr(text_to_complete, '/');
1699 cmp_len = first_slash - text_to_complete - 1;
1701 cmp_len = strlen(text_to_complete + 1);
1706 if (!cmpl_state->user_home_dir &&
1707 !get_pwdb(cmpl_state))
1709 return open_dir(cmpl_state->user_home_dir, cmpl_state);
1714 char* copy = g_new(char, cmp_len + 1);
1716 strncpy(copy, text_to_complete + 1, cmp_len);
1718 pwd = getpwnam(copy);
1726 return open_dir(pwd->pw_dir, cmpl_state);
1730 /* open a directory relative the the current relative directory */
1731 static CompletionDir*
1732 open_relative_dir(gchar* dir_name,
1734 CompletionState *cmpl_state)
1736 gchar path_buf[2*MAXPATHLEN];
1738 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1740 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1744 strcpy(path_buf, dir->fullname);
1746 if(dir->fullname_len > 1)
1748 path_buf[dir->fullname_len] = '/';
1749 strcpy(path_buf + dir->fullname_len + 1, dir_name);
1753 strcpy(path_buf + dir->fullname_len, dir_name);
1756 return open_dir(path_buf, cmpl_state);
1759 /* after the cache lookup fails, really open a new directory */
1760 static CompletionDirSent*
1761 open_new_dir(gchar* dir_name, struct stat* sbuf)
1763 CompletionDirSent* sent;
1766 struct dirent *dirent_ptr;
1767 gint buffer_size = 0;
1768 gint entry_count = 0;
1770 struct stat ent_sbuf;
1771 char path_buf[MAXPATHLEN*2];
1774 sent = g_new(CompletionDirSent, 1);
1775 sent->mtime = sbuf->st_mtime;
1776 sent->inode = sbuf->st_ino;
1778 path_buf_len = strlen(dir_name);
1780 if (path_buf_len > MAXPATHLEN)
1782 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1786 strcpy(path_buf, dir_name);
1788 directory = opendir(dir_name);
1796 while((dirent_ptr = readdir(directory)) != NULL)
1798 int entry_len = strlen(dirent_ptr->d_name);
1799 buffer_size += entry_len + 1;
1802 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1804 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1805 closedir(directory);
1810 sent->name_buffer = g_new(gchar, buffer_size);
1811 sent->entries = g_new(CompletionDirEntry, entry_count);
1812 sent->entry_count = entry_count;
1814 buffer_ptr = sent->name_buffer;
1816 rewinddir(directory);
1818 for(i = 0; i < entry_count; i += 1)
1820 dirent_ptr = readdir(directory);
1825 closedir(directory);
1829 strcpy(buffer_ptr, dirent_ptr->d_name);
1830 sent->entries[i].entry_name = buffer_ptr;
1831 buffer_ptr += strlen(dirent_ptr->d_name);
1835 path_buf[path_buf_len] = '/';
1836 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1838 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1839 sent->entries[i].is_dir = 1;
1841 /* stat may fail, and we don't mind, since it could be a
1842 * dangling symlink. */
1843 sent->entries[i].is_dir = 0;
1846 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1848 closedir(directory);
1853 /* open a directory by absolute pathname */
1854 static CompletionDir*
1855 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1858 CompletionDirSent *sent;
1861 if(stat(dir_name, &sbuf) < 0)
1867 cdsl = cmpl_state->directory_sent_storage;
1873 if(sent->inode == sbuf.st_ino &&
1874 sent->mtime == sbuf.st_mtime)
1875 return attach_dir(sent, dir_name, cmpl_state);
1880 sent = open_new_dir(dir_name, &sbuf);
1883 cmpl_state->directory_sent_storage =
1884 g_list_prepend(cmpl_state->directory_sent_storage, sent);
1886 return attach_dir(sent, dir_name, cmpl_state);
1892 static CompletionDir*
1893 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1895 CompletionDir* new_dir;
1897 new_dir = g_new(CompletionDir, 1);
1899 cmpl_state->directory_storage =
1900 g_list_prepend(cmpl_state->directory_storage, new_dir);
1902 new_dir->sent = sent;
1903 new_dir->fullname = g_strdup(dir_name);
1904 new_dir->fullname_len = strlen(dir_name);
1910 correct_dir_fullname(CompletionDir* cmpl_dir)
1912 gint length = strlen(cmpl_dir->fullname);
1915 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
1919 strcpy(cmpl_dir->fullname, "/");
1920 cmpl_dir->fullname_len = 1;
1923 cmpl_dir->fullname[length - 2] = 0;
1926 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
1927 cmpl_dir->fullname[length - 2] = 0;
1928 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
1932 strcpy(cmpl_dir->fullname, "/");
1933 cmpl_dir->fullname_len = 1;
1937 if(stat(cmpl_dir->fullname, &sbuf) < 0)
1943 cmpl_dir->fullname[length - 2] = 0;
1945 if(!correct_parent(cmpl_dir, &sbuf))
1948 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
1952 strcpy(cmpl_dir->fullname, "/");
1953 cmpl_dir->fullname_len = 1;
1957 if(stat(cmpl_dir->fullname, &sbuf) < 0)
1963 cmpl_dir->fullname[length - 3] = 0;
1965 if(!correct_parent(cmpl_dir, &sbuf))
1969 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
1975 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
1982 last_slash = strrchr(cmpl_dir->fullname, '/');
1984 g_assert(last_slash);
1986 if(last_slash != cmpl_dir->fullname)
1987 { /* last_slash[0] = 0; */ }
1994 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2000 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2001 /* it wasn't a link */
2007 last_slash[0] = '/'; */
2009 /* it was a link, have to figure it out the hard way */
2011 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2016 g_free(cmpl_dir->fullname);
2018 cmpl_dir->fullname = new_name;
2024 find_parent_dir_fullname(gchar* dirname)
2026 gchar buffer[MAXPATHLEN];
2027 gchar buffer2[MAXPATHLEN];
2029 if(!getcwd(buffer, MAXPATHLEN))
2035 if(chdir(dirname) != 0 || chdir("..") != 0)
2041 if(!getcwd(buffer2, MAXPATHLEN))
2049 if(chdir(buffer) != 0)
2055 return g_strdup(buffer2);
2058 /**********************************************************************/
2059 /* Completion Operations */
2060 /**********************************************************************/
2062 static PossibleCompletion*
2063 attempt_homedir_completion(gchar* text_to_complete,
2064 CompletionState *cmpl_state)
2068 if (!cmpl_state->user_dir_name_buffer &&
2069 !get_pwdb(cmpl_state))
2071 length = strlen(text_to_complete) - 1;
2073 cmpl_state->user_completion_index += 1;
2075 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2077 index = first_diff_index(text_to_complete + 1,
2078 cmpl_state->user_directories
2079 [cmpl_state->user_completion_index].login);
2086 if(cmpl_state->last_valid_char < (index + 1))
2087 cmpl_state->last_valid_char = index + 1;
2088 cmpl_state->user_completion_index += 1;
2092 cmpl_state->the_completion.is_a_completion = 1;
2093 cmpl_state->the_completion.is_directory = 1;
2095 append_completion_text("~", cmpl_state);
2097 append_completion_text(cmpl_state->
2098 user_directories[cmpl_state->user_completion_index].login,
2101 return append_completion_text("/", cmpl_state);
2104 if(text_to_complete[1] ||
2105 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2107 cmpl_state->user_completion_index = -1;
2112 cmpl_state->user_completion_index += 1;
2113 cmpl_state->the_completion.is_a_completion = 1;
2114 cmpl_state->the_completion.is_directory = 1;
2116 return append_completion_text("~/", cmpl_state);
2120 /* returns the index (>= 0) of the first differing character,
2121 * PATTERN_MATCH if the completion matches */
2123 first_diff_index(gchar* pat, gchar* text)
2127 while(*pat && *text && *text == *pat)
2137 return PATTERN_MATCH;
2140 static PossibleCompletion*
2141 append_completion_text(gchar* text, CompletionState* cmpl_state)
2145 if(!cmpl_state->the_completion.text)
2148 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2150 if(cmpl_state->the_completion.text_alloc > len)
2152 strcat(cmpl_state->the_completion.text, text);
2153 return &cmpl_state->the_completion;
2156 while(i < len) { i <<= 1; }
2158 cmpl_state->the_completion.text_alloc = i;
2160 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2162 if(!cmpl_state->the_completion.text)
2166 strcat(cmpl_state->the_completion.text, text);
2167 return &cmpl_state->the_completion;
2171 static CompletionDir*
2172 find_completion_dir(gchar* text_to_complete,
2173 gchar** remaining_text,
2174 CompletionState* cmpl_state)
2176 gchar* first_slash = strchr(text_to_complete, '/');
2177 CompletionDir* dir = cmpl_state->reference_dir;
2178 *remaining_text = text_to_complete;
2182 gint len = first_slash - *remaining_text;
2184 gint found_index = -1;
2186 gchar* pat_buf = g_new (gchar, len + 1);
2188 strncpy(pat_buf, *remaining_text, len);
2191 for(i = 0; i < dir->sent->entry_count; i += 1)
2193 if(dir->sent->entries[i].is_dir &&
2194 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2195 FNMATCH_FLAGS)!= FNM_NOMATCH)
2212 CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
2221 next->cmpl_parent = dir;
2225 if(!correct_dir_fullname(dir))
2231 *remaining_text = first_slash + 1;
2232 first_slash = strchr(*remaining_text, '/');
2247 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
2251 if(!poss || !cmpl_is_a_completion(poss))
2254 cmpl_len = strlen(cmpl_this_completion(poss));
2256 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
2258 cmpl_state->updated_text =
2259 (gchar*)g_realloc(cmpl_state->updated_text,
2260 cmpl_state->updated_text_alloc);
2261 cmpl_state->updated_text_alloc = 2*cmpl_len;
2264 if(cmpl_state->updated_text_len < 0)
2266 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2267 cmpl_state->updated_text_len = cmpl_len;
2268 cmpl_state->re_complete = cmpl_is_directory(poss);
2270 else if(cmpl_state->updated_text_len == 0)
2272 cmpl_state->re_complete = FALSE;
2277 first_diff_index(cmpl_state->updated_text,
2278 cmpl_this_completion(poss));
2280 cmpl_state->re_complete = FALSE;
2282 if(first_diff == PATTERN_MATCH)
2285 if(first_diff > cmpl_state->updated_text_len)
2286 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2288 cmpl_state->updated_text_len = first_diff;
2289 cmpl_state->updated_text[first_diff] = 0;
2293 static PossibleCompletion*
2294 attempt_file_completion(CompletionState *cmpl_state)
2296 gchar *pat_buf, *first_slash;
2297 CompletionDir *dir = cmpl_state->active_completion_dir;
2299 dir->cmpl_index += 1;
2301 if(dir->cmpl_index == dir->sent->entry_count)
2303 if(dir->cmpl_parent == NULL)
2305 cmpl_state->active_completion_dir = NULL;
2311 cmpl_state->active_completion_dir = dir->cmpl_parent;
2313 return attempt_file_completion(cmpl_state);
2317 g_assert(dir->cmpl_text);
2319 first_slash = strchr(dir->cmpl_text, '/');
2323 gint len = first_slash - dir->cmpl_text;
2325 pat_buf = g_new (gchar, len + 1);
2326 strncpy(pat_buf, dir->cmpl_text, len);
2331 gint len = strlen(dir->cmpl_text);
2333 pat_buf = g_new (gchar, len + 2);
2334 strcpy(pat_buf, dir->cmpl_text);
2335 strcpy(pat_buf + len, "*");
2340 if(dir->sent->entries[dir->cmpl_index].is_dir)
2342 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2343 FNMATCH_FLAGS) != FNM_NOMATCH)
2345 CompletionDir* new_dir;
2347 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
2356 new_dir->cmpl_parent = dir;
2358 new_dir->cmpl_index = -1;
2359 new_dir->cmpl_text = first_slash + 1;
2361 cmpl_state->active_completion_dir = new_dir;
2364 return attempt_file_completion(cmpl_state);
2369 return attempt_file_completion(cmpl_state);
2375 return attempt_file_completion(cmpl_state);
2380 if(dir->cmpl_parent != NULL)
2382 append_completion_text(dir->fullname +
2383 strlen(cmpl_state->completion_dir->fullname) + 1,
2385 append_completion_text("/", cmpl_state);
2388 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2390 cmpl_state->the_completion.is_a_completion =
2391 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2392 FNMATCH_FLAGS) != FNM_NOMATCH);
2394 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2395 if(dir->sent->entries[dir->cmpl_index].is_dir)
2396 append_completion_text("/", cmpl_state);
2399 return &cmpl_state->the_completion;
2405 get_pwdb(CompletionState* cmpl_state)
2407 struct passwd *pwd_ptr;
2408 gchar* buf_ptr, *home_dir = NULL;
2409 gint len = 0, i, count = 0;
2411 if(cmpl_state->user_dir_name_buffer)
2415 while ((pwd_ptr = getpwent()) != NULL)
2417 len += strlen(pwd_ptr->pw_name);
2418 len += strlen(pwd_ptr->pw_dir);
2423 if (!cmpl_state->user_home_dir)
2425 /* the loser doesn't have $HOME set */
2428 pwd_ptr = getpwuid(getuid());
2434 home_dir = pwd_ptr->pw_dir;
2436 len += strlen(home_dir);
2442 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2443 cmpl_state->user_directories = g_new(CompletionUserDir, count);
2444 cmpl_state->user_directories_len = count;
2446 buf_ptr = cmpl_state->user_dir_name_buffer;
2448 if (!cmpl_state->user_home_dir)
2450 strcpy(buf_ptr, home_dir);
2451 cmpl_state->user_home_dir = buf_ptr;
2452 buf_ptr += strlen(buf_ptr);
2456 for(i = 0; i < count; i += 1)
2458 pwd_ptr = getpwent();
2465 strcpy(buf_ptr, pwd_ptr->pw_name);
2466 cmpl_state->user_directories[i].login = buf_ptr;
2467 buf_ptr += strlen(buf_ptr);
2469 strcpy(buf_ptr, pwd_ptr->pw_dir);
2470 cmpl_state->user_directories[i].homedir = buf_ptr;
2471 buf_ptr += strlen(buf_ptr);
2475 qsort(cmpl_state->user_directories,
2476 cmpl_state->user_directories_len,
2477 sizeof(CompletionUserDir),
2486 if(cmpl_state->user_dir_name_buffer)
2487 g_free(cmpl_state->user_dir_name_buffer);
2488 if(cmpl_state->user_directories)
2489 g_free(cmpl_state->user_directories);
2491 cmpl_state->user_dir_name_buffer = NULL;
2492 cmpl_state->user_directories = NULL;
2498 compare_user_dir(const void* a, const void* b)
2500 return strcmp((((CompletionUserDir*)a))->login,
2501 (((CompletionUserDir*)b))->login);
2505 compare_cmpl_dir(const void* a, const void* b)
2507 return strcmp((((CompletionDirEntry*)a))->entry_name,
2508 (((CompletionDirEntry*)b))->entry_name);
2512 cmpl_state_okay(CompletionState* cmpl_state)
2514 return cmpl_state && cmpl_state->reference_dir;
2518 cmpl_strerror(gint err)
2520 if(err == CMPL_ERRNO_TOO_LONG)
2521 return "Name too long";
2523 return g_strerror (err);