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.
21 * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
22 * file for a list of people on the GTK+ Team. See the ChangeLog
23 * files for a list of changes. These files are distributed with
24 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
28 // leo FIXME: if we keep this file then we'll need to ask permission to the author, this is LGPL
29 // This file is from the Advanced File Selector widget
30 // by Michael Torrie <torriem@byu.edu>
31 // http://students.cs.byu.edu/~torriem/gtk/
33 // common files win32/linux
35 #include <sys/types.h>
42 // NOTE: the mkdir stuff etc. is in <direct.h> .. but I don't know what's the best strategy yet.
43 // just including <direct.h> here doesn't cut it
45 #if defined (__linux__) || (__APPLE__)
46 #include <sys/param.h>
54 #include "gdk/gdkkeysyms.h"
55 #include "gtk/gtkbutton.h"
56 #include "gtk/gtkentry.h"
57 #include "gtkfilesel-darwin.h"
58 #include "gtk/gtkhbox.h"
59 #include "gtk/gtkhbbox.h"
60 #include "gtk/gtklabel.h"
61 #include "gtk/gtklist.h"
62 #include "gtk/gtklistitem.h"
63 #include "gtk/gtkmain.h"
64 #include "gtk/gtkscrolledwindow.h"
65 #include "gtk/gtksignal.h"
66 #include "gtk/gtkvbox.h"
67 #include "gtk/gtkmenu.h"
68 #include "gtk/gtkmenuitem.h"
69 #include "gtk/gtkoptionmenu.h"
70 #include "gtk/gtkclist.h"
71 #include "gtk/gtkdialog.h"
72 #include "gtk/gtkcombo.h"
73 #include "gtk/gtkframe.h"
76 //#include "gtk/gtkintl.h"
77 #define _(String) (String)
79 #define DIR_LIST_WIDTH 180
80 #define DIR_LIST_HEIGHT 180
81 #define FILE_LIST_WIDTH 180
82 #define FILE_LIST_HEIGHT 180
84 /* I've put this here so it doesn't get confused with the
85 * file completion interface */
86 typedef struct _HistoryCallbackArg HistoryCallbackArg;
88 struct _HistoryCallbackArg
95 typedef struct _CompletionState CompletionState;
96 typedef struct _CompletionDir CompletionDir;
97 typedef struct _CompletionDirSent CompletionDirSent;
98 typedef struct _CompletionDirEntry CompletionDirEntry;
99 typedef struct _CompletionUserDir CompletionUserDir;
100 typedef struct _PossibleCompletion PossibleCompletion;
102 /* Non-external file completion decls and structures */
104 /* A contant telling PRCS how many directories to cache. Its actually
105 * kept in a list, so the geometry isn't important. */
106 #define CMPL_DIRECTORY_CACHE_SIZE 10
108 /* A constant used to determine whether a substring was an exact
109 * match by first_diff_index()
111 #define PATTERN_MATCH -1
112 /* The arguments used by all fnmatch() calls below
114 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
116 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
118 /* This structure contains all the useful information about a directory
119 * for the purposes of filename completion. These structures are cached
120 * in the CompletionState struct. CompletionDir's are reference counted.
122 struct _CompletionDirSent
129 gchar *name_buffer; /* memory segment containing names of all entries */
131 struct _CompletionDirEntry *entries;
134 struct _CompletionDir
136 CompletionDirSent *sent;
141 struct _CompletionDir *cmpl_parent;
146 /* This structure contains pairs of directory entry names with a flag saying
147 * whether or not they are a valid directory. NOTE: This information is used
148 * to provide the caller with information about whether to update its completions
149 * or try to open a file. Since directories are cached by the directory mtime,
150 * a symlink which points to an invalid file (which will not be a directory),
151 * will not be reevaluated if that file is created, unless the containing
152 * directory is touched. I consider this case to be worth ignoring (josh).
154 struct _CompletionDirEntry
160 struct _CompletionUserDir
166 struct _PossibleCompletion
168 /* accessible fields, all are accessed externally by functions
172 gint is_a_completion;
184 struct _CompletionState
186 gint last_valid_char;
188 gint updated_text_len;
189 gint updated_text_alloc;
192 gchar *user_dir_name_buffer;
193 gint user_directories_len;
195 gchar *last_completion_text;
197 gint user_completion_index; /* if >= 0, currently completing ~user */
199 struct _CompletionDir *completion_dir; /* directory completing from */
200 struct _CompletionDir *active_completion_dir;
202 struct _PossibleCompletion the_completion;
204 struct _CompletionDir *reference_dir; /* initial directory */
206 GList* directory_storage;
207 GList* directory_sent_storage;
209 struct _CompletionUserDir *user_directories;
213 /* File completion functions which would be external, were they used
214 * outside of this file.
217 static CompletionState* cmpl_init_state (void);
218 static void cmpl_free_state (CompletionState *cmpl_state);
219 static gint cmpl_state_okay (CompletionState* cmpl_state);
220 static gchar* cmpl_strerror (gint);
222 static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete,
223 gchar **remaining_text,
224 CompletionState *cmpl_state);
226 /* Returns a name for consideration, possibly a completion, this name
227 * will be invalid after the next call to cmpl_next_completion.
229 static char* cmpl_this_completion (PossibleCompletion*);
231 /* True if this completion matches the given text. Otherwise, this
232 * output can be used to have a list of non-completions.
234 static gint cmpl_is_a_completion (PossibleCompletion*);
236 /* True if the completion is a directory
238 static gint cmpl_is_directory (PossibleCompletion*);
240 /* Obtains the next completion, or NULL
242 static PossibleCompletion* cmpl_next_completion (CompletionState*);
244 /* Updating completions: the return value of cmpl_updated_text() will
245 * be text_to_complete completed as much as possible after the most
246 * recent call to cmpl_completion_matches. For the present
247 * application, this is the suggested replacement for the user's input
248 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
251 static gchar* cmpl_updated_text (CompletionState* cmpl_state);
253 /* After updating, to see if the completion was a directory, call
254 * this. If it was, you should consider re-calling completion_matches.
256 static gint cmpl_updated_dir (CompletionState* cmpl_state);
258 /* Current location: if using file completion, return the current
259 * directory, from which file completion begins. More specifically,
260 * the cwd concatenated with all exact completions up to the last
261 * directory delimiter('/').
263 static gchar* cmpl_reference_position (CompletionState* cmpl_state);
265 /* backing up: if cmpl_completion_matches returns NULL, you may query
266 * the index of the last completable character into cmpl_updated_text.
268 static gint cmpl_last_valid_char (CompletionState* cmpl_state);
270 /* When the user selects a non-directory, call cmpl_completion_fullname
271 * to get the full name of the selected file.
273 static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
276 /* Directory operations. */
277 static CompletionDir* open_ref_dir (gchar* text_to_complete,
278 gchar** remaining_text,
279 CompletionState* cmpl_state);
280 static gboolean check_dir (gchar *dir_name,
282 gboolean *stat_subdirs);
283 static CompletionDir* open_dir (gchar* dir_name,
284 CompletionState* cmpl_state);
285 static CompletionDir* open_user_dir (gchar* text_to_complete,
286 CompletionState *cmpl_state);
287 static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
288 CompletionState *cmpl_state);
289 static CompletionDirSent* open_new_dir (gchar* dir_name,
291 gboolean stat_subdirs);
292 static gint correct_dir_fullname (CompletionDir* cmpl_dir);
293 static gint correct_parent (CompletionDir* cmpl_dir,
295 static gchar* find_parent_dir_fullname (gchar* dirname);
296 static CompletionDir* attach_dir (CompletionDirSent* sent,
298 CompletionState *cmpl_state);
299 static void free_dir_sent (CompletionDirSent* sent);
300 static void free_dir (CompletionDir *dir);
301 static void prune_memory_usage(CompletionState *cmpl_state);
303 /* Completion operations */
304 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
305 CompletionState *cmpl_state);
306 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
307 static CompletionDir* find_completion_dir(gchar* text_to_complete,
308 gchar** remaining_text,
309 CompletionState* cmpl_state);
310 static PossibleCompletion* append_completion_text(gchar* text,
311 CompletionState* cmpl_state);
312 static gint get_pwdb(CompletionState* cmpl_state);
313 static gint first_diff_index(gchar* pat, gchar* text);
314 static gint compare_user_dir(const void* a, const void* b);
315 static gint compare_cmpl_dir(const void* a, const void* b);
316 static void update_cmpl(PossibleCompletion* poss,
317 CompletionState* cmpl_state);
319 static void gtk_file_selection_class_init (GtkFileSelectionClass *klass);
320 static void gtk_file_selection_init (GtkFileSelection *filesel);
321 static void gtk_file_selection_destroy (GtkObject *object);
322 static gint gtk_file_selection_key_press (GtkWidget *widget,
326 static void gtk_file_selection_file_button (GtkWidget *widget,
329 GdkEventButton *bevent,
332 static void gtk_file_selection_dir_button (GtkWidget *widget,
335 GdkEventButton *bevent,
338 static void gtk_file_selection_undir_button (GtkWidget *widget,
341 GdkEventButton *bevent,
344 static void gtk_file_selection_populate (GtkFileSelection *fs,
347 static void gtk_file_selection_abort (GtkFileSelection *fs);
349 static void gtk_file_selection_update_history_menu (GtkFileSelection *fs,
352 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
353 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
354 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
356 static gboolean gtk_file_selection_history_combo_callback (GtkWidget *widget, GdkEventKey *event, gpointer data);
357 static gboolean gtk_file_selection_history_combo_list_key_handler(GtkWidget *widget,
360 static gboolean gtk_file_selection_history_combo_list_callback (GtkWidget *thelist,
361 GdkEventButton *event,
363 static void gtk_file_selection_mask_entry_callback (GtkWidget *widget, gpointer data);
364 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
365 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
366 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
367 static void gtk_file_selection_home_button (GtkWidget *widget, gpointer data);
368 static void gtk_file_selection_up_button (GtkWidget *widget, gpointer data);
369 static void gtk_file_selection_prev_button (GtkWidget *widget, gpointer data);
370 static void gtk_file_selection_next_button (GtkWidget *widget, gpointer data);
371 static void gtk_file_selection_refresh_button (GtkWidget *widget, gpointer data);
373 static gint gtk_file_selection_match_char (gchar, gchar *mask);
374 static gint gtk_file_selection_match_mask (gchar *,gchar *);
377 static GtkWindowClass *parent_class = NULL;
379 /* Saves errno when something cmpl does fails. */
380 static gint cmpl_errno;
383 void gtk_file_selection_clear_masks (GtkFileSelection *filesel)
387 g_return_if_fail (filesel != NULL);
388 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
390 list = filesel->masks;
396 filesel->masks = NULL;
398 gtk_list_clear_items (GTK_LIST (GTK_COMBO (filesel->mask_entry)->list), 0, -1);
401 void gtk_file_selection_set_masks (GtkFileSelection *filesel, const gchar **masks)
403 g_return_if_fail (filesel != NULL);
404 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
408 filesel->masks = g_list_append (filesel->masks, (gpointer)*masks);
413 gtk_combo_set_popdown_strings (GTK_COMBO (filesel->mask_entry), filesel->masks);
418 * Make prev and next inactive if their respective *
419 * histories are empty.
420 * Add facilities for handling hidden files and *
422 * Add an api to access the mask, and hidden files *
423 * check box? (prob not in 1.2.x series) *
426 /* Routine for applying mask to filenames *
427 * Need to be optimized to minimize recursion *
428 * help the for loop by looking for the next *
429 * instance of the mask character following *
430 * the '*'. ei *.c -- look for '.' *
431 * Also, swap all *? pairs (-> ?*), as that *
432 * will make it possible to look ahead (? *
433 * makes it very nondeterministic as in *?.c *
434 * which really is ?*.c *
435 * Allow multiply masks, separted by commas *
436 * Allow more flexible [] handling (ie [a-zA-Z] *
439 static gint gtk_file_selection_match_char (gchar text, gchar *mask){
446 if (!strchr (mask,']')) return 0;
447 maskc = g_strdup(mask + 1); /* get the portion of mask inside []*/
449 (*(strchr (maskc,']'))) = 0;
450 s = strlen ((char *)maskc);
452 for (x = 0; x < s; x++){
453 if (text == maskc[x])
463 if (mask[0] == '?') return 1;
464 if (mask[0] == text) return 1;
470 static gint gtk_file_selection_match_mask (gchar *text, gchar *mask){
477 if (mask[0] == 0 && text[0] == 0) return 1;
481 for (tc = 0; tc <= strlen(text); tc++)
483 if (gtk_file_selection_match_mask (text + tc, mask + 1))
488 mc = gtk_file_selection_match_char (text[0], mask);
491 return gtk_file_selection_match_mask (text + 1, mask + mc);
497 gtk_file_selection_get_type (void)
499 static GtkType file_selection_type = 0;
501 if (!file_selection_type)
503 static const GtkTypeInfo filesel_info =
506 sizeof (GtkFileSelection),
507 sizeof (GtkFileSelectionClass),
508 (GtkClassInitFunc) gtk_file_selection_class_init,
509 (GtkObjectInitFunc) gtk_file_selection_init,
510 /* reserved_1 */ NULL,
511 /* reserved_2 */ NULL,
512 (GtkClassInitFunc) NULL,
515 file_selection_type = gtk_type_unique (GTK_TYPE_WINDOW, &filesel_info);
518 return file_selection_type;
522 gtk_file_selection_class_init (GtkFileSelectionClass *klass) //tigital
524 GtkObjectClass *object_class;
526 object_class = (GtkObjectClass*) klass;
528 parent_class = gtk_type_class (GTK_TYPE_WINDOW);
530 object_class->destroy = gtk_file_selection_destroy;
534 gtk_file_selection_init (GtkFileSelection *filesel)
536 GtkWidget *entry_vbox;
538 GtkWidget *list_hbox;
539 GtkWidget *confirm_area;
542 GtkWidget *pulldown_hbox;
543 GtkWidget *scrolled_win;
544 GtkWidget *mask_label;
546 GtkWidget *label_lookingin;
547 GtkWidget *up_button;
548 GtkWidget *home_button;
549 GtkWidget *prev_button;
550 GtkWidget *next_button;
551 GtkWidget *refresh_button;
554 char *file_title [2];
556 filesel->cmpl_state = cmpl_init_state ();
559 filesel->prev_history=NULL;
560 filesel->next_history=NULL;
561 filesel->saved_entry=NULL;
563 /* The dialog-sized vertical box */
564 filesel->main_vbox = gtk_vbox_new (FALSE, 10);
565 gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
566 gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
567 gtk_widget_show (filesel->main_vbox);
569 /* The horizontal box containing create, rename etc. buttons */
570 filesel->button_area = gtk_hbutton_box_new ();
571 gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START);
572 gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0);
573 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area,
575 gtk_widget_show (filesel->button_area);
577 gtk_file_selection_show_fileop_buttons(filesel);
579 /* hbox for pulldown menu */
580 pulldown_hbox = gtk_hbox_new (FALSE, 5);
581 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
582 gtk_widget_show (pulldown_hbox);
584 /* The combo box that replaces the pulldown menu */
585 label_lookingin = gtk_label_new (_("Looking in:"));
586 gtk_widget_show (label_lookingin);
587 gtk_box_pack_start (GTK_BOX (pulldown_hbox), label_lookingin, FALSE, FALSE, 0);
589 filesel->history_combo = gtk_combo_new();
590 gtk_widget_show(filesel->history_combo);
591 gtk_combo_set_value_in_list(GTK_COMBO(filesel->history_combo),FALSE,FALSE);
592 gtk_box_pack_start (GTK_BOX(pulldown_hbox),filesel->history_combo,
594 gtk_signal_connect(GTK_OBJECT(((GtkCombo *)filesel->history_combo)->entry),"key-press-event",
595 (GtkSignalFunc) gtk_file_selection_history_combo_callback,
598 gtk_signal_connect(GTK_OBJECT(((GtkCombo *)filesel->history_combo)->list),"button-press-event",
599 (GtkSignalFunc) gtk_file_selection_history_combo_list_callback,
602 gtk_signal_connect(GTK_OBJECT(((GtkCombo *)filesel->history_combo)->list),"key-press-event",
603 (GtkSignalFunc) gtk_file_selection_history_combo_list_key_handler,
606 /* frame to put the following hbox in */
607 bigframe = gtk_frame_new (NULL);
608 gtk_widget_show (bigframe);
609 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), bigframe, TRUE, TRUE, 0);
611 /* The horizontal box containing the directory and file listboxes */
612 list_hbox = gtk_hbox_new (FALSE, 5);
613 gtk_container_add (GTK_CONTAINER(bigframe), list_hbox);
614 gtk_container_set_border_width (GTK_CONTAINER (list_hbox), 5);
615 gtk_widget_show (list_hbox);
617 /* vbox to put the buttons and directory listing in */
618 vbox = gtk_vbox_new (FALSE, 0);
619 gtk_widget_show (vbox);
620 gtk_box_pack_start (GTK_BOX (list_hbox), vbox, FALSE, FALSE, 0);
622 hbox = gtk_hbox_new (FALSE, 0);
623 gtk_widget_show (hbox);
624 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
626 home_button = gtk_button_new_with_label (_("Home"));
627 gtk_widget_show (home_button);
628 gtk_signal_connect (GTK_OBJECT (home_button), "clicked",
629 (GtkSignalFunc) gtk_file_selection_home_button,
631 gtk_box_pack_start (GTK_BOX (hbox), home_button, TRUE,TRUE, 0);
633 prev_button = gtk_button_new_with_label (_("Prev"));
634 gtk_signal_connect (GTK_OBJECT (prev_button), "clicked",
635 (GtkSignalFunc) gtk_file_selection_prev_button,
637 gtk_widget_show (prev_button);
638 gtk_box_pack_start (GTK_BOX (hbox), prev_button, TRUE,TRUE, 0);
640 up_button = gtk_button_new_with_label (_("Up"));
641 gtk_signal_connect (GTK_OBJECT (up_button), "clicked",
642 (GtkSignalFunc) gtk_file_selection_up_button,
644 gtk_widget_show (up_button);
645 gtk_box_pack_start (GTK_BOX (hbox), up_button, TRUE,TRUE, 0);
647 next_button = gtk_button_new_with_label (_("Next"));
648 gtk_widget_show (next_button);
649 gtk_signal_connect (GTK_OBJECT (next_button), "clicked",
650 (GtkSignalFunc) gtk_file_selection_next_button,
652 gtk_box_pack_start (GTK_BOX (hbox), next_button, TRUE,TRUE, 0);
654 refresh_button = gtk_button_new_with_label (_("Refresh"));
655 gtk_widget_show (refresh_button);
656 gtk_signal_connect (GTK_OBJECT (refresh_button), "clicked",
657 (GtkSignalFunc) gtk_file_selection_refresh_button,
659 gtk_box_pack_start (GTK_BOX (hbox), refresh_button, TRUE, TRUE, 0);
661 /* The directories clist */
662 dir_title[0] = _("Directories");
664 filesel->dir_list = gtk_clist_new_with_titles (1, (gchar**) dir_title);
665 gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
666 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
667 (GtkSignalFunc) gtk_file_selection_dir_button,
669 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "unselect_row",
670 (GtkSignalFunc) gtk_file_selection_undir_button,
672 gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
674 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
675 gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list);
676 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
677 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
678 gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE,TRUE, 5);
679 gtk_widget_show (filesel->dir_list);
680 gtk_widget_show (scrolled_win);
682 /* vbox area for mask entry and files clist */
683 vbox = gtk_vbox_new (FALSE, 0);
684 gtk_widget_show (vbox);
685 gtk_box_pack_start (GTK_BOX (list_hbox), vbox, TRUE, TRUE, 0);
687 hbox = gtk_hbox_new (FALSE, 5);
688 gtk_widget_show (hbox);
689 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
691 mask_label = gtk_label_new (_("Mask:"));
692 gtk_widget_show (mask_label);
693 gtk_box_pack_start (GTK_BOX (hbox), mask_label, FALSE, FALSE, 0);
695 filesel->mask_entry = gtk_entry_new ();
696 gtk_widget_show (filesel->mask_entry);
697 gtk_signal_connect(GTK_OBJECT(filesel->mask_entry),"activate",
698 (GtkSignalFunc) gtk_file_selection_mask_entry_callback,
700 gtk_box_pack_start (GTK_BOX (hbox),filesel->mask_entry, TRUE, TRUE, 0);
703 /* The files clist */
704 file_title[0] = _("Files");
705 file_title[1] = NULL;
706 filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title);
707 gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
708 gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
709 (GtkSignalFunc) gtk_file_selection_file_button,
711 gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
713 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
714 gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list);
715 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
716 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
717 gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 5);
718 gtk_widget_show (filesel->file_list);
719 gtk_widget_show (scrolled_win);
721 /* action area for packing buttons into. */
722 filesel->action_area = gtk_hbox_new (TRUE, 0);
723 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area,
725 gtk_widget_show (filesel->action_area);
727 /* The OK/Cancel button area */
728 confirm_area = gtk_hbutton_box_new ();
729 gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END);
730 gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5);
731 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0);
732 gtk_widget_show (confirm_area);
735 filesel->ok_button = gtk_button_new_with_label (_("OK"));
736 GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
737 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0);
738 gtk_widget_grab_default (filesel->ok_button);
739 gtk_widget_show (filesel->ok_button);
741 /* The Cancel button */
742 filesel->cancel_button = gtk_button_new_with_label (_("Cancel"));
743 GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
744 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0);
745 gtk_widget_show (filesel->cancel_button);
747 /* The selection entry widget */
748 entry_vbox = gtk_vbox_new (FALSE, 2);
749 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
750 gtk_widget_show (entry_vbox);
752 filesel->selection_text = label = gtk_label_new ("");
753 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
754 gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
755 gtk_widget_show (label);
757 filesel->selection_entry = gtk_entry_new ();
758 gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
759 (GtkSignalFunc) gtk_file_selection_key_press, filesel);
760 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
761 (GtkSignalFunc) gtk_widget_grab_default,
762 GTK_OBJECT (filesel->ok_button));
763 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
764 (GtkSignalFunc) gtk_button_clicked,
765 GTK_OBJECT (filesel->ok_button));
766 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
767 gtk_widget_show (filesel->selection_entry);
769 if (!cmpl_state_okay (filesel->cmpl_state))
773 sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
775 gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
779 gtk_file_selection_populate (filesel, "", FALSE);
782 gtk_widget_grab_focus (filesel->selection_entry);
786 gtk_file_selection_new (const gchar *title)
788 GtkFileSelection *filesel;
790 filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION);
791 gtk_window_set_title (GTK_WINDOW (filesel), title);
793 return GTK_WIDGET (filesel);
797 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
799 g_return_if_fail (filesel != NULL);
800 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
802 /* delete, create directory, and rename */
803 if (!filesel->fileop_c_dir)
805 filesel->fileop_c_dir = gtk_button_new_with_label (_("Create Dir"));
806 gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
807 (GtkSignalFunc) gtk_file_selection_create_dir,
809 gtk_box_pack_start (GTK_BOX (filesel->button_area),
810 filesel->fileop_c_dir, TRUE, TRUE, 0);
811 gtk_widget_show (filesel->fileop_c_dir);
814 if (!filesel->fileop_del_file)
816 filesel->fileop_del_file = gtk_button_new_with_label (_("Delete File"));
817 gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
818 (GtkSignalFunc) gtk_file_selection_delete_file,
820 gtk_box_pack_start (GTK_BOX (filesel->button_area),
821 filesel->fileop_del_file, TRUE, TRUE, 0);
822 gtk_widget_show (filesel->fileop_del_file);
825 if (!filesel->fileop_ren_file)
827 filesel->fileop_ren_file = gtk_button_new_with_label (_("Rename File"));
828 gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
829 (GtkSignalFunc) gtk_file_selection_rename_file,
831 gtk_box_pack_start (GTK_BOX (filesel->button_area),
832 filesel->fileop_ren_file, TRUE, TRUE, 0);
833 gtk_widget_show (filesel->fileop_ren_file);
836 gtk_widget_queue_resize(GTK_WIDGET(filesel));
840 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
842 g_return_if_fail (filesel != NULL);
843 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
845 if (filesel->fileop_ren_file)
847 gtk_widget_destroy (filesel->fileop_ren_file);
848 filesel->fileop_ren_file = NULL;
851 if (filesel->fileop_del_file)
853 gtk_widget_destroy (filesel->fileop_del_file);
854 filesel->fileop_del_file = NULL;
857 if (filesel->fileop_c_dir)
859 gtk_widget_destroy (filesel->fileop_c_dir);
860 filesel->fileop_c_dir = NULL;
867 gtk_file_selection_set_filename (GtkFileSelection *filesel,
868 const gchar *filename)
870 char buf[MAXPATHLEN];
871 const char *name, *last_slash;
873 g_return_if_fail (filesel != NULL);
874 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
875 g_return_if_fail (filename != NULL);
877 last_slash = strrchr (filename, '/');
886 gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
888 strncpy (buf, filename, len);
891 name = last_slash + 1;
894 gtk_file_selection_populate (filesel, buf, FALSE);
896 if (filesel->selection_entry)
897 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
901 gtk_file_selection_get_filename (GtkFileSelection *filesel)
903 static char nothing[2] = "";
907 g_return_val_if_fail (filesel != NULL, nothing);
908 g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
910 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
913 filename = cmpl_completion_fullname (text, filesel->cmpl_state);
921 gtk_file_selection_complete (GtkFileSelection *filesel,
922 const gchar *pattern)
927 g_return_if_fail (filesel != NULL);
928 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
929 g_return_if_fail (pattern != NULL);
931 if (filesel->selection_entry)
932 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern);
934 if(strchr(pattern,'*') || strchr(pattern,'?'))
936 for(x=strlen(pattern);x>=0;x--)
938 if(pattern[x]=='/') break;
940 gtk_entry_set_text(GTK_ENTRY(filesel->mask_entry),g_strdup(pattern+x+1));
942 if(filesel->mask) g_free(filesel->mask);
944 filesel->mask=g_strdup(pattern+x+1);
945 new_pattern=g_strdup(pattern);
947 gtk_file_selection_populate (filesel, (gchar*) new_pattern, TRUE);
952 gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE);
957 gtk_file_selection_destroy (GtkObject *object)
959 GtkFileSelection *filesel;
962 g_return_if_fail (object != NULL);
963 g_return_if_fail (GTK_IS_FILE_SELECTION (object));
965 filesel = GTK_FILE_SELECTION (object);
967 if (filesel->fileop_dialog)
968 gtk_widget_destroy (filesel->fileop_dialog);
970 if (filesel->next_history)
972 list = filesel->next_history;
979 g_list_free (filesel->next_history);
980 filesel->next_history = NULL;
982 if (filesel->prev_history)
984 list = filesel->prev_history;
991 g_list_free (filesel->prev_history);
992 filesel->prev_history = NULL;
996 g_free (filesel->mask);
997 filesel->mask = NULL;
1000 cmpl_free_state (filesel->cmpl_state);
1001 filesel->cmpl_state = NULL;
1003 if (GTK_OBJECT_CLASS (parent_class)->destroy)
1004 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
1007 /* Begin file operations callbacks */
1010 gtk_file_selection_fileop_error (GtkFileSelection *fs, gchar *error_message)
1017 g_return_if_fail (error_message != NULL);
1020 dialog = gtk_dialog_new ();
1022 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1023 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1026 gtk_window_set_title (GTK_WINDOW (dialog), _("Error"));
1027 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1029 /* If file dialog is grabbed, make this dialog modal too */
1030 /* When error dialog is closed, file dialog will be grabbed again */
1031 if (GTK_WINDOW(fs)->modal)
1032 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1034 vbox = gtk_vbox_new(FALSE, 0);
1035 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1036 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1038 gtk_widget_show(vbox);
1040 label = gtk_label_new(error_message);
1041 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1042 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1043 gtk_widget_show(label);
1045 /* yes, we free it */
1046 g_free (error_message);
1049 button = gtk_button_new_with_label (_("Close"));
1050 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1051 (GtkSignalFunc) gtk_widget_destroy,
1053 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1054 button, TRUE, TRUE, 0);
1055 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1056 gtk_widget_grab_default(button);
1057 gtk_widget_show (button);
1059 gtk_widget_show (dialog);
1063 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
1065 GtkFileSelection *fs = data;
1067 g_return_if_fail (fs != NULL);
1068 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1070 fs->fileop_dialog = NULL;
1075 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
1077 GtkFileSelection *fs = data;
1082 CompletionState *cmpl_state;
1084 g_return_if_fail (fs != NULL);
1085 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1087 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1088 cmpl_state = (CompletionState*) fs->cmpl_state;
1089 path = cmpl_reference_position (cmpl_state);
1091 full_path = g_strconcat (path, "/", dirname, NULL);
1092 if ( (mkdir (full_path, 0755) < 0) )
1094 buf = g_strconcat ("Error creating directory \"", dirname, "\": ",
1095 g_strerror(errno), NULL);
1096 gtk_file_selection_fileop_error (fs, buf);
1100 gtk_widget_destroy (fs->fileop_dialog);
1101 gtk_file_selection_populate (fs, "", FALSE);
1105 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
1107 GtkFileSelection *fs = data;
1113 g_return_if_fail (fs != NULL);
1114 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1116 if (fs->fileop_dialog)
1120 fs->fileop_dialog = dialog = gtk_dialog_new ();
1121 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1122 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1124 gtk_window_set_title (GTK_WINDOW (dialog), _("Create Directory"));
1125 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1127 /* If file dialog is grabbed, grab option dialog */
1128 /* When option dialog is closed, file dialog will be grabbed again */
1129 if (GTK_WINDOW(fs)->modal)
1130 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1132 vbox = gtk_vbox_new(FALSE, 0);
1133 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1134 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1136 gtk_widget_show(vbox);
1138 label = gtk_label_new(_("Directory name:"));
1139 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1140 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1141 gtk_widget_show(label);
1143 /* The directory entry widget */
1144 fs->fileop_entry = gtk_entry_new ();
1145 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
1147 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
1148 gtk_widget_show (fs->fileop_entry);
1151 button = gtk_button_new_with_label (_("Create"));
1152 gtk_signal_connect (GTK_OBJECT (button), "clicked",
1153 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
1155 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1156 button, TRUE, TRUE, 0);
1157 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1158 gtk_widget_show(button);
1160 button = gtk_button_new_with_label (_("Cancel"));
1161 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1162 (GtkSignalFunc) gtk_widget_destroy,
1164 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1165 button, TRUE, TRUE, 0);
1166 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1167 gtk_widget_grab_default(button);
1168 gtk_widget_show (button);
1170 gtk_widget_show (dialog);
1174 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
1176 GtkFileSelection *fs = data;
1177 CompletionState *cmpl_state;
1182 g_return_if_fail (fs != NULL);
1183 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1185 cmpl_state = (CompletionState*) fs->cmpl_state;
1186 path = cmpl_reference_position (cmpl_state);
1188 full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
1189 if ( (unlink (full_path) < 0) )
1191 buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ",
1192 g_strerror(errno), NULL);
1193 gtk_file_selection_fileop_error (fs, buf);
1197 gtk_widget_destroy (fs->fileop_dialog);
1198 gtk_file_selection_populate (fs, "", FALSE);
1202 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
1204 GtkFileSelection *fs = data;
1212 g_return_if_fail (fs != NULL);
1213 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1215 if (fs->fileop_dialog)
1218 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1219 if (strlen(filename) < 1)
1222 fs->fileop_file = filename;
1225 fs->fileop_dialog = dialog = gtk_dialog_new ();
1226 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1227 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1229 gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File"));
1230 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1232 /* If file dialog is grabbed, grab option dialog */
1233 /* When option dialog is closed, file dialog will be grabbed again */
1234 if (GTK_WINDOW(fs)->modal)
1235 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1237 vbox = gtk_vbox_new(FALSE, 0);
1238 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1239 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1241 gtk_widget_show(vbox);
1243 buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
1244 label = gtk_label_new(buf);
1245 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1246 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1247 gtk_widget_show(label);
1251 button = gtk_button_new_with_label (_("Delete"));
1252 gtk_signal_connect (GTK_OBJECT (button), "clicked",
1253 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
1255 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1256 button, TRUE, TRUE, 0);
1257 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1258 gtk_widget_show(button);
1260 button = gtk_button_new_with_label (_("Cancel"));
1261 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1262 (GtkSignalFunc) gtk_widget_destroy,
1264 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1265 button, TRUE, TRUE, 0);
1266 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1267 gtk_widget_grab_default(button);
1268 gtk_widget_show (button);
1270 gtk_widget_show (dialog);
1275 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
1277 GtkFileSelection *fs = data;
1281 gchar *new_filename;
1282 gchar *old_filename;
1283 CompletionState *cmpl_state;
1285 g_return_if_fail (fs != NULL);
1286 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1288 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1289 cmpl_state = (CompletionState*) fs->cmpl_state;
1290 path = cmpl_reference_position (cmpl_state);
1292 new_filename = g_strconcat (path, "/", file, NULL);
1293 old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
1295 if ( (rename (old_filename, new_filename)) < 0)
1297 buf = g_strconcat ("Error renaming file \"", file, "\": ",
1298 g_strerror(errno), NULL);
1299 gtk_file_selection_fileop_error (fs, buf);
1301 g_free (new_filename);
1302 g_free (old_filename);
1304 gtk_widget_destroy (fs->fileop_dialog);
1305 gtk_file_selection_populate (fs, "", FALSE);
1309 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
1311 GtkFileSelection *fs = data;
1318 g_return_if_fail (fs != NULL);
1319 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1321 if (fs->fileop_dialog)
1324 fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1325 if (strlen(fs->fileop_file) < 1)
1329 fs->fileop_dialog = dialog = gtk_dialog_new ();
1330 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1331 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1333 gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File"));
1334 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1336 /* If file dialog is grabbed, grab option dialog */
1337 /* When option dialog closed, file dialog will be grabbed again */
1338 if (GTK_WINDOW(fs)->modal)
1339 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1341 vbox = gtk_vbox_new(FALSE, 0);
1342 gtk_container_set_border_width (GTK_CONTAINER(vbox), 8);
1343 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1345 gtk_widget_show(vbox);
1347 buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
1348 label = gtk_label_new(buf);
1349 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1350 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1351 gtk_widget_show(label);
1354 /* New filename entry */
1355 fs->fileop_entry = gtk_entry_new ();
1356 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
1358 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
1359 gtk_widget_show (fs->fileop_entry);
1361 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
1362 gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
1363 0, strlen (fs->fileop_file));
1366 button = gtk_button_new_with_label (_("Rename"));
1367 gtk_signal_connect (GTK_OBJECT (button), "clicked",
1368 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
1370 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1371 button, TRUE, TRUE, 0);
1372 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1373 gtk_widget_show(button);
1375 button = gtk_button_new_with_label (_("Cancel"));
1376 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1377 (GtkSignalFunc) gtk_widget_destroy,
1379 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1380 button, TRUE, TRUE, 0);
1381 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1382 gtk_widget_grab_default(button);
1383 gtk_widget_show (button);
1385 gtk_widget_show (dialog);
1390 gtk_file_selection_key_press (GtkWidget *widget,
1394 GtkFileSelection *fs;
1397 g_return_val_if_fail (widget != NULL, FALSE);
1398 g_return_val_if_fail (event != NULL, FALSE);
1400 fs = GTK_FILE_SELECTION (user_data);
1402 if (event->keyval == GDK_Tab)
1404 text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1406 text = g_strdup (text);
1408 gtk_file_selection_populate (fs, text, TRUE);
1412 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1416 if (fs->saved_entry)
1418 gtk_clist_unselect_all ((GtkCList *) (fs->dir_list));
1419 gtk_entry_set_text(GTK_ENTRY(fs->selection_entry),fs->saved_entry);
1420 g_free (fs->saved_entry);
1421 fs->saved_entry = NULL;
1429 gtk_file_selection_home_button (GtkWidget *widget, gpointer data){
1432 GtkFileSelection *fs=data;
1434 list = fs->next_history;
1437 g_free (list->data);
1440 g_list_free (fs->next_history);
1441 fs->next_history = NULL;
1443 gtk_file_selection_populate (fs,"~/",FALSE);
1447 gtk_file_selection_up_button (GtkWidget *widget, gpointer data){
1448 GtkFileSelection *fs = data;
1451 g_return_if_fail (fs != NULL);
1452 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1454 list = fs->next_history;
1457 g_free (list->data);
1460 g_list_free (fs->next_history);
1461 fs->next_history = NULL;
1463 gtk_file_selection_populate (fs, "../", FALSE); /*change directories. */
1468 gtk_file_selection_prev_button (GtkWidget *widget, gpointer data){
1469 GtkFileSelection *fs = data;
1474 g_return_if_fail (fs != NULL);
1475 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1477 list = fs->prev_history;
1479 if (list && g_list_length(list) > 1)
1481 first = list; /* get first element */
1482 list = list->next; /* pop off current directory */
1484 list->prev = NULL; /* make this the new head. */
1486 fs->prev_history = list; /* update prev_history list */
1487 fs->next_history = g_list_prepend(fs->next_history,first->data); /* put it on next_history */
1489 first->next = NULL; /* orphan the old first node */
1490 g_list_free (first); /* free the node (data is now in use by next_history) */
1494 path = g_malloc(strlen(list->data)+4); /* plenty of space */
1495 strcpy(path,list->data); /* get the 2nd path in the history */
1496 strcat(path,"/"); /* append a '/' */
1497 gtk_file_selection_populate (fs, path, FALSE); /* change directories. */
1503 gtk_file_selection_next_button (GtkWidget *widget, gpointer data){
1504 GtkFileSelection *fs = data;
1509 g_return_if_fail (fs != NULL);
1510 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1512 list = fs->next_history;
1514 if (list && g_list_length(list) > 0)
1516 first = list; /*get first element*/
1517 list = list->next; /*pop off current directory*/
1522 fs->next_history = list; /*update prev_history list*/
1524 path = g_malloc(strlen(first->data)+4); /*plenty of space*/
1525 strcpy(path,first->data);
1526 strcat(path,"/"); /*append a / */
1527 gtk_file_selection_populate (fs, path, FALSE); /*change directories.*/
1530 first->next = NULL; /* orphan the old first node */
1531 g_list_free (first); /* free the node (data is now in use by next_history) */
1537 gtk_file_selection_refresh_button (GtkWidget *widget, gpointer data){
1538 GtkFileSelection *fs = data;
1540 g_return_if_fail (fs != NULL);
1541 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1543 gtk_file_selection_populate (fs,"",FALSE);
1547 gtk_file_selection_mask_entry_callback (GtkWidget *widget, gpointer data){
1548 GtkFileSelection *fs = data;
1553 fs->mask = g_strdup(gtk_entry_get_text (GTK_ENTRY(fs->mask_entry)));
1555 if (strlen(fs->mask) == 0)
1561 gtk_file_selection_refresh_button (widget,data);
1564 static gboolean gtk_file_selection_history_combo_list_key_handler(GtkWidget *widget,
1569 g_print("Key pressed! \n");
1575 static gboolean gtk_file_selection_history_combo_list_callback (GtkWidget *thelist,
1576 GdkEventButton *event,
1580 GtkFileSelection *fs = user_data;
1584 list = fs->next_history;
1587 g_free (list->data);
1590 g_list_free (fs->next_history);
1591 fs->next_history = NULL;
1593 path = g_malloc(strlen(gtk_entry_get_text(GTK_ENTRY (((GtkCombo *)fs->history_combo)->entry)))+4);
1594 strcpy (path,gtk_entry_get_text(GTK_ENTRY( ((GtkCombo *)fs->history_combo)->entry)));
1597 gtk_file_selection_populate (fs,path,TRUE);
1605 gtk_file_selection_history_combo_callback (GtkWidget *widget, GdkEventKey *event, gpointer data)
1607 GtkEntry *entry=(GtkEntry *)widget;
1608 GtkFileSelection *fs=data;
1612 g_return_val_if_fail (fs != NULL,FALSE);
1613 g_return_val_if_fail (GTK_IS_FILE_SELECTION (fs),FALSE);
1616 if (event->keyval == GDK_Return)
1618 list = fs->next_history;
1621 g_free (list->data);
1624 g_list_free (fs->next_history);
1625 fs->next_history = NULL;
1627 path = g_malloc(strlen(gtk_entry_get_text(entry))+4);
1628 strcpy (path,gtk_entry_get_text(entry));
1630 gtk_file_selection_populate (fs,path,TRUE);
1632 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1643 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1644 gchar *current_directory)
1648 g_return_if_fail (fs != NULL);
1649 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1650 g_return_if_fail (current_directory != NULL);
1652 current_dir = g_strdup (current_directory);
1654 if(fs->prev_history)
1656 if (strcmp((fs->prev_history)->data,current_dir))
1657 { /*if this item isn't on the top of the list */
1658 fs->prev_history = g_list_prepend(fs->prev_history,g_strdup(current_dir));
1661 fs->prev_history = g_list_prepend(fs->prev_history,g_strdup(current_dir));
1664 gtk_combo_set_popdown_strings (GTK_COMBO (fs->history_combo),fs->prev_history);
1666 g_free (current_dir);
1670 gtk_file_selection_file_button (GtkWidget *widget,
1673 GdkEventButton *bevent,
1676 GtkFileSelection *fs = NULL;
1677 gchar *filename, *temp = NULL;
1679 g_return_if_fail (GTK_IS_CLIST (widget));
1682 g_return_if_fail (fs != NULL);
1683 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1685 gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1686 filename = g_strdup (temp);
1691 switch (bevent->type)
1693 case GDK_2BUTTON_PRESS:
1694 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1698 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1702 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1709 gtk_file_selection_dir_button (GtkWidget *widget,
1712 GdkEventButton *bevent,
1716 GtkFileSelection *fs = NULL;
1717 gchar *filename, *temp = NULL;
1719 g_return_if_fail (GTK_IS_CLIST (widget));
1721 fs = GTK_FILE_SELECTION (user_data);
1722 g_return_if_fail (fs != NULL);
1723 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1725 gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1726 filename = g_strdup (temp);
1731 switch (bevent->type)
1733 case GDK_2BUTTON_PRESS:
1734 list = fs->next_history;
1737 g_free (list->data);
1740 g_list_free (fs->next_history);
1741 fs->next_history = NULL;
1743 gtk_file_selection_populate (fs, filename, FALSE);
1744 gtk_entry_set_text(GTK_ENTRY(fs->selection_entry),fs->saved_entry);
1745 g_free (fs->saved_entry);
1746 fs->saved_entry = NULL;
1750 /* here we need to add the "filename" to the beginning of what's already
1751 in the entry. Save what's in the entry, then restore it on the double click
1753 if (fs->saved_entry) g_free (fs->saved_entry);
1754 fs->saved_entry=g_strdup(gtk_entry_get_text(GTK_ENTRY (fs->selection_entry)));
1756 temp=g_strconcat(filename,fs->saved_entry,NULL);
1757 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), temp);
1763 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1770 gtk_file_selection_undir_button (GtkWidget *widget,
1773 GdkEventButton *bevent,
1776 GtkFileSelection *fs = NULL;
1777 gchar *filename, *temp = NULL;
1779 g_return_if_fail (GTK_IS_CLIST (widget));
1781 fs = GTK_FILE_SELECTION (user_data);
1782 g_return_if_fail (fs != NULL);
1783 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1785 gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1786 filename = g_strdup (temp);
1791 switch (bevent->type)
1794 /* here we need to add the "filename" to the beginning of what's already
1795 in the entry. Save what's in the entry, then restore it on the double click
1797 if (fs->saved_entry)
1799 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),fs->saved_entry);
1800 g_free (fs->saved_entry);
1801 fs->saved_entry = NULL;
1806 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); //?????
1813 gtk_file_selection_populate (GtkFileSelection *fs,
1817 CompletionState *cmpl_state;
1818 PossibleCompletion* poss;
1821 gchar* rem_path = rel_path;
1824 gint did_recurse = FALSE;
1825 gint possible_count = 0;
1826 gint selection_index = -1;
1827 gint file_list_width;
1828 gint dir_list_width;
1830 g_return_if_fail (fs != NULL);
1831 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1833 cmpl_state = (CompletionState*) fs->cmpl_state;
1834 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1836 if (!cmpl_state_okay (cmpl_state))
1838 /* Something went wrong. */
1839 gtk_file_selection_abort (fs);
1843 g_assert (cmpl_state->reference_dir);
1845 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1846 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1847 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1848 gtk_clist_clear (GTK_CLIST (fs->file_list));
1850 /* Set the dir_list to include ./ and ../ */
1853 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1856 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1858 /*reset the max widths of the lists*/
1859 dir_list_width = gdk_string_width(fs->dir_list->style->font,"../");
1860 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width);
1861 file_list_width = 1;
1862 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width);
1866 if (cmpl_is_a_completion (poss))
1868 possible_count += 1;
1870 filename = cmpl_this_completion (poss);
1874 if (cmpl_is_directory (poss))
1876 if (strcmp (filename, "./") != 0 &&
1877 strcmp (filename, "../") != 0)
1879 int width = gdk_string_width(fs->dir_list->style->font,
1881 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1882 if(width > dir_list_width)
1884 dir_list_width = width;
1885 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,
1894 if (gtk_file_selection_match_mask(filename,fs->mask))
1896 int width = gdk_string_width(fs->file_list->style->font,
1898 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1899 if(width > file_list_width)
1901 file_list_width = width;
1902 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1909 int width = gdk_string_width(fs->file_list->style->font,
1911 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1912 if(width > file_list_width)
1914 file_list_width = width;
1915 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1922 poss = cmpl_next_completion (cmpl_state);
1925 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1926 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1928 /* File lists are set. */
1930 g_assert (cmpl_state->reference_dir);
1935 /* User is trying to complete filenames, so advance the user's input
1936 * string to the updated_text, which is the common leading substring
1937 * of all possible completions, and if its a directory attempt
1938 * attempt completions in it. */
1940 if (cmpl_updated_text (cmpl_state)[0])
1943 if (cmpl_updated_dir (cmpl_state))
1945 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1949 gtk_file_selection_populate (fs, dir_name, TRUE);
1955 if (fs->selection_entry)
1956 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1957 cmpl_updated_text (cmpl_state));
1962 selection_index = cmpl_last_valid_char (cmpl_state) -
1963 (strlen (rel_path) - strlen (rem_path));
1964 if (fs->selection_entry)
1965 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1970 if (fs->selection_entry)
1971 /* Here we need to take the old filename and keep it!*/
1972 /*gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");*/
1978 if (fs->selection_entry)
1979 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1981 if (fs->selection_entry)
1983 sel_text = g_strconcat (_("Selection: "),
1984 cmpl_reference_position (cmpl_state),
1987 gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text);
1991 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1997 gtk_file_selection_abort (GtkFileSelection *fs)
2001 sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
2003 /* BEEP gdk_beep(); */
2005 if (fs->selection_entry)
2006 gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
2009 /**********************************************************************/
2010 /* External Interface */
2011 /**********************************************************************/
2013 /* The four completion state selectors
2016 cmpl_updated_text (CompletionState* cmpl_state)
2018 return cmpl_state->updated_text;
2022 cmpl_updated_dir (CompletionState* cmpl_state)
2024 return cmpl_state->re_complete;
2028 cmpl_reference_position (CompletionState* cmpl_state)
2030 return cmpl_state->reference_dir->fullname;
2034 cmpl_last_valid_char (CompletionState* cmpl_state)
2036 return cmpl_state->last_valid_char;
2040 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
2042 static char nothing[2] = "";
2044 if (!cmpl_state_okay (cmpl_state))
2048 else if (text[0] == '/')
2050 strcpy (cmpl_state->updated_text, text);
2052 else if (text[0] == '~')
2057 dir = open_user_dir (text, cmpl_state);
2061 /* spencer says just return ~something, so
2062 * for now just do it. */
2063 strcpy (cmpl_state->updated_text, text);
2068 strcpy (cmpl_state->updated_text, dir->fullname);
2070 slash = strchr (text, '/');
2073 strcat (cmpl_state->updated_text, slash);
2078 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
2079 if (strcmp (cmpl_state->reference_dir->fullname, "/") != 0)
2080 strcat (cmpl_state->updated_text, "/");
2081 strcat (cmpl_state->updated_text, text);
2084 return cmpl_state->updated_text;
2087 /* The three completion selectors
2090 cmpl_this_completion (PossibleCompletion* pc)
2096 cmpl_is_directory (PossibleCompletion* pc)
2098 return pc->is_directory;
2102 cmpl_is_a_completion (PossibleCompletion* pc)
2104 return pc->is_a_completion;
2107 /**********************************************************************/
2108 /* Construction, deletion */
2109 /**********************************************************************/
2111 static CompletionState*
2112 cmpl_init_state (void)
2114 gchar getcwd_buf[2*MAXPATHLEN];
2115 CompletionState *new_state;
2117 new_state = g_new (CompletionState, 1);
2119 /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
2120 * and, if that wasn't bad enough, hangs in doing so.
2122 #if defined(sun) && !defined(__SVR4)
2123 if (!getwd (getcwd_buf))
2125 if (!getcwd (getcwd_buf, MAXPATHLEN))
2128 /* Oh joy, we can't get the current directory. Um..., we should have
2129 * a root directory, right? Right? (Probably not portable to non-Unix)
2131 strcpy (getcwd_buf, "/");
2136 new_state->reference_dir = NULL;
2137 new_state->completion_dir = NULL;
2138 new_state->active_completion_dir = NULL;
2139 new_state->directory_storage = NULL;
2140 new_state->directory_sent_storage = NULL;
2141 new_state->last_valid_char = 0;
2142 new_state->updated_text = g_new (gchar, MAXPATHLEN);
2143 new_state->updated_text_alloc = MAXPATHLEN;
2144 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
2145 new_state->the_completion.text_alloc = MAXPATHLEN;
2146 new_state->user_dir_name_buffer = NULL;
2147 new_state->user_directories = NULL;
2149 new_state->reference_dir = open_dir (getcwd_buf, new_state);
2151 if (!new_state->reference_dir)
2153 /* Directories changing from underneath us, grumble */
2154 strcpy (getcwd_buf, "/");
2162 cmpl_free_dir_list(GList* dp0)
2167 free_dir (dp->data);
2175 cmpl_free_dir_sent_list(GList* dp0)
2180 free_dir_sent (dp->data);
2188 cmpl_free_state (CompletionState* cmpl_state)
2190 cmpl_free_dir_list (cmpl_state->directory_storage);
2191 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
2193 if (cmpl_state->user_dir_name_buffer)
2194 g_free (cmpl_state->user_dir_name_buffer);
2195 if (cmpl_state->user_directories)
2196 g_free (cmpl_state->user_directories);
2197 if (cmpl_state->the_completion.text)
2198 g_free (cmpl_state->the_completion.text);
2199 if (cmpl_state->updated_text)
2200 g_free (cmpl_state->updated_text);
2202 g_free (cmpl_state);
2206 free_dir(CompletionDir* dir)
2208 g_free(dir->fullname);
2213 free_dir_sent(CompletionDirSent* sent)
2215 g_free(sent->name_buffer);
2216 g_free(sent->entries);
2221 prune_memory_usage(CompletionState *cmpl_state)
2223 GList* cdsl = cmpl_state->directory_sent_storage;
2224 GList* cdl = cmpl_state->directory_storage;
2228 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
2232 cmpl_free_dir_sent_list(cdsl->next);
2236 cmpl_state->directory_storage = NULL;
2238 if (cdl->data == cmpl_state->reference_dir)
2239 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
2241 free_dir (cdl->data);
2248 /**********************************************************************/
2249 /* The main entrances. */
2250 /**********************************************************************/
2252 static PossibleCompletion*
2253 cmpl_completion_matches (gchar* text_to_complete,
2254 gchar** remaining_text,
2255 CompletionState* cmpl_state)
2258 PossibleCompletion *poss;
2260 prune_memory_usage(cmpl_state);
2262 g_assert (text_to_complete != NULL);
2264 cmpl_state->user_completion_index = -1;
2265 cmpl_state->last_completion_text = text_to_complete;
2266 cmpl_state->the_completion.text[0] = 0;
2267 cmpl_state->last_valid_char = 0;
2268 cmpl_state->updated_text_len = -1;
2269 cmpl_state->updated_text[0] = 0;
2270 cmpl_state->re_complete = FALSE;
2272 first_slash = strchr (text_to_complete, '/');
2274 if (text_to_complete[0] == '~' && !first_slash)
2276 /* Text starts with ~ and there is no slash, show all the
2277 * home directory completions.
2279 poss = attempt_homedir_completion (text_to_complete, cmpl_state);
2281 update_cmpl(poss, cmpl_state);
2286 cmpl_state->reference_dir =
2287 open_ref_dir (text_to_complete, remaining_text, cmpl_state);
2289 if(!cmpl_state->reference_dir)
2292 cmpl_state->completion_dir =
2293 find_completion_dir (*remaining_text, remaining_text, cmpl_state);
2295 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2297 if(!cmpl_state->completion_dir)
2300 cmpl_state->completion_dir->cmpl_index = -1;
2301 cmpl_state->completion_dir->cmpl_parent = NULL;
2302 cmpl_state->completion_dir->cmpl_text = *remaining_text;
2304 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2306 cmpl_state->reference_dir = cmpl_state->completion_dir;
2308 poss = attempt_file_completion(cmpl_state);
2310 update_cmpl(poss, cmpl_state);
2315 static PossibleCompletion*
2316 cmpl_next_completion (CompletionState* cmpl_state)
2318 PossibleCompletion* poss = NULL;
2320 cmpl_state->the_completion.text[0] = 0;
2322 if(cmpl_state->user_completion_index >= 0)
2323 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
2325 poss = attempt_file_completion(cmpl_state);
2327 update_cmpl(poss, cmpl_state);
2332 /**********************************************************************/
2333 /* Directory Operations */
2334 /**********************************************************************/
2336 /* Open the directory where completion will begin from, if possible. */
2337 static CompletionDir*
2338 open_ref_dir(gchar* text_to_complete,
2339 gchar** remaining_text,
2340 CompletionState* cmpl_state)
2343 CompletionDir *new_dir;
2345 first_slash = strchr(text_to_complete, '/');
2347 if (text_to_complete[0] == '~')
2349 new_dir = open_user_dir(text_to_complete, cmpl_state);
2354 *remaining_text = first_slash + 1;
2356 *remaining_text = text_to_complete + strlen(text_to_complete);
2363 else if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
2365 gchar *tmp = g_strdup(text_to_complete);
2369 while (*p && *p != '*' && *p != '?')
2373 p = strrchr(tmp, '/');
2381 new_dir = open_dir(tmp, cmpl_state);
2384 *remaining_text = text_to_complete +
2385 ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp));
2389 /* If no possible candidates, use the cwd */
2390 gchar *curdir = g_get_current_dir ();
2392 new_dir = open_dir(curdir, cmpl_state);
2395 *remaining_text = text_to_complete;
2404 *remaining_text = text_to_complete;
2406 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
2411 new_dir->cmpl_index = -1;
2412 new_dir->cmpl_parent = NULL;
2418 /* open a directory by user name */
2419 static CompletionDir*
2420 open_user_dir(gchar* text_to_complete,
2421 CompletionState *cmpl_state)
2426 g_assert(text_to_complete && text_to_complete[0] == '~');
2428 first_slash = strchr(text_to_complete, '/');
2431 cmp_len = first_slash - text_to_complete - 1;
2433 cmp_len = strlen(text_to_complete + 1);
2438 gchar *homedir = g_get_home_dir ();
2441 return open_dir(homedir, cmpl_state);
2448 char* copy = g_new(char, cmp_len + 1);
2450 strncpy(copy, text_to_complete + 1, cmp_len);
2452 pwd = getpwnam(copy);
2460 return open_dir(pwd->pw_dir, cmpl_state);
2464 /* open a directory relative the the current relative directory */
2465 static CompletionDir*
2466 open_relative_dir(gchar* dir_name,
2468 CompletionState *cmpl_state)
2470 gchar path_buf[2*MAXPATHLEN];
2472 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
2474 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2478 strcpy(path_buf, dir->fullname);
2480 if(dir->fullname_len > 1)
2482 path_buf[dir->fullname_len] = '/';
2483 strcpy(path_buf + dir->fullname_len + 1, dir_name);
2487 strcpy(path_buf + dir->fullname_len, dir_name);
2490 return open_dir(path_buf, cmpl_state);
2493 /* after the cache lookup fails, really open a new directory */
2494 static CompletionDirSent*
2495 open_new_dir(gchar* dir_name, struct stat* sbuf, gboolean stat_subdirs)
2497 CompletionDirSent* sent;
2500 struct dirent *dirent_ptr;
2501 gint buffer_size = 0;
2502 gint entry_count = 0;
2504 struct stat ent_sbuf;
2505 char path_buf[MAXPATHLEN*2];
2508 sent = g_new(CompletionDirSent, 1);
2509 sent->mtime = sbuf->st_mtime;
2510 sent->inode = sbuf->st_ino;
2511 sent->device = sbuf->st_dev;
2513 path_buf_len = strlen(dir_name);
2515 if (path_buf_len > MAXPATHLEN)
2517 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2521 strcpy(path_buf, dir_name);
2523 directory = opendir(dir_name);
2531 while((dirent_ptr = readdir(directory)) != NULL)
2533 int entry_len = strlen(dirent_ptr->d_name);
2534 buffer_size += entry_len + 1;
2537 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
2539 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2540 closedir(directory);
2545 sent->name_buffer = g_new(gchar, buffer_size);
2546 sent->entries = g_new(CompletionDirEntry, entry_count);
2547 sent->entry_count = entry_count;
2549 buffer_ptr = sent->name_buffer;
2551 rewinddir(directory);
2553 for(i = 0; i < entry_count; i += 1)
2555 dirent_ptr = readdir(directory);
2560 closedir(directory);
2564 strcpy(buffer_ptr, dirent_ptr->d_name);
2565 sent->entries[i].entry_name = buffer_ptr;
2566 buffer_ptr += strlen(dirent_ptr->d_name);
2570 path_buf[path_buf_len] = '/';
2571 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
2575 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
2576 sent->entries[i].is_dir = 1;
2578 /* stat may fail, and we don't mind, since it could be a
2579 * dangling symlink. */
2580 sent->entries[i].is_dir = 0;
2583 sent->entries[i].is_dir = 1;
2586 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
2588 closedir(directory);
2594 check_dir(gchar *dir_name, struct stat *result, gboolean *stat_subdirs)
2596 /* A list of directories that we know only contain other directories.
2597 * Trying to stat every file in these directories would be very
2604 struct stat statbuf;
2605 } no_stat_dirs[] = {
2606 { "/afs", FALSE, { 0 } },
2607 { "/net", FALSE, { 0 } }
2610 static const gint n_no_stat_dirs = sizeof(no_stat_dirs) / sizeof(no_stat_dirs[0]);
2611 static gboolean initialized = FALSE;
2618 for (i = 0; i < n_no_stat_dirs; i++)
2620 if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0)
2621 no_stat_dirs[i].present = TRUE;
2625 if(stat(dir_name, result) < 0)
2631 *stat_subdirs = TRUE;
2632 for (i=0; i<n_no_stat_dirs; i++)
2634 if (no_stat_dirs[i].present &&
2635 (no_stat_dirs[i].statbuf.st_dev == result->st_dev) &&
2636 (no_stat_dirs[i].statbuf.st_ino == result->st_ino))
2638 *stat_subdirs = FALSE;
2646 /* open a directory by absolute pathname */
2647 static CompletionDir*
2648 open_dir(gchar* dir_name, CompletionState* cmpl_state)
2651 gboolean stat_subdirs;
2652 CompletionDirSent *sent;
2655 if (!check_dir (dir_name, &sbuf, &stat_subdirs))
2658 cdsl = cmpl_state->directory_sent_storage;
2664 if(sent->inode == sbuf.st_ino &&
2665 sent->mtime == sbuf.st_mtime &&
2666 sent->device == sbuf.st_dev)
2667 return attach_dir(sent, dir_name, cmpl_state);
2672 sent = open_new_dir(dir_name, &sbuf, stat_subdirs);
2675 cmpl_state->directory_sent_storage =
2676 g_list_prepend(cmpl_state->directory_sent_storage, sent);
2678 return attach_dir(sent, dir_name, cmpl_state);
2684 static CompletionDir*
2685 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
2687 CompletionDir* new_dir;
2689 new_dir = g_new(CompletionDir, 1);
2691 cmpl_state->directory_storage =
2692 g_list_prepend(cmpl_state->directory_storage, new_dir);
2694 new_dir->sent = sent;
2695 new_dir->fullname = g_strdup(dir_name);
2696 new_dir->fullname_len = strlen(dir_name);
2702 correct_dir_fullname(CompletionDir* cmpl_dir)
2704 gint length = strlen(cmpl_dir->fullname);
2707 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
2711 strcpy(cmpl_dir->fullname, "/");
2712 cmpl_dir->fullname_len = 1;
2715 cmpl_dir->fullname[length - 2] = 0;
2718 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
2719 cmpl_dir->fullname[length - 2] = 0;
2720 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
2724 strcpy(cmpl_dir->fullname, "/");
2725 cmpl_dir->fullname_len = 1;
2729 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2735 cmpl_dir->fullname[length - 2] = 0;
2737 if(!correct_parent(cmpl_dir, &sbuf))
2740 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
2744 strcpy(cmpl_dir->fullname, "/");
2745 cmpl_dir->fullname_len = 1;
2749 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2755 cmpl_dir->fullname[length - 3] = 0;
2757 if(!correct_parent(cmpl_dir, &sbuf))
2761 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
2767 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
2774 last_slash = strrchr(cmpl_dir->fullname, '/');
2776 g_assert(last_slash);
2778 if(last_slash != cmpl_dir->fullname)
2779 { /* last_slash[0] = 0; */ }
2786 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2792 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2793 /* it wasn't a link */
2799 last_slash[0] = '/'; */
2801 /* it was a link, have to figure it out the hard way */
2803 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2808 g_free(cmpl_dir->fullname);
2810 cmpl_dir->fullname = new_name;
2816 find_parent_dir_fullname(gchar* dirname)
2818 gchar buffer[MAXPATHLEN];
2819 gchar buffer2[MAXPATHLEN];
2821 #if defined(sun) && !defined(__SVR4)
2824 if(!getcwd(buffer, MAXPATHLEN))
2831 if(chdir(dirname) != 0 || chdir("..") != 0)
2837 #if defined(sun) && !defined(__SVR4)
2840 if(!getcwd(buffer2, MAXPATHLEN))
2849 if(chdir(buffer) != 0)
2855 return g_strdup(buffer2);
2858 /**********************************************************************/
2859 /* Completion Operations */
2860 /**********************************************************************/
2862 static PossibleCompletion*
2863 attempt_homedir_completion(gchar* text_to_complete,
2864 CompletionState *cmpl_state)
2868 if (!cmpl_state->user_dir_name_buffer &&
2869 !get_pwdb(cmpl_state))
2871 length = strlen(text_to_complete) - 1;
2873 cmpl_state->user_completion_index += 1;
2875 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2877 index = first_diff_index(text_to_complete + 1,
2878 cmpl_state->user_directories
2879 [cmpl_state->user_completion_index].login);
2886 if(cmpl_state->last_valid_char < (index + 1))
2887 cmpl_state->last_valid_char = index + 1;
2888 cmpl_state->user_completion_index += 1;
2892 cmpl_state->the_completion.is_a_completion = 1;
2893 cmpl_state->the_completion.is_directory = 1;
2895 append_completion_text("~", cmpl_state);
2897 append_completion_text(cmpl_state->
2898 user_directories[cmpl_state->user_completion_index].login,
2901 return append_completion_text("/", cmpl_state);
2904 if(text_to_complete[1] ||
2905 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2907 cmpl_state->user_completion_index = -1;
2912 cmpl_state->user_completion_index += 1;
2913 cmpl_state->the_completion.is_a_completion = 1;
2914 cmpl_state->the_completion.is_directory = 1;
2916 return append_completion_text("~/", cmpl_state);
2920 /* returns the index (>= 0) of the first differing character,
2921 * PATTERN_MATCH if the completion matches */
2923 first_diff_index(gchar* pat, gchar* text)
2927 while(*pat && *text && *text == *pat)
2937 return PATTERN_MATCH;
2940 static PossibleCompletion*
2941 append_completion_text(gchar* text, CompletionState* cmpl_state)
2945 if(!cmpl_state->the_completion.text)
2948 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2950 if(cmpl_state->the_completion.text_alloc > len)
2952 strcat(cmpl_state->the_completion.text, text);
2953 return &cmpl_state->the_completion;
2956 while(i < len) { i <<= 1; }
2958 cmpl_state->the_completion.text_alloc = i;
2960 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2962 if(!cmpl_state->the_completion.text)
2966 strcat(cmpl_state->the_completion.text, text);
2967 return &cmpl_state->the_completion;
2971 static CompletionDir*
2972 find_completion_dir(gchar* text_to_complete,
2973 gchar** remaining_text,
2974 CompletionState* cmpl_state)
2976 gchar* first_slash = strchr(text_to_complete, '/');
2977 CompletionDir* dir = cmpl_state->reference_dir;
2978 CompletionDir* next;
2979 *remaining_text = text_to_complete;
2983 gint len = first_slash - *remaining_text;
2985 gchar *found_name = NULL; /* Quiet gcc */
2987 gchar* pat_buf = g_new (gchar, len + 1);
2989 strncpy(pat_buf, *remaining_text, len);
2992 for(i = 0; i < dir->sent->entry_count; i += 1)
2994 if(dir->sent->entries[i].is_dir &&
2995 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2996 FNMATCH_FLAGS)!= FNM_NOMATCH)
3006 found_name = dir->sent->entries[i].entry_name;
3013 /* Perhaps we are trying to open an automount directory */
3014 found_name = pat_buf;
3017 next = open_relative_dir(found_name, dir, cmpl_state);
3025 next->cmpl_parent = dir;
3029 if(!correct_dir_fullname(dir))
3035 *remaining_text = first_slash + 1;
3036 first_slash = strchr(*remaining_text, '/');
3045 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
3049 if(!poss || !cmpl_is_a_completion(poss))
3052 cmpl_len = strlen(cmpl_this_completion(poss));
3054 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
3056 cmpl_state->updated_text =
3057 (gchar*)g_realloc(cmpl_state->updated_text,
3058 cmpl_state->updated_text_alloc);
3059 cmpl_state->updated_text_alloc = 2*cmpl_len;
3062 if(cmpl_state->updated_text_len < 0)
3064 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
3065 cmpl_state->updated_text_len = cmpl_len;
3066 cmpl_state->re_complete = cmpl_is_directory(poss);
3068 else if(cmpl_state->updated_text_len == 0)
3070 cmpl_state->re_complete = FALSE;
3075 first_diff_index(cmpl_state->updated_text,
3076 cmpl_this_completion(poss));
3078 cmpl_state->re_complete = FALSE;
3080 if(first_diff == PATTERN_MATCH)
3083 if(first_diff > cmpl_state->updated_text_len)
3084 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
3086 cmpl_state->updated_text_len = first_diff;
3087 cmpl_state->updated_text[first_diff] = 0;
3091 static PossibleCompletion*
3092 attempt_file_completion(CompletionState *cmpl_state)
3094 gchar *pat_buf, *first_slash;
3095 CompletionDir *dir = cmpl_state->active_completion_dir;
3097 dir->cmpl_index += 1;
3099 if(dir->cmpl_index == dir->sent->entry_count)
3101 if(dir->cmpl_parent == NULL)
3103 cmpl_state->active_completion_dir = NULL;
3109 cmpl_state->active_completion_dir = dir->cmpl_parent;
3111 return attempt_file_completion(cmpl_state);
3115 g_assert(dir->cmpl_text);
3117 first_slash = strchr(dir->cmpl_text, '/');
3121 gint len = first_slash - dir->cmpl_text;
3123 pat_buf = g_new (gchar, len + 1);
3124 strncpy(pat_buf, dir->cmpl_text, len);
3129 gint len = strlen(dir->cmpl_text);
3131 pat_buf = g_new (gchar, len + 2);
3132 strcpy(pat_buf, dir->cmpl_text);
3133 strcpy(pat_buf + len, "*");
3138 if(dir->sent->entries[dir->cmpl_index].is_dir)
3140 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3141 FNMATCH_FLAGS) != FNM_NOMATCH)
3143 CompletionDir* new_dir;
3145 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
3154 new_dir->cmpl_parent = dir;
3156 new_dir->cmpl_index = -1;
3157 new_dir->cmpl_text = first_slash + 1;
3159 cmpl_state->active_completion_dir = new_dir;
3162 return attempt_file_completion(cmpl_state);
3167 return attempt_file_completion(cmpl_state);
3173 return attempt_file_completion(cmpl_state);
3178 if(dir->cmpl_parent != NULL)
3180 append_completion_text(dir->fullname +
3181 strlen(cmpl_state->completion_dir->fullname) + 1,
3183 append_completion_text("/", cmpl_state);
3186 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
3188 cmpl_state->the_completion.is_a_completion =
3189 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3190 FNMATCH_FLAGS) != FNM_NOMATCH);
3192 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3193 if(dir->sent->entries[dir->cmpl_index].is_dir)
3194 append_completion_text("/", cmpl_state);
3197 return &cmpl_state->the_completion;
3203 get_pwdb(CompletionState* cmpl_state)
3205 struct passwd *pwd_ptr;
3207 gint len = 0, i, count = 0;
3209 if(cmpl_state->user_dir_name_buffer)
3213 while ((pwd_ptr = getpwent()) != NULL)
3215 len += strlen(pwd_ptr->pw_name);
3216 len += strlen(pwd_ptr->pw_dir);
3223 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
3224 cmpl_state->user_directories = g_new(CompletionUserDir, count);
3225 cmpl_state->user_directories_len = count;
3227 buf_ptr = cmpl_state->user_dir_name_buffer;
3229 for(i = 0; i < count; i += 1)
3231 pwd_ptr = getpwent();
3238 strcpy(buf_ptr, pwd_ptr->pw_name);
3239 cmpl_state->user_directories[i].login = buf_ptr;
3240 buf_ptr += strlen(buf_ptr);
3242 strcpy(buf_ptr, pwd_ptr->pw_dir);
3243 cmpl_state->user_directories[i].homedir = buf_ptr;
3244 buf_ptr += strlen(buf_ptr);
3248 qsort(cmpl_state->user_directories,
3249 cmpl_state->user_directories_len,
3250 sizeof(CompletionUserDir),
3259 if(cmpl_state->user_dir_name_buffer)
3260 g_free(cmpl_state->user_dir_name_buffer);
3261 if(cmpl_state->user_directories)
3262 g_free(cmpl_state->user_directories);
3264 cmpl_state->user_dir_name_buffer = NULL;
3265 cmpl_state->user_directories = NULL;
3271 compare_user_dir(const void* a, const void* b)
3273 return strcmp((((CompletionUserDir*)a))->login,
3274 (((CompletionUserDir*)b))->login);
3278 compare_cmpl_dir(const void* a, const void* b)
3280 return strcmp((((CompletionDirEntry*)a))->entry_name,
3281 (((CompletionDirEntry*)b))->entry_name);
3285 cmpl_state_okay(CompletionState* cmpl_state)
3287 return cmpl_state && cmpl_state->reference_dir;
3291 cmpl_strerror(gint err)
3293 if(err == CMPL_ERRNO_TOO_LONG)
3294 return "Name too long";
3296 return g_strerror (err);
3303 /* Get the selected filename and print it to the console */
3304 void file_ok_sel( GtkWidget *w,
3305 GtkFileSelection *fs )
3307 g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
3310 void destroy( GtkWidget *widget,
3321 gtk_init (&argc, &argv);
3323 /* Create a new file selection widget */
3324 filew = gtk_file_selection_new ("Michael's Glorious File Selector");
3325 // gtk_file_selection_complete(GTK_FILE_SELECTION(filew),"bob");
3328 gtk_signal_connect (GTK_OBJECT (filew), "destroy",
3329 (GtkSignalFunc) destroy, &filew);
3330 /* Connect the ok_button to file_ok_sel function */
3331 gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
3332 "clicked", (GtkSignalFunc) file_ok_sel, filew );
3334 /* Connect the cancel_button to destroy the widget */
3335 gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION
3336 (filew)->cancel_button),
3337 "clicked", (GtkSignalFunc) gtk_widget_destroy,
3338 GTK_OBJECT (filew));
3341 gtk_widget_show(filew);
3344 g_print("%d",gtk_file_selection_match_mask("mask.c","m*.c"));
3345 g_print("%d",gtk_file_selection_match_mask("mask.c","m???.c"));
3346 g_print("%d",gtk_file_selection_match_mask("mask.c","m??*.c"));
3347 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c"));
3348 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c???"));
3349 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c*"));
3350 g_print("%d",gtk_file_selection_match_mask("mask.cout","n*.c???"));
3351 g_print("%d",gtk_file_selection_match_mask("mask.c","[mn]*"));
3352 g_print("%d",gtk_file_selection_match_mask("COPYING","*.xpm"));