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
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
20 #include <sys/types.h>
22 #include <sys/param.h>
31 #include "gdk/gdkkeysyms.h"
32 #include "gtkbutton.h"
34 #include "gtkfilesel.h"
39 #include "gtklistitem.h"
41 #include "gtkscrolledwindow.h"
42 #include "gtksignal.h"
45 #include "gtkmenuitem.h"
46 #include "gtkoptionmenu.h"
48 #include "gtkdialog.h"
50 #define DIR_LIST_WIDTH 180
51 #define DIR_LIST_HEIGHT 180
52 #define FILE_LIST_WIDTH 180
53 #define FILE_LIST_HEIGHT 180
55 /* I've put this here so it doesn't get confused with the
56 * file completion interface */
57 typedef struct _HistoryCallbackArg HistoryCallbackArg;
59 struct _HistoryCallbackArg
66 typedef struct _CompletionState CompletionState;
67 typedef struct _CompletionDir CompletionDir;
68 typedef struct _CompletionDirSent CompletionDirSent;
69 typedef struct _CompletionDirEntry CompletionDirEntry;
70 typedef struct _CompletionUserDir CompletionUserDir;
71 typedef struct _PossibleCompletion PossibleCompletion;
73 /* Non-external file completion decls and structures */
75 /* A contant telling PRCS how many directories to cache. Its actually
76 * kept in a list, so the geometry isn't important. */
77 #define CMPL_DIRECTORY_CACHE_SIZE 10
79 /* A constant used to determine whether a substring was an exact
80 * match by first_diff_index()
82 #define PATTERN_MATCH -1
83 /* The arguments used by all fnmatch() calls below
85 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
87 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
89 /* This structure contains all the useful information about a directory
90 * for the purposes of filename completion. These structures are cached
91 * in the CompletionState struct. CompletionDir's are reference counted.
93 struct _CompletionDirSent
100 gchar *name_buffer; /* memory segment containing names of all entries */
102 struct _CompletionDirEntry *entries;
105 struct _CompletionDir
107 CompletionDirSent *sent;
112 struct _CompletionDir *cmpl_parent;
117 /* This structure contains pairs of directory entry names with a flag saying
118 * whether or not they are a valid directory. NOTE: This information is used
119 * to provide the caller with information about whether to update its completions
120 * or try to open a file. Since directories are cached by the directory mtime,
121 * a symlink which points to an invalid file (which will not be a directory),
122 * will not be reevaluated if that file is created, unless the containing
123 * directory is touched. I consider this case to be worth ignoring (josh).
125 struct _CompletionDirEntry
131 struct _CompletionUserDir
137 struct _PossibleCompletion
139 /* accessible fields, all are accessed externally by functions
143 gint is_a_completion;
151 struct _CompletionState
153 gint last_valid_char;
155 gint updated_text_len;
156 gint updated_text_alloc;
159 gchar *user_dir_name_buffer;
160 gint user_directories_len;
161 gchar *user_home_dir;
163 gchar *last_completion_text;
165 gint user_completion_index; /* if >= 0, currently completing ~user */
167 struct _CompletionDir *completion_dir; /* directory completing from */
168 struct _CompletionDir *active_completion_dir;
170 struct _PossibleCompletion the_completion;
172 struct _CompletionDir *reference_dir; /* initial directory */
174 GList* directory_storage;
175 GList* directory_sent_storage;
177 struct _CompletionUserDir *user_directories;
181 /* File completion functions which would be external, were they used
182 * outside of this file.
185 static CompletionState* cmpl_init_state (void);
186 static void cmpl_free_state (CompletionState *cmpl_state);
187 static gint cmpl_state_okay (CompletionState* cmpl_state);
188 static gchar* cmpl_strerror (gint);
190 static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete,
191 gchar **remaining_text,
192 CompletionState *cmpl_state);
194 /* Returns a name for consideration, possibly a completion, this name
195 * will be invalid after the next call to cmpl_next_completion.
197 static char* cmpl_this_completion (PossibleCompletion*);
199 /* True if this completion matches the given text. Otherwise, this
200 * output can be used to have a list of non-completions.
202 static gint cmpl_is_a_completion (PossibleCompletion*);
204 /* True if the completion is a directory
206 static gint cmpl_is_directory (PossibleCompletion*);
208 /* Obtains the next completion, or NULL
210 static PossibleCompletion* cmpl_next_completion (CompletionState*);
212 /* Updating completions: the return value of cmpl_updated_text() will
213 * be text_to_complete completed as much as possible after the most
214 * recent call to cmpl_completion_matches. For the present
215 * application, this is the suggested replacement for the user's input
216 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
219 static gchar* cmpl_updated_text (CompletionState* cmpl_state);
221 /* After updating, to see if the completion was a directory, call
222 * this. If it was, you should consider re-calling completion_matches.
224 static gint cmpl_updated_dir (CompletionState* cmpl_state);
226 /* Current location: if using file completion, return the current
227 * directory, from which file completion begins. More specifically,
228 * the cwd concatenated with all exact completions up to the last
229 * directory delimiter('/').
231 static gchar* cmpl_reference_position (CompletionState* cmpl_state);
233 /* backing up: if cmpl_completion_matches returns NULL, you may query
234 * the index of the last completable character into cmpl_updated_text.
236 static gint cmpl_last_valid_char (CompletionState* cmpl_state);
238 /* When the user selects a non-directory, call cmpl_completion_fullname
239 * to get the full name of the selected file.
241 static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
244 /* Directory operations. */
245 static CompletionDir* open_ref_dir (gchar* text_to_complete,
246 gchar** remaining_text,
247 CompletionState* cmpl_state);
248 static CompletionDir* open_dir (gchar* dir_name,
249 CompletionState* cmpl_state);
250 static CompletionDir* open_user_dir (gchar* text_to_complete,
251 CompletionState *cmpl_state);
252 static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
253 CompletionState *cmpl_state);
254 static CompletionDirSent* open_new_dir (gchar* dir_name, struct stat* sbuf);
255 static gint correct_dir_fullname (CompletionDir* cmpl_dir);
256 static gint correct_parent (CompletionDir* cmpl_dir,
258 static gchar* find_parent_dir_fullname (gchar* dirname);
259 static CompletionDir* attach_dir (CompletionDirSent* sent,
261 CompletionState *cmpl_state);
262 static void free_dir_sent (CompletionDirSent* sent);
263 static void free_dir (CompletionDir *dir);
264 static void prune_memory_usage(CompletionState *cmpl_state);
266 /* Completion operations */
267 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
268 CompletionState *cmpl_state);
269 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
270 static CompletionDir* find_completion_dir(gchar* text_to_complete,
271 gchar** remaining_text,
272 CompletionState* cmpl_state);
273 static PossibleCompletion* append_completion_text(gchar* text,
274 CompletionState* cmpl_state);
275 static gint get_pwdb(CompletionState* cmpl_state);
276 static gint first_diff_index(gchar* pat, gchar* text);
277 static gint compare_user_dir(const void* a, const void* b);
278 static gint compare_cmpl_dir(const void* a, const void* b);
279 static void update_cmpl(PossibleCompletion* poss,
280 CompletionState* cmpl_state);
282 static void gtk_file_selection_class_init (GtkFileSelectionClass *klass);
283 static void gtk_file_selection_init (GtkFileSelection *filesel);
284 static void gtk_file_selection_destroy (GtkObject *object);
285 static gint gtk_file_selection_key_press (GtkWidget *widget,
289 static void gtk_file_selection_file_button (GtkWidget *widget,
292 GdkEventButton *bevent,
295 static void gtk_file_selection_dir_button (GtkWidget *widget,
298 GdkEventButton *bevent,
301 static void gtk_file_selection_populate (GtkFileSelection *fs,
304 static void gtk_file_selection_abort (GtkFileSelection *fs);
306 static void gtk_file_selection_update_history_menu (GtkFileSelection *fs,
309 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
310 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
311 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
315 static GtkWindowClass *parent_class = NULL;
317 /* Saves errno when something cmpl does fails. */
318 static gint cmpl_errno;
321 gtk_file_selection_get_type ()
323 static guint file_selection_type = 0;
325 if (!file_selection_type)
327 GtkTypeInfo filesel_info =
330 sizeof (GtkFileSelection),
331 sizeof (GtkFileSelectionClass),
332 (GtkClassInitFunc) gtk_file_selection_class_init,
333 (GtkObjectInitFunc) gtk_file_selection_init,
334 (GtkArgSetFunc) NULL,
335 (GtkArgGetFunc) NULL,
338 file_selection_type = gtk_type_unique (gtk_window_get_type (), &filesel_info);
341 return file_selection_type;
345 gtk_file_selection_class_init (GtkFileSelectionClass *class)
347 GtkObjectClass *object_class;
349 object_class = (GtkObjectClass*) class;
351 parent_class = gtk_type_class (gtk_window_get_type ());
353 object_class->destroy = gtk_file_selection_destroy;
357 gtk_file_selection_init (GtkFileSelection *filesel)
359 GtkWidget *entry_vbox;
361 GtkWidget *list_hbox;
362 GtkWidget *confirm_area;
363 GtkWidget *pulldown_hbox;
364 char *dir_title [] = { "Directories", };
365 char *file_title [] = { "Files", };
367 filesel->cmpl_state = cmpl_init_state ();
369 /* The dialog-sized vertical box */
370 filesel->main_vbox = gtk_vbox_new (FALSE, 10);
371 gtk_container_border_width (GTK_CONTAINER (filesel), 10);
372 gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
373 gtk_widget_show (filesel->main_vbox);
375 /* The horizontal box containing create, rename etc. buttons */
376 filesel->button_area = gtk_hbutton_box_new ();
377 gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START);
378 gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0);
379 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area,
381 gtk_widget_show (filesel->button_area);
383 gtk_file_selection_show_fileop_buttons(filesel);
385 /* hbox for pulldown menu */
386 pulldown_hbox = gtk_hbox_new (TRUE, 5);
387 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
388 gtk_widget_show (pulldown_hbox);
391 filesel->history_pulldown = gtk_option_menu_new ();
392 gtk_widget_show (filesel->history_pulldown);
393 gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown,
396 /* The horizontal box containing the directory and file listboxes */
397 list_hbox = gtk_hbox_new (FALSE, 5);
398 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
399 gtk_widget_show (list_hbox);
401 /* The directories clist */
402 filesel->dir_list = gtk_clist_new_with_titles (1, dir_title);
403 gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
404 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
405 (GtkSignalFunc) gtk_file_selection_dir_button,
407 gtk_clist_set_policy (GTK_CLIST (filesel->dir_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
408 gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
409 gtk_container_border_width (GTK_CONTAINER (filesel->dir_list), 5);
410 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->dir_list, TRUE, TRUE, 0);
411 gtk_widget_show (filesel->dir_list);
413 /* The files clist */
414 filesel->file_list = gtk_clist_new_with_titles (1, file_title);
415 gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
416 gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
417 (GtkSignalFunc) gtk_file_selection_file_button,
419 gtk_clist_set_policy (GTK_CLIST (filesel->file_list), GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC);
420 gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
421 gtk_container_border_width (GTK_CONTAINER (filesel->file_list), 5);
422 gtk_box_pack_start (GTK_BOX (list_hbox), filesel->file_list, TRUE, TRUE, 0);
423 gtk_widget_show (filesel->file_list);
425 /* action area for packing buttons into. */
426 filesel->action_area = gtk_hbox_new (TRUE, 0);
427 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area,
429 gtk_widget_show (filesel->action_area);
431 /* The OK/Cancel button area */
432 confirm_area = gtk_hbutton_box_new ();
433 gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END);
434 gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5);
435 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0);
436 gtk_widget_show (confirm_area);
439 filesel->ok_button = gtk_button_new_with_label ("OK");
440 GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
441 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0);
442 gtk_widget_grab_default (filesel->ok_button);
443 gtk_widget_show (filesel->ok_button);
445 /* The Cancel button */
446 filesel->cancel_button = gtk_button_new_with_label ("Cancel");
447 GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
448 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0);
449 gtk_widget_show (filesel->cancel_button);
451 /* The selection entry widget */
452 entry_vbox = gtk_vbox_new (FALSE, 2);
453 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
454 gtk_widget_show (entry_vbox);
456 filesel->selection_text = label = gtk_label_new ("");
457 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
458 gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
459 gtk_widget_show (label);
461 filesel->selection_entry = gtk_entry_new ();
462 gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
463 (GtkSignalFunc) gtk_file_selection_key_press, filesel);
464 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
465 (GtkSignalFunc) gtk_widget_grab_default,
466 GTK_OBJECT (filesel->ok_button));
467 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
468 (GtkSignalFunc) gtk_button_clicked,
469 GTK_OBJECT (filesel->ok_button));
470 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
471 gtk_widget_show (filesel->selection_entry);
473 if (!cmpl_state_okay (filesel->cmpl_state))
477 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
479 gtk_label_set (GTK_LABEL (filesel->selection_text), err_buf);
483 gtk_file_selection_populate (filesel, "", FALSE);
486 gtk_widget_grab_focus (filesel->selection_entry);
490 gtk_file_selection_new (const gchar *title)
492 GtkFileSelection *filesel;
494 filesel = gtk_type_new (gtk_file_selection_get_type ());
495 gtk_window_set_title (GTK_WINDOW (filesel), title);
497 return GTK_WIDGET (filesel);
501 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
503 g_return_if_fail (filesel != NULL);
504 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
506 /* delete, create directory, and rename */
507 if (!filesel->fileop_c_dir)
509 filesel->fileop_c_dir = gtk_button_new_with_label ("Create Dir");
510 gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
511 (GtkSignalFunc) gtk_file_selection_create_dir,
513 gtk_box_pack_start (GTK_BOX (filesel->button_area),
514 filesel->fileop_c_dir, TRUE, TRUE, 0);
515 gtk_widget_show (filesel->fileop_c_dir);
518 if (!filesel->fileop_del_file)
520 filesel->fileop_del_file = gtk_button_new_with_label ("Delete File");
521 gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
522 (GtkSignalFunc) gtk_file_selection_delete_file,
524 gtk_box_pack_start (GTK_BOX (filesel->button_area),
525 filesel->fileop_del_file, TRUE, TRUE, 0);
526 gtk_widget_show (filesel->fileop_del_file);
529 if (!filesel->fileop_ren_file)
531 filesel->fileop_ren_file = gtk_button_new_with_label ("Rename File");
532 gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
533 (GtkSignalFunc) gtk_file_selection_rename_file,
535 gtk_box_pack_start (GTK_BOX (filesel->button_area),
536 filesel->fileop_ren_file, TRUE, TRUE, 0);
537 gtk_widget_show (filesel->fileop_ren_file);
540 gtk_widget_queue_resize(GTK_WIDGET(filesel));
544 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
546 g_return_if_fail (filesel != NULL);
547 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
549 if (filesel->fileop_ren_file)
551 gtk_widget_destroy (filesel->fileop_ren_file);
552 filesel->fileop_ren_file = NULL;
555 if (filesel->fileop_del_file)
557 gtk_widget_destroy (filesel->fileop_del_file);
558 filesel->fileop_del_file = NULL;
561 if (filesel->fileop_c_dir)
563 gtk_widget_destroy (filesel->fileop_c_dir);
564 filesel->fileop_c_dir = NULL;
571 gtk_file_selection_set_filename (GtkFileSelection *filesel,
572 const gchar *filename)
574 char buf[MAXPATHLEN];
575 const char *name, *last_slash;
577 g_return_if_fail (filesel != NULL);
578 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
579 g_return_if_fail (filename != NULL);
581 last_slash = strrchr (filename, '/');
590 gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
592 strncpy (buf, filename, len);
595 name = last_slash + 1;
598 gtk_file_selection_populate (filesel, buf, FALSE);
600 if (filesel->selection_entry)
601 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
605 gtk_file_selection_get_filename (GtkFileSelection *filesel)
607 static char nothing[2] = "";
611 g_return_val_if_fail (filesel != NULL, nothing);
612 g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
614 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
617 filename = cmpl_completion_fullname (text, filesel->cmpl_state);
625 gtk_file_selection_destroy (GtkObject *object)
627 GtkFileSelection *filesel;
629 HistoryCallbackArg *callback_arg;
631 g_return_if_fail (object != NULL);
632 g_return_if_fail (GTK_IS_FILE_SELECTION (object));
634 filesel = GTK_FILE_SELECTION (object);
636 if (filesel->fileop_dialog)
637 gtk_widget_destroy (filesel->fileop_dialog);
639 if (filesel->history_list)
641 list = filesel->history_list;
644 callback_arg = list->data;
645 g_free (callback_arg->directory);
646 g_free (callback_arg);
649 g_list_free (filesel->history_list);
650 filesel->history_list = NULL;
653 cmpl_free_state (filesel->cmpl_state);
654 filesel->cmpl_state = NULL;
656 if (GTK_OBJECT_CLASS (parent_class)->destroy)
657 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
660 /* Begin file operations callbacks */
663 gtk_file_selection_fileop_error (gchar *error_message)
670 g_return_if_fail (error_message != NULL);
673 dialog = gtk_dialog_new ();
675 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
676 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
679 gtk_window_set_title (GTK_WINDOW (dialog), "Error");
680 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
682 vbox = gtk_vbox_new(FALSE, 0);
683 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
684 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
686 gtk_widget_show(vbox);
688 label = gtk_label_new(error_message);
689 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
690 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
691 gtk_widget_show(label);
693 /* yes, we free it */
694 g_free (error_message);
697 button = gtk_button_new_with_label ("Close");
698 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
699 (GtkSignalFunc) gtk_widget_destroy,
701 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
702 button, TRUE, TRUE, 0);
703 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
704 gtk_widget_grab_default(button);
705 gtk_widget_show (button);
707 gtk_widget_show (dialog);
711 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
713 GtkFileSelection *fs = data;
715 g_return_if_fail (fs != NULL);
716 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
718 fs->fileop_dialog = NULL;
723 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
725 GtkFileSelection *fs = data;
730 CompletionState *cmpl_state;
732 g_return_if_fail (fs != NULL);
733 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
735 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
736 cmpl_state = (CompletionState*) fs->cmpl_state;
737 path = cmpl_reference_position (cmpl_state);
739 full_path = g_strconcat (path, "/", dirname, NULL);
740 if ( (mkdir (full_path, 0755) < 0) )
742 buf = g_strconcat ("Error creating directory \"", dirname, "\": ",
743 g_strerror(errno), NULL);
744 gtk_file_selection_fileop_error (buf);
748 gtk_widget_destroy (fs->fileop_dialog);
749 gtk_file_selection_populate (fs, "", FALSE);
753 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
755 GtkFileSelection *fs = data;
761 g_return_if_fail (fs != NULL);
762 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
764 if (fs->fileop_dialog)
768 fs->fileop_dialog = dialog = gtk_dialog_new ();
769 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
770 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
772 gtk_window_set_title (GTK_WINDOW (dialog), "Create Directory");
773 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
774 gtk_widget_show (dialog);
776 vbox = gtk_vbox_new(FALSE, 0);
777 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
778 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
780 gtk_widget_show(vbox);
782 label = gtk_label_new("Directory name:");
783 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
784 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
785 gtk_widget_show(label);
787 /* The directory entry widget */
788 fs->fileop_entry = gtk_entry_new ();
789 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
791 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
792 gtk_widget_show (fs->fileop_entry);
795 button = gtk_button_new_with_label ("Create");
796 gtk_signal_connect (GTK_OBJECT (button), "clicked",
797 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
799 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
800 button, TRUE, TRUE, 0);
801 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
802 gtk_widget_show(button);
804 button = gtk_button_new_with_label ("Cancel");
805 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
806 (GtkSignalFunc) gtk_widget_destroy,
808 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
809 button, TRUE, TRUE, 0);
810 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
811 gtk_widget_grab_default(button);
812 gtk_widget_show (button);
816 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
818 GtkFileSelection *fs = data;
819 CompletionState *cmpl_state;
824 g_return_if_fail (fs != NULL);
825 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
827 cmpl_state = (CompletionState*) fs->cmpl_state;
828 path = cmpl_reference_position (cmpl_state);
830 full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
831 if ( (unlink (full_path) < 0) )
833 buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ",
834 g_strerror(errno), NULL);
835 gtk_file_selection_fileop_error (buf);
839 gtk_widget_destroy (fs->fileop_dialog);
840 gtk_file_selection_populate (fs, "", FALSE);
844 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
846 GtkFileSelection *fs = data;
854 g_return_if_fail (fs != NULL);
855 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
857 if (fs->fileop_dialog)
860 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
861 if (strlen(filename) < 1)
864 fs->fileop_file = filename;
867 fs->fileop_dialog = dialog = gtk_dialog_new ();
868 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
869 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
871 gtk_window_set_title (GTK_WINDOW (dialog), "Delete File");
872 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
874 vbox = gtk_vbox_new(FALSE, 0);
875 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
876 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
878 gtk_widget_show(vbox);
880 buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
881 label = gtk_label_new(buf);
882 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
883 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
884 gtk_widget_show(label);
888 button = gtk_button_new_with_label ("Delete");
889 gtk_signal_connect (GTK_OBJECT (button), "clicked",
890 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
892 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
893 button, TRUE, TRUE, 0);
894 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
895 gtk_widget_show(button);
897 button = gtk_button_new_with_label ("Cancel");
898 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
899 (GtkSignalFunc) gtk_widget_destroy,
901 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
902 button, TRUE, TRUE, 0);
903 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
904 gtk_widget_grab_default(button);
905 gtk_widget_show (button);
907 gtk_widget_show (dialog);
911 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
913 GtkFileSelection *fs = data;
919 CompletionState *cmpl_state;
921 g_return_if_fail (fs != NULL);
922 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
924 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
925 cmpl_state = (CompletionState*) fs->cmpl_state;
926 path = cmpl_reference_position (cmpl_state);
928 new_filename = g_strconcat (path, "/", file, NULL);
929 old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
931 if ( (rename (old_filename, new_filename)) < 0)
933 buf = g_strconcat ("Error renaming file \"", file, "\": ",
934 g_strerror(errno), NULL);
935 gtk_file_selection_fileop_error (buf);
937 g_free (new_filename);
938 g_free (old_filename);
940 gtk_widget_destroy (fs->fileop_dialog);
941 gtk_file_selection_populate (fs, "", FALSE);
945 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
947 GtkFileSelection *fs = data;
954 g_return_if_fail (fs != NULL);
955 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
957 if (fs->fileop_dialog)
960 fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
961 if (strlen(fs->fileop_file) < 1)
965 fs->fileop_dialog = dialog = gtk_dialog_new ();
966 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
967 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
969 gtk_window_set_title (GTK_WINDOW (dialog), "Rename File");
970 gtk_window_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
971 gtk_widget_show (dialog);
973 vbox = gtk_vbox_new(FALSE, 0);
974 gtk_container_border_width(GTK_CONTAINER(vbox), 8);
975 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
977 gtk_widget_show(vbox);
979 buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
980 label = gtk_label_new(buf);
981 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
982 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
983 gtk_widget_show(label);
986 /* New filename entry */
987 fs->fileop_entry = gtk_entry_new ();
988 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
990 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
991 gtk_widget_show (fs->fileop_entry);
993 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
994 gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
995 0, strlen (fs->fileop_file));
998 button = gtk_button_new_with_label ("Rename");
999 gtk_signal_connect (GTK_OBJECT (button), "clicked",
1000 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
1002 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1003 button, TRUE, TRUE, 0);
1004 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1005 gtk_widget_show(button);
1007 button = gtk_button_new_with_label ("Cancel");
1008 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1009 (GtkSignalFunc) gtk_widget_destroy,
1011 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1012 button, TRUE, TRUE, 0);
1013 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1014 gtk_widget_grab_default(button);
1015 gtk_widget_show (button);
1020 gtk_file_selection_key_press (GtkWidget *widget,
1024 GtkFileSelection *fs;
1027 g_return_val_if_fail (widget != NULL, FALSE);
1028 g_return_val_if_fail (event != NULL, FALSE);
1030 if (event->keyval == GDK_Tab)
1032 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1034 fs = GTK_FILE_SELECTION (user_data);
1035 text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1036 gtk_file_selection_populate (fs, text, TRUE);
1046 gtk_file_selection_history_callback (GtkWidget *widget, gpointer data)
1048 GtkFileSelection *fs = data;
1049 HistoryCallbackArg *callback_arg;
1052 g_return_if_fail (fs != NULL);
1053 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1055 list = fs->history_list;
1058 callback_arg = list->data;
1060 if (callback_arg->menu_item == widget)
1062 gtk_file_selection_populate (fs, callback_arg->directory, FALSE);
1071 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1072 gchar *current_directory)
1074 HistoryCallbackArg *callback_arg;
1075 GtkWidget *menu_item;
1081 g_return_if_fail (fs != NULL);
1082 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1083 g_return_if_fail (current_directory != NULL);
1085 list = fs->history_list;
1087 if (fs->history_menu)
1090 callback_arg = list->data;
1091 g_free (callback_arg->directory);
1092 g_free (callback_arg);
1095 g_list_free (fs->history_list);
1096 fs->history_list = NULL;
1098 gtk_widget_destroy (fs->history_menu);
1101 fs->history_menu = gtk_menu_new();
1103 current_dir = g_strdup (current_directory);
1105 dir_len = strlen (current_dir);
1107 for (i = dir_len; i >= 0; i--)
1109 /* the i == dir_len is to catch the full path for the first
1111 if ( (current_dir[i] == '/') || (i == dir_len))
1113 /* another small hack to catch the full path */
1115 current_dir[i + 1] = '\0';
1116 menu_item = gtk_menu_item_new_with_label (current_dir);
1118 callback_arg = g_new (HistoryCallbackArg, 1);
1119 callback_arg->menu_item = menu_item;
1121 /* since the autocompletion gets confused if you don't
1122 * supply a trailing '/' on a dir entry, set the full
1123 * (current) path to "" which just refreshes the filesel */
1125 callback_arg->directory = g_strdup ("");
1127 callback_arg->directory = g_strdup (current_dir);
1130 fs->history_list = g_list_append (fs->history_list, callback_arg);
1132 gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
1133 (GtkSignalFunc) gtk_file_selection_history_callback,
1135 gtk_menu_append (GTK_MENU (fs->history_menu), menu_item);
1136 gtk_widget_show (menu_item);
1140 gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown),
1142 g_free (current_dir);
1146 gtk_file_selection_file_button (GtkWidget *widget,
1149 GdkEventButton *bevent,
1152 GtkFileSelection *fs = NULL;
1153 gchar *filename, *temp = NULL;
1155 g_return_if_fail (GTK_IS_CLIST (widget));
1158 g_return_if_fail (fs != NULL);
1159 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1161 gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1162 filename = g_strdup (temp);
1164 if (bevent && filename)
1166 switch (bevent->type)
1168 case GDK_BUTTON_PRESS:
1169 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1172 case GDK_2BUTTON_PRESS:
1173 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1186 gtk_file_selection_dir_button (GtkWidget *widget,
1189 GdkEventButton *bevent,
1192 GtkFileSelection *fs = NULL;
1193 gchar *filename, *temp = NULL;
1195 g_return_if_fail (GTK_IS_CLIST (widget));
1197 fs = GTK_FILE_SELECTION (user_data);
1198 g_return_if_fail (fs != NULL);
1199 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1201 gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1202 filename = g_strdup (temp);
1204 if (bevent && filename)
1206 switch (bevent->type)
1208 case GDK_BUTTON_PRESS:
1209 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1212 case GDK_2BUTTON_PRESS:
1213 gtk_file_selection_populate (fs, filename, FALSE);
1226 gtk_file_selection_populate (GtkFileSelection *fs,
1230 CompletionState *cmpl_state;
1231 PossibleCompletion* poss;
1234 gchar* rem_path = rel_path;
1237 gint did_recurse = FALSE;
1238 gint possible_count = 0;
1239 gint selection_index = -1;
1240 gint file_list_width;
1241 gint dir_list_width;
1243 g_return_if_fail (fs != NULL);
1244 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1246 cmpl_state = (CompletionState*) fs->cmpl_state;
1247 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1249 if (!cmpl_state_okay (cmpl_state))
1251 /* Something went wrong. */
1252 gtk_file_selection_abort (fs);
1256 g_assert (cmpl_state->reference_dir);
1258 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1259 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1260 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1261 gtk_clist_clear (GTK_CLIST (fs->file_list));
1263 /* Set the dir_list to include ./ and ../ */
1266 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1269 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1271 /*reset the max widths of the lists*/
1272 dir_list_width = gdk_string_width(fs->dir_list->style->font,"../");
1273 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width);
1274 file_list_width = 1;
1275 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width);
1279 if (cmpl_is_a_completion (poss))
1281 possible_count += 1;
1283 filename = cmpl_this_completion (poss);
1287 if (cmpl_is_directory (poss))
1289 if (strcmp (filename, "./") != 0 &&
1290 strcmp (filename, "../") != 0)
1292 int width = gdk_string_width(fs->dir_list->style->font,
1294 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1295 if(width > dir_list_width)
1297 dir_list_width = width;
1298 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,
1305 int width = gdk_string_width(fs->file_list->style->font,
1307 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1308 if(width > file_list_width)
1310 file_list_width = width;
1311 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1317 poss = cmpl_next_completion (cmpl_state);
1320 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1321 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1323 /* File lists are set. */
1325 g_assert (cmpl_state->reference_dir);
1330 /* User is trying to complete filenames, so advance the user's input
1331 * string to the updated_text, which is the common leading substring
1332 * of all possible completions, and if its a directory attempt
1333 * attempt completions in it. */
1335 if (cmpl_updated_text (cmpl_state)[0])
1338 if (cmpl_updated_dir (cmpl_state))
1340 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1344 gtk_file_selection_populate (fs, dir_name, TRUE);
1350 if (fs->selection_entry)
1351 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1352 cmpl_updated_text (cmpl_state));
1357 selection_index = cmpl_last_valid_char (cmpl_state) -
1358 (strlen (rel_path) - strlen (rem_path));
1359 if (fs->selection_entry)
1360 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1365 if (fs->selection_entry)
1366 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
1371 if (fs->selection_entry)
1372 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1374 if (fs->selection_entry)
1376 sel_text = g_new (char, strlen (cmpl_reference_position (cmpl_state)) +
1377 sizeof ("Selection: "));
1378 strcpy (sel_text, "Selection: ");
1379 strcat (sel_text, cmpl_reference_position (cmpl_state));
1381 gtk_label_set (GTK_LABEL (fs->selection_text), sel_text);
1385 if (fs->history_pulldown)
1387 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1394 gtk_file_selection_abort (GtkFileSelection *fs)
1398 sprintf (err_buf, "Directory unreadable: %s", cmpl_strerror (cmpl_errno));
1400 /* BEEP gdk_beep(); */
1402 if (fs->selection_entry)
1403 gtk_label_set (GTK_LABEL (fs->selection_text), err_buf);
1406 /**********************************************************************/
1407 /* External Interface */
1408 /**********************************************************************/
1410 /* The four completion state selectors
1413 cmpl_updated_text (CompletionState* cmpl_state)
1415 return cmpl_state->updated_text;
1419 cmpl_updated_dir (CompletionState* cmpl_state)
1421 return cmpl_state->re_complete;
1425 cmpl_reference_position (CompletionState* cmpl_state)
1427 return cmpl_state->reference_dir->fullname;
1431 cmpl_last_valid_char (CompletionState* cmpl_state)
1433 return cmpl_state->last_valid_char;
1437 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
1441 strcpy (cmpl_state->updated_text, text);
1443 else if (text[0] == '~')
1448 dir = open_user_dir (text, cmpl_state);
1452 /* spencer says just return ~something, so
1453 * for now just do it. */
1454 strcpy (cmpl_state->updated_text, text);
1459 strcpy (cmpl_state->updated_text, dir->fullname);
1461 slash = strchr (text, '/');
1464 strcat (cmpl_state->updated_text, slash);
1469 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
1470 strcat (cmpl_state->updated_text, "/");
1471 strcat (cmpl_state->updated_text, text);
1474 return cmpl_state->updated_text;
1477 /* The three completion selectors
1480 cmpl_this_completion (PossibleCompletion* pc)
1486 cmpl_is_directory (PossibleCompletion* pc)
1488 return pc->is_directory;
1492 cmpl_is_a_completion (PossibleCompletion* pc)
1494 return pc->is_a_completion;
1497 /**********************************************************************/
1498 /* Construction, deletion */
1499 /**********************************************************************/
1501 static CompletionState*
1502 cmpl_init_state (void)
1504 gchar getcwd_buf[2*MAXPATHLEN];
1505 CompletionState *new_state;
1507 new_state = g_new (CompletionState, 1);
1509 /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
1510 * and, if that wasn't bad enough, hangs in doing so.
1512 #if defined(sun) && !defined(__SVR4)
1513 if (!getwd (getcwd_buf))
1515 if (!getcwd (getcwd_buf, MAXPATHLEN))
1522 new_state->reference_dir = NULL;
1523 new_state->completion_dir = NULL;
1524 new_state->active_completion_dir = NULL;
1526 if ((new_state->user_home_dir = getenv("HOME")) != NULL)
1528 /* if this fails, get_pwdb will fill it in. */
1529 new_state->user_home_dir = g_strdup(new_state->user_home_dir);
1532 new_state->directory_storage = NULL;
1533 new_state->directory_sent_storage = NULL;
1534 new_state->last_valid_char = 0;
1535 new_state->updated_text = g_new (gchar, MAXPATHLEN);
1536 new_state->updated_text_alloc = MAXPATHLEN;
1537 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
1538 new_state->the_completion.text_alloc = MAXPATHLEN;
1539 new_state->user_dir_name_buffer = NULL;
1540 new_state->user_directories = NULL;
1542 new_state->reference_dir = open_dir (getcwd_buf, new_state);
1544 if (!new_state->reference_dir)
1551 cmpl_free_dir_list(GList* dp0)
1556 free_dir (dp->data);
1564 cmpl_free_dir_sent_list(GList* dp0)
1569 free_dir_sent (dp->data);
1577 cmpl_free_state (CompletionState* cmpl_state)
1579 cmpl_free_dir_list (cmpl_state->directory_storage);
1580 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
1582 if (cmpl_state->user_dir_name_buffer)
1583 g_free (cmpl_state->user_dir_name_buffer);
1584 if (cmpl_state->user_home_dir)
1585 g_free (cmpl_state->user_home_dir);
1586 if (cmpl_state->user_directories)
1587 g_free (cmpl_state->user_directories);
1588 if (cmpl_state->the_completion.text)
1589 g_free (cmpl_state->the_completion.text);
1590 if (cmpl_state->updated_text)
1591 g_free (cmpl_state->updated_text);
1593 g_free (cmpl_state);
1597 free_dir(CompletionDir* dir)
1599 g_free(dir->fullname);
1604 free_dir_sent(CompletionDirSent* sent)
1606 g_free(sent->name_buffer);
1607 g_free(sent->entries);
1612 prune_memory_usage(CompletionState *cmpl_state)
1614 GList* cdsl = cmpl_state->directory_sent_storage;
1615 GList* cdl = cmpl_state->directory_storage;
1619 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
1623 cmpl_free_dir_sent_list(cdsl->next);
1627 cmpl_state->directory_storage = NULL;
1629 if (cdl->data == cmpl_state->reference_dir)
1630 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
1632 free_dir (cdl->data);
1639 /**********************************************************************/
1640 /* The main entrances. */
1641 /**********************************************************************/
1643 static PossibleCompletion*
1644 cmpl_completion_matches (gchar* text_to_complete,
1645 gchar** remaining_text,
1646 CompletionState* cmpl_state)
1649 PossibleCompletion *poss;
1651 prune_memory_usage(cmpl_state);
1653 g_assert(text_to_complete);
1655 cmpl_state->user_completion_index = -1;
1656 cmpl_state->last_completion_text = text_to_complete;
1657 cmpl_state->the_completion.text[0] = 0;
1658 cmpl_state->last_valid_char = 0;
1659 cmpl_state->updated_text_len = -1;
1660 cmpl_state->updated_text[0] = 0;
1661 cmpl_state->re_complete = FALSE;
1663 first_slash = strchr(text_to_complete, '/');
1665 if(text_to_complete[0] == '~' && !first_slash)
1667 /* Text starts with ~ and there is no slash, show all the
1668 * home directory completions.
1670 poss = attempt_homedir_completion(text_to_complete, cmpl_state);
1672 update_cmpl(poss, cmpl_state);
1677 cmpl_state->reference_dir =
1678 open_ref_dir(text_to_complete, remaining_text, cmpl_state);
1680 if(!cmpl_state->reference_dir)
1683 cmpl_state->completion_dir =
1684 find_completion_dir(*remaining_text, remaining_text, cmpl_state);
1686 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
1688 if(!cmpl_state->completion_dir)
1691 cmpl_state->completion_dir->cmpl_index = -1;
1692 cmpl_state->completion_dir->cmpl_parent = NULL;
1693 cmpl_state->completion_dir->cmpl_text = *remaining_text;
1695 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
1697 cmpl_state->reference_dir = cmpl_state->completion_dir;
1699 poss = attempt_file_completion(cmpl_state);
1701 update_cmpl(poss, cmpl_state);
1706 static PossibleCompletion*
1707 cmpl_next_completion (CompletionState* cmpl_state)
1709 PossibleCompletion* poss = NULL;
1711 cmpl_state->the_completion.text[0] = 0;
1713 if(cmpl_state->user_completion_index >= 0)
1714 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
1716 poss = attempt_file_completion(cmpl_state);
1718 update_cmpl(poss, cmpl_state);
1723 /**********************************************************************/
1724 /* Directory Operations */
1725 /**********************************************************************/
1727 /* Open the directory where completion will begin from, if possible. */
1728 static CompletionDir*
1729 open_ref_dir(gchar* text_to_complete,
1730 gchar** remaining_text,
1731 CompletionState* cmpl_state)
1734 CompletionDir *new_dir;
1736 first_slash = strchr(text_to_complete, '/');
1738 if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
1740 new_dir = open_dir("/", cmpl_state);
1743 *remaining_text = text_to_complete + 1;
1745 else if (text_to_complete[0] == '~')
1747 new_dir = open_user_dir(text_to_complete, cmpl_state);
1752 *remaining_text = first_slash + 1;
1754 *remaining_text = text_to_complete + strlen(text_to_complete);
1763 *remaining_text = text_to_complete;
1765 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
1770 new_dir->cmpl_index = -1;
1771 new_dir->cmpl_parent = NULL;
1777 /* open a directory by user name */
1778 static CompletionDir*
1779 open_user_dir(gchar* text_to_complete,
1780 CompletionState *cmpl_state)
1785 g_assert(text_to_complete && text_to_complete[0] == '~');
1787 first_slash = strchr(text_to_complete, '/');
1790 cmp_len = first_slash - text_to_complete - 1;
1792 cmp_len = strlen(text_to_complete + 1);
1797 if (!cmpl_state->user_home_dir &&
1798 !get_pwdb(cmpl_state))
1800 return open_dir(cmpl_state->user_home_dir, cmpl_state);
1805 char* copy = g_new(char, cmp_len + 1);
1807 strncpy(copy, text_to_complete + 1, cmp_len);
1809 pwd = getpwnam(copy);
1817 return open_dir(pwd->pw_dir, cmpl_state);
1821 /* open a directory relative the the current relative directory */
1822 static CompletionDir*
1823 open_relative_dir(gchar* dir_name,
1825 CompletionState *cmpl_state)
1827 gchar path_buf[2*MAXPATHLEN];
1829 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
1831 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1835 strcpy(path_buf, dir->fullname);
1837 if(dir->fullname_len > 1)
1839 path_buf[dir->fullname_len] = '/';
1840 strcpy(path_buf + dir->fullname_len + 1, dir_name);
1844 strcpy(path_buf + dir->fullname_len, dir_name);
1847 return open_dir(path_buf, cmpl_state);
1850 /* after the cache lookup fails, really open a new directory */
1851 static CompletionDirSent*
1852 open_new_dir(gchar* dir_name, struct stat* sbuf)
1854 CompletionDirSent* sent;
1857 struct dirent *dirent_ptr;
1858 gint buffer_size = 0;
1859 gint entry_count = 0;
1861 struct stat ent_sbuf;
1862 char path_buf[MAXPATHLEN*2];
1865 sent = g_new(CompletionDirSent, 1);
1866 sent->mtime = sbuf->st_mtime;
1867 sent->inode = sbuf->st_ino;
1868 sent->device = sbuf->st_dev;
1870 path_buf_len = strlen(dir_name);
1872 if (path_buf_len > MAXPATHLEN)
1874 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1878 strcpy(path_buf, dir_name);
1880 directory = opendir(dir_name);
1888 while((dirent_ptr = readdir(directory)) != NULL)
1890 int entry_len = strlen(dirent_ptr->d_name);
1891 buffer_size += entry_len + 1;
1894 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
1896 cmpl_errno = CMPL_ERRNO_TOO_LONG;
1897 closedir(directory);
1902 sent->name_buffer = g_new(gchar, buffer_size);
1903 sent->entries = g_new(CompletionDirEntry, entry_count);
1904 sent->entry_count = entry_count;
1906 buffer_ptr = sent->name_buffer;
1908 rewinddir(directory);
1910 for(i = 0; i < entry_count; i += 1)
1912 dirent_ptr = readdir(directory);
1917 closedir(directory);
1921 strcpy(buffer_ptr, dirent_ptr->d_name);
1922 sent->entries[i].entry_name = buffer_ptr;
1923 buffer_ptr += strlen(dirent_ptr->d_name);
1927 path_buf[path_buf_len] = '/';
1928 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
1930 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
1931 sent->entries[i].is_dir = 1;
1933 /* stat may fail, and we don't mind, since it could be a
1934 * dangling symlink. */
1935 sent->entries[i].is_dir = 0;
1938 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
1940 closedir(directory);
1945 /* open a directory by absolute pathname */
1946 static CompletionDir*
1947 open_dir(gchar* dir_name, CompletionState* cmpl_state)
1950 CompletionDirSent *sent;
1953 if(stat(dir_name, &sbuf) < 0)
1959 cdsl = cmpl_state->directory_sent_storage;
1965 if(sent->inode == sbuf.st_ino &&
1966 sent->mtime == sbuf.st_mtime &&
1967 sent->device == sbuf.st_dev)
1968 return attach_dir(sent, dir_name, cmpl_state);
1973 sent = open_new_dir(dir_name, &sbuf);
1976 cmpl_state->directory_sent_storage =
1977 g_list_prepend(cmpl_state->directory_sent_storage, sent);
1979 return attach_dir(sent, dir_name, cmpl_state);
1985 static CompletionDir*
1986 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
1988 CompletionDir* new_dir;
1990 new_dir = g_new(CompletionDir, 1);
1992 cmpl_state->directory_storage =
1993 g_list_prepend(cmpl_state->directory_storage, new_dir);
1995 new_dir->sent = sent;
1996 new_dir->fullname = g_strdup(dir_name);
1997 new_dir->fullname_len = strlen(dir_name);
2003 correct_dir_fullname(CompletionDir* cmpl_dir)
2005 gint length = strlen(cmpl_dir->fullname);
2008 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
2012 strcpy(cmpl_dir->fullname, "/");
2013 cmpl_dir->fullname_len = 1;
2016 cmpl_dir->fullname[length - 2] = 0;
2019 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
2020 cmpl_dir->fullname[length - 2] = 0;
2021 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 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 - 2] = 0;
2038 if(!correct_parent(cmpl_dir, &sbuf))
2041 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
2045 strcpy(cmpl_dir->fullname, "/");
2046 cmpl_dir->fullname_len = 1;
2050 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2056 cmpl_dir->fullname[length - 3] = 0;
2058 if(!correct_parent(cmpl_dir, &sbuf))
2062 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
2068 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
2075 last_slash = strrchr(cmpl_dir->fullname, '/');
2077 g_assert(last_slash);
2079 if(last_slash != cmpl_dir->fullname)
2080 { /* last_slash[0] = 0; */ }
2087 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2093 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2094 /* it wasn't a link */
2100 last_slash[0] = '/'; */
2102 /* it was a link, have to figure it out the hard way */
2104 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2109 g_free(cmpl_dir->fullname);
2111 cmpl_dir->fullname = new_name;
2117 find_parent_dir_fullname(gchar* dirname)
2119 gchar buffer[MAXPATHLEN];
2120 gchar buffer2[MAXPATHLEN];
2122 #if defined(sun) && !defined(__SVR4)
2125 if(!getcwd(buffer, MAXPATHLEN))
2132 if(chdir(dirname) != 0 || chdir("..") != 0)
2138 #if defined(sun) && !defined(__SVR4)
2141 if(!getcwd(buffer2, MAXPATHLEN))
2150 if(chdir(buffer) != 0)
2156 return g_strdup(buffer2);
2159 /**********************************************************************/
2160 /* Completion Operations */
2161 /**********************************************************************/
2163 static PossibleCompletion*
2164 attempt_homedir_completion(gchar* text_to_complete,
2165 CompletionState *cmpl_state)
2169 if (!cmpl_state->user_dir_name_buffer &&
2170 !get_pwdb(cmpl_state))
2172 length = strlen(text_to_complete) - 1;
2174 cmpl_state->user_completion_index += 1;
2176 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2178 index = first_diff_index(text_to_complete + 1,
2179 cmpl_state->user_directories
2180 [cmpl_state->user_completion_index].login);
2187 if(cmpl_state->last_valid_char < (index + 1))
2188 cmpl_state->last_valid_char = index + 1;
2189 cmpl_state->user_completion_index += 1;
2193 cmpl_state->the_completion.is_a_completion = 1;
2194 cmpl_state->the_completion.is_directory = 1;
2196 append_completion_text("~", cmpl_state);
2198 append_completion_text(cmpl_state->
2199 user_directories[cmpl_state->user_completion_index].login,
2202 return append_completion_text("/", cmpl_state);
2205 if(text_to_complete[1] ||
2206 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2208 cmpl_state->user_completion_index = -1;
2213 cmpl_state->user_completion_index += 1;
2214 cmpl_state->the_completion.is_a_completion = 1;
2215 cmpl_state->the_completion.is_directory = 1;
2217 return append_completion_text("~/", cmpl_state);
2221 /* returns the index (>= 0) of the first differing character,
2222 * PATTERN_MATCH if the completion matches */
2224 first_diff_index(gchar* pat, gchar* text)
2228 while(*pat && *text && *text == *pat)
2238 return PATTERN_MATCH;
2241 static PossibleCompletion*
2242 append_completion_text(gchar* text, CompletionState* cmpl_state)
2246 if(!cmpl_state->the_completion.text)
2249 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2251 if(cmpl_state->the_completion.text_alloc > len)
2253 strcat(cmpl_state->the_completion.text, text);
2254 return &cmpl_state->the_completion;
2257 while(i < len) { i <<= 1; }
2259 cmpl_state->the_completion.text_alloc = i;
2261 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2263 if(!cmpl_state->the_completion.text)
2267 strcat(cmpl_state->the_completion.text, text);
2268 return &cmpl_state->the_completion;
2272 static CompletionDir*
2273 find_completion_dir(gchar* text_to_complete,
2274 gchar** remaining_text,
2275 CompletionState* cmpl_state)
2277 gchar* first_slash = strchr(text_to_complete, '/');
2278 CompletionDir* dir = cmpl_state->reference_dir;
2279 *remaining_text = text_to_complete;
2283 gint len = first_slash - *remaining_text;
2285 gint found_index = -1;
2287 gchar* pat_buf = g_new (gchar, len + 1);
2289 strncpy(pat_buf, *remaining_text, len);
2292 for(i = 0; i < dir->sent->entry_count; i += 1)
2294 if(dir->sent->entries[i].is_dir &&
2295 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2296 FNMATCH_FLAGS)!= FNM_NOMATCH)
2313 CompletionDir* next = open_relative_dir(dir->sent->entries[found_index].entry_name,
2322 next->cmpl_parent = dir;
2326 if(!correct_dir_fullname(dir))
2332 *remaining_text = first_slash + 1;
2333 first_slash = strchr(*remaining_text, '/');
2348 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
2352 if(!poss || !cmpl_is_a_completion(poss))
2355 cmpl_len = strlen(cmpl_this_completion(poss));
2357 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
2359 cmpl_state->updated_text =
2360 (gchar*)g_realloc(cmpl_state->updated_text,
2361 cmpl_state->updated_text_alloc);
2362 cmpl_state->updated_text_alloc = 2*cmpl_len;
2365 if(cmpl_state->updated_text_len < 0)
2367 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2368 cmpl_state->updated_text_len = cmpl_len;
2369 cmpl_state->re_complete = cmpl_is_directory(poss);
2371 else if(cmpl_state->updated_text_len == 0)
2373 cmpl_state->re_complete = FALSE;
2378 first_diff_index(cmpl_state->updated_text,
2379 cmpl_this_completion(poss));
2381 cmpl_state->re_complete = FALSE;
2383 if(first_diff == PATTERN_MATCH)
2386 if(first_diff > cmpl_state->updated_text_len)
2387 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
2389 cmpl_state->updated_text_len = first_diff;
2390 cmpl_state->updated_text[first_diff] = 0;
2394 static PossibleCompletion*
2395 attempt_file_completion(CompletionState *cmpl_state)
2397 gchar *pat_buf, *first_slash;
2398 CompletionDir *dir = cmpl_state->active_completion_dir;
2400 dir->cmpl_index += 1;
2402 if(dir->cmpl_index == dir->sent->entry_count)
2404 if(dir->cmpl_parent == NULL)
2406 cmpl_state->active_completion_dir = NULL;
2412 cmpl_state->active_completion_dir = dir->cmpl_parent;
2414 return attempt_file_completion(cmpl_state);
2418 g_assert(dir->cmpl_text);
2420 first_slash = strchr(dir->cmpl_text, '/');
2424 gint len = first_slash - dir->cmpl_text;
2426 pat_buf = g_new (gchar, len + 1);
2427 strncpy(pat_buf, dir->cmpl_text, len);
2432 gint len = strlen(dir->cmpl_text);
2434 pat_buf = g_new (gchar, len + 2);
2435 strcpy(pat_buf, dir->cmpl_text);
2436 strcpy(pat_buf + len, "*");
2441 if(dir->sent->entries[dir->cmpl_index].is_dir)
2443 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2444 FNMATCH_FLAGS) != FNM_NOMATCH)
2446 CompletionDir* new_dir;
2448 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
2457 new_dir->cmpl_parent = dir;
2459 new_dir->cmpl_index = -1;
2460 new_dir->cmpl_text = first_slash + 1;
2462 cmpl_state->active_completion_dir = new_dir;
2465 return attempt_file_completion(cmpl_state);
2470 return attempt_file_completion(cmpl_state);
2476 return attempt_file_completion(cmpl_state);
2481 if(dir->cmpl_parent != NULL)
2483 append_completion_text(dir->fullname +
2484 strlen(cmpl_state->completion_dir->fullname) + 1,
2486 append_completion_text("/", cmpl_state);
2489 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
2491 cmpl_state->the_completion.is_a_completion =
2492 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
2493 FNMATCH_FLAGS) != FNM_NOMATCH);
2495 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
2496 if(dir->sent->entries[dir->cmpl_index].is_dir)
2497 append_completion_text("/", cmpl_state);
2500 return &cmpl_state->the_completion;
2506 get_pwdb(CompletionState* cmpl_state)
2508 struct passwd *pwd_ptr;
2510 gint len = 0, i, count = 0;
2512 if(cmpl_state->user_dir_name_buffer)
2516 while ((pwd_ptr = getpwent()) != NULL)
2518 len += strlen(pwd_ptr->pw_name);
2519 len += strlen(pwd_ptr->pw_dir);
2524 if (!cmpl_state->user_home_dir)
2526 /* the loser doesn't have $HOME set */
2529 pwd_ptr = getpwuid(getuid());
2535 /* Allocate this separately, since it might be filled in elsewhere */
2536 cmpl_state->user_home_dir = g_strdup (pwd_ptr->pw_dir);
2541 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
2542 cmpl_state->user_directories = g_new(CompletionUserDir, count);
2543 cmpl_state->user_directories_len = count;
2545 buf_ptr = cmpl_state->user_dir_name_buffer;
2547 for(i = 0; i < count; i += 1)
2549 pwd_ptr = getpwent();
2556 strcpy(buf_ptr, pwd_ptr->pw_name);
2557 cmpl_state->user_directories[i].login = buf_ptr;
2558 buf_ptr += strlen(buf_ptr);
2560 strcpy(buf_ptr, pwd_ptr->pw_dir);
2561 cmpl_state->user_directories[i].homedir = buf_ptr;
2562 buf_ptr += strlen(buf_ptr);
2566 qsort(cmpl_state->user_directories,
2567 cmpl_state->user_directories_len,
2568 sizeof(CompletionUserDir),
2577 if(cmpl_state->user_dir_name_buffer)
2578 g_free(cmpl_state->user_dir_name_buffer);
2579 if(cmpl_state->user_directories)
2580 g_free(cmpl_state->user_directories);
2582 cmpl_state->user_dir_name_buffer = NULL;
2583 cmpl_state->user_directories = NULL;
2589 compare_user_dir(const void* a, const void* b)
2591 return strcmp((((CompletionUserDir*)a))->login,
2592 (((CompletionUserDir*)b))->login);
2596 compare_cmpl_dir(const void* a, const void* b)
2598 return strcmp((((CompletionDirEntry*)a))->entry_name,
2599 (((CompletionDirEntry*)b))->entry_name);
2603 cmpl_state_okay(CompletionState* cmpl_state)
2605 return cmpl_state && cmpl_state->reference_dir;
2609 cmpl_strerror(gint err)
2611 if(err == CMPL_ERRNO_TOO_LONG)
2612 return "Name too long";
2614 return g_strerror (err);