2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 /* GTK - The GIMP Toolkit
23 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
25 * This library is free software; you can redistribute it and/or
26 * modify it under the terms of the GNU Library General Public
27 * License as published by the Free Software Foundation; either
28 * version 2 of the License, or (at your option) any later version.
30 * This library is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
33 * Library General Public License for more details.
35 * You should have received a copy of the GNU Library General Public
36 * License along with this library; if not, write to the
37 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
38 * Boston, MA 02111-1307, USA.
42 * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
43 * file for a list of people on the GTK+ Team. See the ChangeLog
44 * files for a list of changes. These files are distributed with
45 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
49 // leo FIXME: if we keep this file then we'll need to ask permission to the author, this is LGPL
50 // This file is from the Advanced File Selector widget
51 // by Michael Torrie <torriem@byu.edu>
52 // http://students.cs.byu.edu/~torriem/gtk/
55 #include <sys/types.h>
57 #include <sys/param.h>
67 #include "gdk/gdkkeysyms.h"
68 #include "gtk/gtkbutton.h"
69 #include "gtk/gtkentry.h"
70 #include "gtkfilesel.h"
71 #include "gtk/gtkhbox.h"
72 #include "gtk/gtkhbbox.h"
73 #include "gtk/gtklabel.h"
74 #include "gtk/gtklist.h"
75 #include "gtk/gtklistitem.h"
76 #include "gtk/gtkmain.h"
77 #include "gtk/gtkscrolledwindow.h"
78 #include "gtk/gtksignal.h"
79 #include "gtk/gtkvbox.h"
80 #include "gtk/gtkmenu.h"
81 #include "gtk/gtkmenuitem.h"
82 #include "gtk/gtkoptionmenu.h"
83 #include "gtk/gtkclist.h"
84 #include "gtk/gtkdialog.h"
85 #include "gtk/gtkcombo.h"
86 #include "gtk/gtkframe.h"
89 //#include "gtk/gtkintl.h"
90 #define _(String) (String)
92 #define DIR_LIST_WIDTH 180
93 #define DIR_LIST_HEIGHT 180
94 #define FILE_LIST_WIDTH 180
95 #define FILE_LIST_HEIGHT 180
97 /* I've put this here so it doesn't get confused with the
98 * file completion interface */
99 typedef struct _HistoryCallbackArg HistoryCallbackArg;
101 struct _HistoryCallbackArg
104 GtkWidget *menu_item;
108 typedef struct _CompletionState CompletionState;
109 typedef struct _CompletionDir CompletionDir;
110 typedef struct _CompletionDirSent CompletionDirSent;
111 typedef struct _CompletionDirEntry CompletionDirEntry;
112 typedef struct _CompletionUserDir CompletionUserDir;
113 typedef struct _PossibleCompletion PossibleCompletion;
115 /* Non-external file completion decls and structures */
117 /* A contant telling PRCS how many directories to cache. Its actually
118 * kept in a list, so the geometry isn't important. */
119 #define CMPL_DIRECTORY_CACHE_SIZE 10
121 /* A constant used to determine whether a substring was an exact
122 * match by first_diff_index()
124 #define PATTERN_MATCH -1
125 /* The arguments used by all fnmatch() calls below
127 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
129 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
131 /* This structure contains all the useful information about a directory
132 * for the purposes of filename completion. These structures are cached
133 * in the CompletionState struct. CompletionDir's are reference counted.
135 struct _CompletionDirSent
142 gchar *name_buffer; /* memory segment containing names of all entries */
144 struct _CompletionDirEntry *entries;
147 struct _CompletionDir
149 CompletionDirSent *sent;
154 struct _CompletionDir *cmpl_parent;
159 /* This structure contains pairs of directory entry names with a flag saying
160 * whether or not they are a valid directory. NOTE: This information is used
161 * to provide the caller with information about whether to update its completions
162 * or try to open a file. Since directories are cached by the directory mtime,
163 * a symlink which points to an invalid file (which will not be a directory),
164 * will not be reevaluated if that file is created, unless the containing
165 * directory is touched. I consider this case to be worth ignoring (josh).
167 struct _CompletionDirEntry
173 struct _CompletionUserDir
179 struct _PossibleCompletion
181 /* accessible fields, all are accessed externally by functions
185 gint is_a_completion;
197 struct _CompletionState
199 gint last_valid_char;
201 gint updated_text_len;
202 gint updated_text_alloc;
205 gchar *user_dir_name_buffer;
206 gint user_directories_len;
208 gchar *last_completion_text;
210 gint user_completion_index; /* if >= 0, currently completing ~user */
212 struct _CompletionDir *completion_dir; /* directory completing from */
213 struct _CompletionDir *active_completion_dir;
215 struct _PossibleCompletion the_completion;
217 struct _CompletionDir *reference_dir; /* initial directory */
219 GList* directory_storage;
220 GList* directory_sent_storage;
222 struct _CompletionUserDir *user_directories;
226 /* File completion functions which would be external, were they used
227 * outside of this file.
230 static CompletionState* cmpl_init_state (void);
231 static void cmpl_free_state (CompletionState *cmpl_state);
232 static gint cmpl_state_okay (CompletionState* cmpl_state);
233 static gchar* cmpl_strerror (gint);
235 static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete,
236 gchar **remaining_text,
237 CompletionState *cmpl_state);
239 /* Returns a name for consideration, possibly a completion, this name
240 * will be invalid after the next call to cmpl_next_completion.
242 static char* cmpl_this_completion (PossibleCompletion*);
244 /* True if this completion matches the given text. Otherwise, this
245 * output can be used to have a list of non-completions.
247 static gint cmpl_is_a_completion (PossibleCompletion*);
249 /* True if the completion is a directory
251 static gint cmpl_is_directory (PossibleCompletion*);
253 /* Obtains the next completion, or NULL
255 static PossibleCompletion* cmpl_next_completion (CompletionState*);
257 /* Updating completions: the return value of cmpl_updated_text() will
258 * be text_to_complete completed as much as possible after the most
259 * recent call to cmpl_completion_matches. For the present
260 * application, this is the suggested replacement for the user's input
261 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
264 static gchar* cmpl_updated_text (CompletionState* cmpl_state);
266 /* After updating, to see if the completion was a directory, call
267 * this. If it was, you should consider re-calling completion_matches.
269 static gint cmpl_updated_dir (CompletionState* cmpl_state);
271 /* Current location: if using file completion, return the current
272 * directory, from which file completion begins. More specifically,
273 * the cwd concatenated with all exact completions up to the last
274 * directory delimiter('/').
276 static gchar* cmpl_reference_position (CompletionState* cmpl_state);
278 /* backing up: if cmpl_completion_matches returns NULL, you may query
279 * the index of the last completable character into cmpl_updated_text.
281 static gint cmpl_last_valid_char (CompletionState* cmpl_state);
283 /* When the user selects a non-directory, call cmpl_completion_fullname
284 * to get the full name of the selected file.
286 static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);
289 /* Directory operations. */
290 static CompletionDir* open_ref_dir (gchar* text_to_complete,
291 gchar** remaining_text,
292 CompletionState* cmpl_state);
293 static gboolean check_dir (gchar *dir_name,
295 gboolean *stat_subdirs);
296 static CompletionDir* open_dir (gchar* dir_name,
297 CompletionState* cmpl_state);
298 static CompletionDir* open_user_dir (gchar* text_to_complete,
299 CompletionState *cmpl_state);
300 static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
301 CompletionState *cmpl_state);
302 static CompletionDirSent* open_new_dir (gchar* dir_name,
304 gboolean stat_subdirs);
305 static gint correct_dir_fullname (CompletionDir* cmpl_dir);
306 static gint correct_parent (CompletionDir* cmpl_dir,
308 static gchar* find_parent_dir_fullname (gchar* dirname);
309 static CompletionDir* attach_dir (CompletionDirSent* sent,
311 CompletionState *cmpl_state);
312 static void free_dir_sent (CompletionDirSent* sent);
313 static void free_dir (CompletionDir *dir);
314 static void prune_memory_usage(CompletionState *cmpl_state);
316 /* Completion operations */
317 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
318 CompletionState *cmpl_state);
319 static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);
320 static CompletionDir* find_completion_dir(gchar* text_to_complete,
321 gchar** remaining_text,
322 CompletionState* cmpl_state);
323 static PossibleCompletion* append_completion_text(gchar* text,
324 CompletionState* cmpl_state);
325 static gint get_pwdb(CompletionState* cmpl_state);
326 static gint first_diff_index(gchar* pat, gchar* text);
327 static gint compare_user_dir(const void* a, const void* b);
328 static gint compare_cmpl_dir(const void* a, const void* b);
329 static void update_cmpl(PossibleCompletion* poss,
330 CompletionState* cmpl_state);
332 static void gtk_file_selection_class_init (GtkFileSelectionClass *klass);
333 static void gtk_file_selection_init (GtkFileSelection *filesel);
334 static void gtk_file_selection_destroy (GtkObject *object);
335 static gint gtk_file_selection_key_press (GtkWidget *widget,
339 static void gtk_file_selection_file_button (GtkWidget *widget,
342 GdkEventButton *bevent,
345 static void gtk_file_selection_dir_button (GtkWidget *widget,
348 GdkEventButton *bevent,
351 static void gtk_file_selection_undir_button (GtkWidget *widget,
354 GdkEventButton *bevent,
357 static void gtk_file_selection_populate (GtkFileSelection *fs,
360 static void gtk_file_selection_abort (GtkFileSelection *fs);
362 static void gtk_file_selection_update_history_menu (GtkFileSelection *fs,
365 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
366 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
367 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
369 static gboolean gtk_file_selection_history_combo_callback (GtkWidget *widget, GdkEventKey *event, gpointer data);
370 static gboolean gtk_file_selection_history_combo_list_key_handler(GtkWidget *widget,
373 static gboolean gtk_file_selection_history_combo_list_callback (GtkWidget *thelist,
374 GdkEventButton *event,
376 static void gtk_file_selection_mask_entry_callback (GtkWidget *widget, gpointer data);
377 static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);
378 static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
379 static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
380 static void gtk_file_selection_home_button (GtkWidget *widget, gpointer data);
381 static void gtk_file_selection_up_button (GtkWidget *widget, gpointer data);
382 static void gtk_file_selection_prev_button (GtkWidget *widget, gpointer data);
383 static void gtk_file_selection_next_button (GtkWidget *widget, gpointer data);
384 static void gtk_file_selection_refresh_button (GtkWidget *widget, gpointer data);
386 static gint gtk_file_selection_match_char (gchar, gchar *mask);
387 static gint gtk_file_selection_match_mask (gchar *,gchar *);
390 static GtkWindowClass *parent_class = NULL;
392 /* Saves errno when something cmpl does fails. */
393 static gint cmpl_errno;
396 * Make prev and next inactive if their respective *
397 * histories are empty.
398 * Add facilities for handling hidden files and *
400 * Add an api to access the mask, and hidden files *
401 * check box? (prob not in 1.2.x series) *
404 /* Routine for applying mask to filenames *
405 * Need to be optimized to minimize recursion *
406 * help the for loop by looking for the next *
407 * instance of the mask character following *
408 * the '*'. ei *.c -- look for '.' *
409 * Also, swap all *? pairs (-> ?*), as that *
410 * will make it possible to look ahead (? *
411 * makes it very nondeterministic as in *?.c *
412 * which really is ?*.c *
413 * Allow multiply masks, separted by commas *
414 * Allow more flexible [] handling (ie [a-zA-Z] *
417 static gint gtk_file_selection_match_char (gchar text, gchar *mask){
424 if (!strchr (mask,']')) return 0;
425 maskc = g_strdup(mask + 1); /* get the portion of mask inside []*/
427 (*(strchr (maskc,']'))) = 0;
428 s = strlen ((char *)maskc);
430 for (x = 0; x < s; x++){
431 if (text == maskc[x])
441 if (mask[0] == '?') return 1;
442 if (mask[0] == text) return 1;
448 static gint gtk_file_selection_match_mask (gchar *text, gchar *mask){
455 if (mask[0] == 0 && text[0] == 0) return 1;
459 for (tc = 0; tc <= strlen(text); tc++)
461 if (gtk_file_selection_match_mask (text + tc, mask + 1))
466 mc = gtk_file_selection_match_char (text[0], mask);
469 return gtk_file_selection_match_mask (text + 1, mask + mc);
475 gtk_file_selection_get_type (void)
477 static GtkType file_selection_type = 0;
479 if (!file_selection_type)
481 static const GtkTypeInfo filesel_info =
484 sizeof (GtkFileSelection),
485 sizeof (GtkFileSelectionClass),
486 (GtkClassInitFunc) gtk_file_selection_class_init,
487 (GtkObjectInitFunc) gtk_file_selection_init,
488 /* reserved_1 */ NULL,
489 /* reserved_2 */ NULL,
490 (GtkClassInitFunc) NULL,
493 file_selection_type = gtk_type_unique (GTK_TYPE_WINDOW, &filesel_info);
496 return file_selection_type;
500 gtk_file_selection_class_init (GtkFileSelectionClass *class)
502 GtkObjectClass *object_class;
504 object_class = (GtkObjectClass*) class;
506 parent_class = gtk_type_class (GTK_TYPE_WINDOW);
508 object_class->destroy = gtk_file_selection_destroy;
512 gtk_file_selection_init (GtkFileSelection *filesel)
514 GtkWidget *entry_vbox;
516 GtkWidget *list_hbox;
517 GtkWidget *confirm_area;
520 GtkWidget *pulldown_hbox;
521 GtkWidget *scrolled_win;
522 GtkWidget *mask_label;
524 GtkWidget *label_lookingin;
525 GtkWidget *up_button;
526 GtkWidget *home_button;
527 GtkWidget *prev_button;
528 GtkWidget *next_button;
529 GtkWidget *refresh_button;
532 char *file_title [2];
534 filesel->cmpl_state = cmpl_init_state ();
537 filesel->prev_history=NULL;
538 filesel->next_history=NULL;
539 filesel->saved_entry=NULL;
541 /* The dialog-sized vertical box */
542 filesel->main_vbox = gtk_vbox_new (FALSE, 10);
543 gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
544 gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
545 gtk_widget_show (filesel->main_vbox);
547 /* The horizontal box containing create, rename etc. buttons */
548 filesel->button_area = gtk_hbutton_box_new ();
549 gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START);
550 gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0);
551 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area,
553 gtk_widget_show (filesel->button_area);
555 gtk_file_selection_show_fileop_buttons(filesel);
557 /* hbox for pulldown menu */
558 pulldown_hbox = gtk_hbox_new (FALSE, 5);
559 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
560 gtk_widget_show (pulldown_hbox);
562 /* The combo box that replaces the pulldown menu */
563 label_lookingin = gtk_label_new (_("Looking in:"));
564 gtk_widget_show (label_lookingin);
565 gtk_box_pack_start (GTK_BOX (pulldown_hbox), label_lookingin, FALSE, FALSE, 0);
567 filesel->history_combo = gtk_combo_new();
568 gtk_widget_show(filesel->history_combo);
569 gtk_combo_set_value_in_list(GTK_COMBO(filesel->history_combo),FALSE,FALSE);
570 gtk_box_pack_start (GTK_BOX(pulldown_hbox),filesel->history_combo,
572 gtk_signal_connect(GTK_OBJECT(((GtkCombo *)filesel->history_combo)->entry),"key-press-event",
573 (GtkSignalFunc) gtk_file_selection_history_combo_callback,
576 gtk_signal_connect(GTK_OBJECT(((GtkCombo *)filesel->history_combo)->list),"button-press-event",
577 (GtkSignalFunc) gtk_file_selection_history_combo_list_callback,
580 gtk_signal_connect(GTK_OBJECT(((GtkCombo *)filesel->history_combo)->list),"key-press-event",
581 (GtkSignalFunc) gtk_file_selection_history_combo_list_key_handler,
584 /* frame to put the following hbox in */
585 bigframe = gtk_frame_new (NULL);
586 gtk_widget_show (bigframe);
587 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), bigframe, TRUE, TRUE, 0);
589 /* The horizontal box containing the directory and file listboxes */
590 list_hbox = gtk_hbox_new (FALSE, 5);
591 gtk_container_add (GTK_CONTAINER(bigframe), list_hbox);
592 gtk_container_set_border_width (GTK_CONTAINER (list_hbox), 5);
593 gtk_widget_show (list_hbox);
595 /* vbox to put the buttons and directory listing in */
596 vbox = gtk_vbox_new (FALSE, 0);
597 gtk_widget_show (vbox);
598 gtk_box_pack_start (GTK_BOX (list_hbox), vbox, FALSE, FALSE, 0);
600 hbox = gtk_hbox_new (FALSE, 0);
601 gtk_widget_show (hbox);
602 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
604 home_button = gtk_button_new_with_label (_("Home"));
605 gtk_widget_show (home_button);
606 gtk_signal_connect (GTK_OBJECT (home_button), "clicked",
607 (GtkSignalFunc) gtk_file_selection_home_button,
609 gtk_box_pack_start (GTK_BOX (hbox), home_button, TRUE,TRUE, 0);
611 prev_button = gtk_button_new_with_label (_("Prev"));
612 gtk_signal_connect (GTK_OBJECT (prev_button), "clicked",
613 (GtkSignalFunc) gtk_file_selection_prev_button,
615 gtk_widget_show (prev_button);
616 gtk_box_pack_start (GTK_BOX (hbox), prev_button, TRUE,TRUE, 0);
618 up_button = gtk_button_new_with_label (_("Up"));
619 gtk_signal_connect (GTK_OBJECT (up_button), "clicked",
620 (GtkSignalFunc) gtk_file_selection_up_button,
622 gtk_widget_show (up_button);
623 gtk_box_pack_start (GTK_BOX (hbox), up_button, TRUE,TRUE, 0);
625 next_button = gtk_button_new_with_label (_("Next"));
626 gtk_widget_show (next_button);
627 gtk_signal_connect (GTK_OBJECT (next_button), "clicked",
628 (GtkSignalFunc) gtk_file_selection_next_button,
630 gtk_box_pack_start (GTK_BOX (hbox), next_button, TRUE,TRUE, 0);
632 refresh_button = gtk_button_new_with_label (_("Refresh"));
633 gtk_widget_show (refresh_button);
634 gtk_signal_connect (GTK_OBJECT (refresh_button), "clicked",
635 (GtkSignalFunc) gtk_file_selection_refresh_button,
637 gtk_box_pack_start (GTK_BOX (hbox), refresh_button, TRUE, TRUE, 0);
639 /* The directories clist */
640 dir_title[0] = _("Directories");
642 filesel->dir_list = gtk_clist_new_with_titles (1, (gchar**) dir_title);
643 gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
644 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
645 (GtkSignalFunc) gtk_file_selection_dir_button,
647 gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "unselect_row",
648 (GtkSignalFunc) gtk_file_selection_undir_button,
650 gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
652 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
653 gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list);
654 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
655 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
656 gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE,TRUE, 5);
657 gtk_widget_show (filesel->dir_list);
658 gtk_widget_show (scrolled_win);
660 /* vbox area for mask entry and files clist */
661 vbox = gtk_vbox_new (FALSE, 0);
662 gtk_widget_show (vbox);
663 gtk_box_pack_start (GTK_BOX (list_hbox), vbox, TRUE, TRUE, 0);
665 hbox = gtk_hbox_new (FALSE, 5);
666 gtk_widget_show (hbox);
667 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
669 mask_label = gtk_label_new (_("Mask:"));
670 gtk_widget_show (mask_label);
671 gtk_box_pack_start (GTK_BOX (hbox), mask_label, FALSE, FALSE, 0);
673 filesel->mask_entry = gtk_entry_new ();
674 gtk_widget_show (filesel->mask_entry);
675 gtk_signal_connect(GTK_OBJECT(filesel->mask_entry),"activate",
676 (GtkSignalFunc) gtk_file_selection_mask_entry_callback,
678 gtk_box_pack_start (GTK_BOX (hbox),filesel->mask_entry, TRUE, TRUE, 0);
681 /* The files clist */
682 file_title[0] = _("Files");
683 file_title[1] = NULL;
684 filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title);
685 gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
686 gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
687 (GtkSignalFunc) gtk_file_selection_file_button,
689 gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
691 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
692 gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list);
693 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
694 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
695 gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 5);
696 gtk_widget_show (filesel->file_list);
697 gtk_widget_show (scrolled_win);
699 /* action area for packing buttons into. */
700 filesel->action_area = gtk_hbox_new (TRUE, 0);
701 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area,
703 gtk_widget_show (filesel->action_area);
705 /* The OK/Cancel button area */
706 confirm_area = gtk_hbutton_box_new ();
707 gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END);
708 gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5);
709 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0);
710 gtk_widget_show (confirm_area);
713 filesel->ok_button = gtk_button_new_with_label (_("OK"));
714 GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
715 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0);
716 gtk_widget_grab_default (filesel->ok_button);
717 gtk_widget_show (filesel->ok_button);
719 /* The Cancel button */
720 filesel->cancel_button = gtk_button_new_with_label (_("Cancel"));
721 GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
722 gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0);
723 gtk_widget_show (filesel->cancel_button);
725 /* The selection entry widget */
726 entry_vbox = gtk_vbox_new (FALSE, 2);
727 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0);
728 gtk_widget_show (entry_vbox);
730 filesel->selection_text = label = gtk_label_new ("");
731 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
732 gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
733 gtk_widget_show (label);
735 filesel->selection_entry = gtk_entry_new ();
736 gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event",
737 (GtkSignalFunc) gtk_file_selection_key_press, filesel);
738 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event",
739 (GtkSignalFunc) gtk_widget_grab_default,
740 GTK_OBJECT (filesel->ok_button));
741 gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate",
742 (GtkSignalFunc) gtk_button_clicked,
743 GTK_OBJECT (filesel->ok_button));
744 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
745 gtk_widget_show (filesel->selection_entry);
747 if (!cmpl_state_okay (filesel->cmpl_state))
751 sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
753 gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
757 gtk_file_selection_populate (filesel, "", FALSE);
760 gtk_widget_grab_focus (filesel->selection_entry);
764 gtk_file_selection_new (const gchar *title)
766 GtkFileSelection *filesel;
768 filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION);
769 gtk_window_set_title (GTK_WINDOW (filesel), title);
771 return GTK_WIDGET (filesel);
775 gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
777 g_return_if_fail (filesel != NULL);
778 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
780 /* delete, create directory, and rename */
781 if (!filesel->fileop_c_dir)
783 filesel->fileop_c_dir = gtk_button_new_with_label (_("Create Dir"));
784 gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
785 (GtkSignalFunc) gtk_file_selection_create_dir,
787 gtk_box_pack_start (GTK_BOX (filesel->button_area),
788 filesel->fileop_c_dir, TRUE, TRUE, 0);
789 gtk_widget_show (filesel->fileop_c_dir);
792 if (!filesel->fileop_del_file)
794 filesel->fileop_del_file = gtk_button_new_with_label (_("Delete File"));
795 gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
796 (GtkSignalFunc) gtk_file_selection_delete_file,
798 gtk_box_pack_start (GTK_BOX (filesel->button_area),
799 filesel->fileop_del_file, TRUE, TRUE, 0);
800 gtk_widget_show (filesel->fileop_del_file);
803 if (!filesel->fileop_ren_file)
805 filesel->fileop_ren_file = gtk_button_new_with_label (_("Rename File"));
806 gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
807 (GtkSignalFunc) gtk_file_selection_rename_file,
809 gtk_box_pack_start (GTK_BOX (filesel->button_area),
810 filesel->fileop_ren_file, TRUE, TRUE, 0);
811 gtk_widget_show (filesel->fileop_ren_file);
814 gtk_widget_queue_resize(GTK_WIDGET(filesel));
818 gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
820 g_return_if_fail (filesel != NULL);
821 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
823 if (filesel->fileop_ren_file)
825 gtk_widget_destroy (filesel->fileop_ren_file);
826 filesel->fileop_ren_file = NULL;
829 if (filesel->fileop_del_file)
831 gtk_widget_destroy (filesel->fileop_del_file);
832 filesel->fileop_del_file = NULL;
835 if (filesel->fileop_c_dir)
837 gtk_widget_destroy (filesel->fileop_c_dir);
838 filesel->fileop_c_dir = NULL;
845 gtk_file_selection_set_filename (GtkFileSelection *filesel,
846 const gchar *filename)
848 char buf[MAXPATHLEN];
849 const char *name, *last_slash;
851 g_return_if_fail (filesel != NULL);
852 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
853 g_return_if_fail (filename != NULL);
855 last_slash = strrchr (filename, '/');
864 gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1);
866 strncpy (buf, filename, len);
869 name = last_slash + 1;
872 gtk_file_selection_populate (filesel, buf, FALSE);
874 if (filesel->selection_entry)
875 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
879 gtk_file_selection_get_filename (GtkFileSelection *filesel)
881 static char nothing[2] = "";
885 g_return_val_if_fail (filesel != NULL, nothing);
886 g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
888 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
891 filename = cmpl_completion_fullname (text, filesel->cmpl_state);
899 gtk_file_selection_complete (GtkFileSelection *filesel,
900 const gchar *pattern)
905 g_return_if_fail (filesel != NULL);
906 g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
907 g_return_if_fail (pattern != NULL);
909 if (filesel->selection_entry)
910 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern);
912 if(strchr(pattern,'*') || strchr(pattern,'?'))
914 for(x=strlen(pattern);x>=0;x--)
916 if(pattern[x]=='/') break;
918 gtk_entry_set_text(GTK_ENTRY(filesel->mask_entry),g_strdup(pattern+x+1));
920 if(filesel->mask) g_free(filesel->mask);
922 filesel->mask=g_strdup(pattern+x+1);
923 new_pattern=g_strdup(pattern);
925 gtk_file_selection_populate (filesel, (gchar*) new_pattern, TRUE);
930 gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE);
935 gtk_file_selection_destroy (GtkObject *object)
937 GtkFileSelection *filesel;
940 g_return_if_fail (object != NULL);
941 g_return_if_fail (GTK_IS_FILE_SELECTION (object));
943 filesel = GTK_FILE_SELECTION (object);
945 if (filesel->fileop_dialog)
946 gtk_widget_destroy (filesel->fileop_dialog);
948 if (filesel->next_history)
950 list = filesel->next_history;
957 g_list_free (filesel->next_history);
958 filesel->next_history = NULL;
960 if (filesel->prev_history)
962 list = filesel->prev_history;
969 g_list_free (filesel->prev_history);
970 filesel->prev_history = NULL;
974 g_free (filesel->mask);
975 filesel->mask = NULL;
978 cmpl_free_state (filesel->cmpl_state);
979 filesel->cmpl_state = NULL;
981 if (GTK_OBJECT_CLASS (parent_class)->destroy)
982 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
985 /* Begin file operations callbacks */
988 gtk_file_selection_fileop_error (GtkFileSelection *fs, gchar *error_message)
995 g_return_if_fail (error_message != NULL);
998 dialog = gtk_dialog_new ();
1000 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1001 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1004 gtk_window_set_title (GTK_WINDOW (dialog), _("Error"));
1005 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1007 /* If file dialog is grabbed, make this dialog modal too */
1008 /* When error dialog is closed, file dialog will be grabbed again */
1009 if (GTK_WINDOW(fs)->modal)
1010 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1012 vbox = gtk_vbox_new(FALSE, 0);
1013 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1014 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1016 gtk_widget_show(vbox);
1018 label = gtk_label_new(error_message);
1019 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1020 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1021 gtk_widget_show(label);
1023 /* yes, we free it */
1024 g_free (error_message);
1027 button = gtk_button_new_with_label (_("Close"));
1028 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1029 (GtkSignalFunc) gtk_widget_destroy,
1031 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1032 button, TRUE, TRUE, 0);
1033 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1034 gtk_widget_grab_default(button);
1035 gtk_widget_show (button);
1037 gtk_widget_show (dialog);
1041 gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data)
1043 GtkFileSelection *fs = data;
1045 g_return_if_fail (fs != NULL);
1046 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1048 fs->fileop_dialog = NULL;
1053 gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data)
1055 GtkFileSelection *fs = data;
1060 CompletionState *cmpl_state;
1062 g_return_if_fail (fs != NULL);
1063 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1065 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1066 cmpl_state = (CompletionState*) fs->cmpl_state;
1067 path = cmpl_reference_position (cmpl_state);
1069 full_path = g_strconcat (path, "/", dirname, NULL);
1070 if ( (mkdir (full_path, 0755) < 0) )
1072 buf = g_strconcat ("Error creating directory \"", dirname, "\": ",
1073 g_strerror(errno), NULL);
1074 gtk_file_selection_fileop_error (fs, buf);
1078 gtk_widget_destroy (fs->fileop_dialog);
1079 gtk_file_selection_populate (fs, "", FALSE);
1083 gtk_file_selection_create_dir (GtkWidget *widget, gpointer data)
1085 GtkFileSelection *fs = data;
1091 g_return_if_fail (fs != NULL);
1092 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1094 if (fs->fileop_dialog)
1098 fs->fileop_dialog = dialog = gtk_dialog_new ();
1099 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1100 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1102 gtk_window_set_title (GTK_WINDOW (dialog), _("Create Directory"));
1103 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1105 /* If file dialog is grabbed, grab option dialog */
1106 /* When option dialog is closed, file dialog will be grabbed again */
1107 if (GTK_WINDOW(fs)->modal)
1108 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1110 vbox = gtk_vbox_new(FALSE, 0);
1111 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1112 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1114 gtk_widget_show(vbox);
1116 label = gtk_label_new(_("Directory name:"));
1117 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1118 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1119 gtk_widget_show(label);
1121 /* The directory entry widget */
1122 fs->fileop_entry = gtk_entry_new ();
1123 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
1125 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
1126 gtk_widget_show (fs->fileop_entry);
1129 button = gtk_button_new_with_label (_("Create"));
1130 gtk_signal_connect (GTK_OBJECT (button), "clicked",
1131 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
1133 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1134 button, TRUE, TRUE, 0);
1135 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1136 gtk_widget_show(button);
1138 button = gtk_button_new_with_label (_("Cancel"));
1139 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1140 (GtkSignalFunc) gtk_widget_destroy,
1142 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1143 button, TRUE, TRUE, 0);
1144 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1145 gtk_widget_grab_default(button);
1146 gtk_widget_show (button);
1148 gtk_widget_show (dialog);
1152 gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data)
1154 GtkFileSelection *fs = data;
1155 CompletionState *cmpl_state;
1160 g_return_if_fail (fs != NULL);
1161 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1163 cmpl_state = (CompletionState*) fs->cmpl_state;
1164 path = cmpl_reference_position (cmpl_state);
1166 full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
1167 if ( (unlink (full_path) < 0) )
1169 buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ",
1170 g_strerror(errno), NULL);
1171 gtk_file_selection_fileop_error (fs, buf);
1175 gtk_widget_destroy (fs->fileop_dialog);
1176 gtk_file_selection_populate (fs, "", FALSE);
1180 gtk_file_selection_delete_file (GtkWidget *widget, gpointer data)
1182 GtkFileSelection *fs = data;
1190 g_return_if_fail (fs != NULL);
1191 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1193 if (fs->fileop_dialog)
1196 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1197 if (strlen(filename) < 1)
1200 fs->fileop_file = filename;
1203 fs->fileop_dialog = dialog = gtk_dialog_new ();
1204 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1205 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1207 gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File"));
1208 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1210 /* If file dialog is grabbed, grab option dialog */
1211 /* When option dialog is closed, file dialog will be grabbed again */
1212 if (GTK_WINDOW(fs)->modal)
1213 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1215 vbox = gtk_vbox_new(FALSE, 0);
1216 gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1217 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1219 gtk_widget_show(vbox);
1221 buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
1222 label = gtk_label_new(buf);
1223 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1224 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1225 gtk_widget_show(label);
1229 button = gtk_button_new_with_label (_("Delete"));
1230 gtk_signal_connect (GTK_OBJECT (button), "clicked",
1231 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
1233 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1234 button, TRUE, TRUE, 0);
1235 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1236 gtk_widget_show(button);
1238 button = gtk_button_new_with_label (_("Cancel"));
1239 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1240 (GtkSignalFunc) gtk_widget_destroy,
1242 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1243 button, TRUE, TRUE, 0);
1244 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1245 gtk_widget_grab_default(button);
1246 gtk_widget_show (button);
1248 gtk_widget_show (dialog);
1253 gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data)
1255 GtkFileSelection *fs = data;
1259 gchar *new_filename;
1260 gchar *old_filename;
1261 CompletionState *cmpl_state;
1263 g_return_if_fail (fs != NULL);
1264 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1266 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1267 cmpl_state = (CompletionState*) fs->cmpl_state;
1268 path = cmpl_reference_position (cmpl_state);
1270 new_filename = g_strconcat (path, "/", file, NULL);
1271 old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
1273 if ( (rename (old_filename, new_filename)) < 0)
1275 buf = g_strconcat ("Error renaming file \"", file, "\": ",
1276 g_strerror(errno), NULL);
1277 gtk_file_selection_fileop_error (fs, buf);
1279 g_free (new_filename);
1280 g_free (old_filename);
1282 gtk_widget_destroy (fs->fileop_dialog);
1283 gtk_file_selection_populate (fs, "", FALSE);
1287 gtk_file_selection_rename_file (GtkWidget *widget, gpointer data)
1289 GtkFileSelection *fs = data;
1296 g_return_if_fail (fs != NULL);
1297 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1299 if (fs->fileop_dialog)
1302 fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1303 if (strlen(fs->fileop_file) < 1)
1307 fs->fileop_dialog = dialog = gtk_dialog_new ();
1308 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1309 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1311 gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File"));
1312 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1314 /* If file dialog is grabbed, grab option dialog */
1315 /* When option dialog closed, file dialog will be grabbed again */
1316 if (GTK_WINDOW(fs)->modal)
1317 gtk_window_set_modal (GTK_WINDOW(dialog), TRUE);
1319 vbox = gtk_vbox_new(FALSE, 0);
1320 gtk_container_set_border_width (GTK_CONTAINER(vbox), 8);
1321 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox,
1323 gtk_widget_show(vbox);
1325 buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
1326 label = gtk_label_new(buf);
1327 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1328 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
1329 gtk_widget_show(label);
1332 /* New filename entry */
1333 fs->fileop_entry = gtk_entry_new ();
1334 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
1336 GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT);
1337 gtk_widget_show (fs->fileop_entry);
1339 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
1340 gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
1341 0, strlen (fs->fileop_file));
1344 button = gtk_button_new_with_label (_("Rename"));
1345 gtk_signal_connect (GTK_OBJECT (button), "clicked",
1346 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
1348 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1349 button, TRUE, TRUE, 0);
1350 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1351 gtk_widget_show(button);
1353 button = gtk_button_new_with_label (_("Cancel"));
1354 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1355 (GtkSignalFunc) gtk_widget_destroy,
1357 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1358 button, TRUE, TRUE, 0);
1359 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1360 gtk_widget_grab_default(button);
1361 gtk_widget_show (button);
1363 gtk_widget_show (dialog);
1368 gtk_file_selection_key_press (GtkWidget *widget,
1372 GtkFileSelection *fs;
1375 g_return_val_if_fail (widget != NULL, FALSE);
1376 g_return_val_if_fail (event != NULL, FALSE);
1378 fs = GTK_FILE_SELECTION (user_data);
1380 if (event->keyval == GDK_Tab)
1382 text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1384 text = g_strdup (text);
1386 gtk_file_selection_populate (fs, text, TRUE);
1390 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1394 if (fs->saved_entry)
1396 gtk_clist_unselect_all ((GtkCList *) (fs->dir_list));
1397 gtk_entry_set_text(GTK_ENTRY(fs->selection_entry),fs->saved_entry);
1398 g_free (fs->saved_entry);
1399 fs->saved_entry = NULL;
1407 gtk_file_selection_home_button (GtkWidget *widget, gpointer data){
1410 GtkFileSelection *fs=data;
1412 list = fs->next_history;
1415 g_free (list->data);
1418 g_list_free (fs->next_history);
1419 fs->next_history = NULL;
1421 gtk_file_selection_populate (fs,"~/",FALSE);
1425 gtk_file_selection_up_button (GtkWidget *widget, gpointer data){
1426 GtkFileSelection *fs = data;
1429 g_return_if_fail (fs != NULL);
1430 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1432 list = fs->next_history;
1435 g_free (list->data);
1438 g_list_free (fs->next_history);
1439 fs->next_history = NULL;
1441 gtk_file_selection_populate (fs, "../", FALSE); /*change directories. */
1446 gtk_file_selection_prev_button (GtkWidget *widget, gpointer data){
1447 GtkFileSelection *fs = data;
1452 g_return_if_fail (fs != NULL);
1453 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1455 list = fs->prev_history;
1457 if (list && g_list_length(list) > 1)
1459 first = list; /* get first element */
1460 list = list->next; /* pop off current directory */
1462 list->prev = NULL; /* make this the new head. */
1464 fs->prev_history = list; /* update prev_history list */
1465 fs->next_history = g_list_prepend(fs->next_history,first->data); /* put it on next_history */
1467 first->next = NULL; /* orphan the old first node */
1468 g_list_free (first); /* free the node (data is now in use by next_history) */
1472 path = g_malloc(strlen(list->data)+4); /* plenty of space */
1473 strcpy(path,list->data); /* get the 2nd path in the history */
1474 strcat(path,"/"); /* append a '/' */
1475 gtk_file_selection_populate (fs, path, FALSE); /* change directories. */
1481 gtk_file_selection_next_button (GtkWidget *widget, gpointer data){
1482 GtkFileSelection *fs = data;
1487 g_return_if_fail (fs != NULL);
1488 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1490 list = fs->next_history;
1492 if (list && g_list_length(list) > 0)
1494 first = list; /*get first element*/
1495 list = list->next; /*pop off current directory*/
1500 fs->next_history = list; /*update prev_history list*/
1502 path = g_malloc(strlen(first->data)+4); /*plenty of space*/
1503 strcpy(path,first->data);
1504 strcat(path,"/"); /*append a / */
1505 gtk_file_selection_populate (fs, path, FALSE); /*change directories.*/
1508 first->next = NULL; /* orphan the old first node */
1509 g_list_free (first); /* free the node (data is now in use by next_history) */
1515 gtk_file_selection_refresh_button (GtkWidget *widget, gpointer data){
1516 GtkFileSelection *fs = data;
1518 g_return_if_fail (fs != NULL);
1519 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1521 gtk_file_selection_populate (fs,"",FALSE);
1525 gtk_file_selection_mask_entry_callback (GtkWidget *widget, gpointer data){
1526 GtkFileSelection *fs = data;
1531 fs->mask = g_strdup(gtk_entry_get_text (GTK_ENTRY(fs->mask_entry)));
1533 if (strlen(fs->mask) == 0)
1539 gtk_file_selection_refresh_button (widget,data);
1542 static gboolean gtk_file_selection_history_combo_list_key_handler(GtkWidget *widget,
1547 g_print("Key pressed! \n");
1553 static gboolean gtk_file_selection_history_combo_list_callback (GtkWidget *thelist,
1554 GdkEventButton *event,
1558 GtkFileSelection *fs = user_data;
1562 list = fs->next_history;
1565 g_free (list->data);
1568 g_list_free (fs->next_history);
1569 fs->next_history = NULL;
1571 path = g_malloc(strlen(gtk_entry_get_text(GTK_ENTRY (((GtkCombo *)fs->history_combo)->entry)))+4);
1572 strcpy (path,gtk_entry_get_text(GTK_ENTRY( ((GtkCombo *)fs->history_combo)->entry)));
1575 gtk_file_selection_populate (fs,path,TRUE);
1583 gtk_file_selection_history_combo_callback (GtkWidget *widget, GdkEventKey *event, gpointer data)
1585 GtkEntry *entry=(GtkEntry *)widget;
1586 GtkFileSelection *fs=data;
1590 g_return_val_if_fail (fs != NULL,FALSE);
1591 g_return_val_if_fail (GTK_IS_FILE_SELECTION (fs),FALSE);
1594 if (event->keyval == GDK_Return)
1596 list = fs->next_history;
1599 g_free (list->data);
1602 g_list_free (fs->next_history);
1603 fs->next_history = NULL;
1605 path = g_malloc(strlen(gtk_entry_get_text(entry))+4);
1606 strcpy (path,gtk_entry_get_text(entry));
1608 gtk_file_selection_populate (fs,path,TRUE);
1610 gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1621 gtk_file_selection_update_history_menu (GtkFileSelection *fs,
1622 gchar *current_directory)
1626 g_return_if_fail (fs != NULL);
1627 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1628 g_return_if_fail (current_directory != NULL);
1630 current_dir = g_strdup (current_directory);
1632 if(fs->prev_history)
1634 if (strcmp((fs->prev_history)->data,current_dir))
1635 { /*if this item isn't on the top of the list */
1636 fs->prev_history = g_list_prepend(fs->prev_history,g_strdup(current_dir));
1639 fs->prev_history = g_list_prepend(fs->prev_history,g_strdup(current_dir));
1642 gtk_combo_set_popdown_strings (GTK_COMBO (fs->history_combo),fs->prev_history);
1644 g_free (current_dir);
1648 gtk_file_selection_file_button (GtkWidget *widget,
1651 GdkEventButton *bevent,
1654 GtkFileSelection *fs = NULL;
1655 gchar *filename, *temp = NULL;
1657 g_return_if_fail (GTK_IS_CLIST (widget));
1660 g_return_if_fail (fs != NULL);
1661 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1663 gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1664 filename = g_strdup (temp);
1669 switch (bevent->type)
1671 case GDK_2BUTTON_PRESS:
1672 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1676 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1680 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1687 gtk_file_selection_dir_button (GtkWidget *widget,
1690 GdkEventButton *bevent,
1694 GtkFileSelection *fs = NULL;
1695 gchar *filename, *temp = NULL;
1697 g_return_if_fail (GTK_IS_CLIST (widget));
1699 fs = GTK_FILE_SELECTION (user_data);
1700 g_return_if_fail (fs != NULL);
1701 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1703 gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1704 filename = g_strdup (temp);
1709 switch (bevent->type)
1711 case GDK_2BUTTON_PRESS:
1712 list = fs->next_history;
1715 g_free (list->data);
1718 g_list_free (fs->next_history);
1719 fs->next_history = NULL;
1721 gtk_file_selection_populate (fs, filename, FALSE);
1722 gtk_entry_set_text(GTK_ENTRY(fs->selection_entry),fs->saved_entry);
1723 g_free (fs->saved_entry);
1724 fs->saved_entry = NULL;
1728 /* here we need to add the "filename" to the beginning of what's already
1729 in the entry. Save what's in the entry, then restore it on the double click
1731 if (fs->saved_entry) g_free (fs->saved_entry);
1732 fs->saved_entry=g_strdup(gtk_entry_get_text(GTK_ENTRY (fs->selection_entry)));
1734 temp=g_strconcat(filename,fs->saved_entry,NULL);
1735 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), temp);
1741 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1748 gtk_file_selection_undir_button (GtkWidget *widget,
1751 GdkEventButton *bevent,
1754 GtkFileSelection *fs = NULL;
1755 gchar *filename, *temp = NULL;
1757 g_return_if_fail (GTK_IS_CLIST (widget));
1759 fs = GTK_FILE_SELECTION (user_data);
1760 g_return_if_fail (fs != NULL);
1761 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1763 gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1764 filename = g_strdup (temp);
1769 switch (bevent->type)
1772 /* here we need to add the "filename" to the beginning of what's already
1773 in the entry. Save what's in the entry, then restore it on the double click
1775 if (fs->saved_entry)
1777 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),fs->saved_entry);
1778 g_free (fs->saved_entry);
1779 fs->saved_entry = NULL;
1784 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); //?????
1791 gtk_file_selection_populate (GtkFileSelection *fs,
1795 CompletionState *cmpl_state;
1796 PossibleCompletion* poss;
1799 gchar* rem_path = rel_path;
1802 gint did_recurse = FALSE;
1803 gint possible_count = 0;
1804 gint selection_index = -1;
1805 gint file_list_width;
1806 gint dir_list_width;
1808 g_return_if_fail (fs != NULL);
1809 g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1811 cmpl_state = (CompletionState*) fs->cmpl_state;
1812 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
1814 if (!cmpl_state_okay (cmpl_state))
1816 /* Something went wrong. */
1817 gtk_file_selection_abort (fs);
1821 g_assert (cmpl_state->reference_dir);
1823 gtk_clist_freeze (GTK_CLIST (fs->dir_list));
1824 gtk_clist_clear (GTK_CLIST (fs->dir_list));
1825 gtk_clist_freeze (GTK_CLIST (fs->file_list));
1826 gtk_clist_clear (GTK_CLIST (fs->file_list));
1828 /* Set the dir_list to include ./ and ../ */
1831 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1834 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1836 /*reset the max widths of the lists*/
1837 dir_list_width = gdk_string_width(fs->dir_list->style->font,"../");
1838 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width);
1839 file_list_width = 1;
1840 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width);
1844 if (cmpl_is_a_completion (poss))
1846 possible_count += 1;
1848 filename = cmpl_this_completion (poss);
1852 if (cmpl_is_directory (poss))
1854 if (strcmp (filename, "./") != 0 &&
1855 strcmp (filename, "../") != 0)
1857 int width = gdk_string_width(fs->dir_list->style->font,
1859 row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
1860 if(width > dir_list_width)
1862 dir_list_width = width;
1863 gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,
1872 if (gtk_file_selection_match_mask(filename,fs->mask))
1874 int width = gdk_string_width(fs->file_list->style->font,
1876 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1877 if(width > file_list_width)
1879 file_list_width = width;
1880 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1887 int width = gdk_string_width(fs->file_list->style->font,
1889 row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
1890 if(width > file_list_width)
1892 file_list_width = width;
1893 gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,
1900 poss = cmpl_next_completion (cmpl_state);
1903 gtk_clist_thaw (GTK_CLIST (fs->dir_list));
1904 gtk_clist_thaw (GTK_CLIST (fs->file_list));
1906 /* File lists are set. */
1908 g_assert (cmpl_state->reference_dir);
1913 /* User is trying to complete filenames, so advance the user's input
1914 * string to the updated_text, which is the common leading substring
1915 * of all possible completions, and if its a directory attempt
1916 * attempt completions in it. */
1918 if (cmpl_updated_text (cmpl_state)[0])
1921 if (cmpl_updated_dir (cmpl_state))
1923 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
1927 gtk_file_selection_populate (fs, dir_name, TRUE);
1933 if (fs->selection_entry)
1934 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1935 cmpl_updated_text (cmpl_state));
1940 selection_index = cmpl_last_valid_char (cmpl_state) -
1941 (strlen (rel_path) - strlen (rem_path));
1942 if (fs->selection_entry)
1943 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
1948 if (fs->selection_entry)
1949 /* Here we need to take the old filename and keep it!*/
1950 /*gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");*/
1956 if (fs->selection_entry)
1957 gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index);
1959 if (fs->selection_entry)
1961 sel_text = g_strconcat (_("Selection: "),
1962 cmpl_reference_position (cmpl_state),
1965 gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text);
1969 gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
1975 gtk_file_selection_abort (GtkFileSelection *fs)
1979 sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno));
1981 /* BEEP gdk_beep(); */
1983 if (fs->selection_entry)
1984 gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
1987 /**********************************************************************/
1988 /* External Interface */
1989 /**********************************************************************/
1991 /* The four completion state selectors
1994 cmpl_updated_text (CompletionState* cmpl_state)
1996 return cmpl_state->updated_text;
2000 cmpl_updated_dir (CompletionState* cmpl_state)
2002 return cmpl_state->re_complete;
2006 cmpl_reference_position (CompletionState* cmpl_state)
2008 return cmpl_state->reference_dir->fullname;
2012 cmpl_last_valid_char (CompletionState* cmpl_state)
2014 return cmpl_state->last_valid_char;
2018 cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state)
2020 static char nothing[2] = "";
2022 if (!cmpl_state_okay (cmpl_state))
2026 else if (text[0] == '/')
2028 strcpy (cmpl_state->updated_text, text);
2030 else if (text[0] == '~')
2035 dir = open_user_dir (text, cmpl_state);
2039 /* spencer says just return ~something, so
2040 * for now just do it. */
2041 strcpy (cmpl_state->updated_text, text);
2046 strcpy (cmpl_state->updated_text, dir->fullname);
2048 slash = strchr (text, '/');
2051 strcat (cmpl_state->updated_text, slash);
2056 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
2057 if (strcmp (cmpl_state->reference_dir->fullname, "/") != 0)
2058 strcat (cmpl_state->updated_text, "/");
2059 strcat (cmpl_state->updated_text, text);
2062 return cmpl_state->updated_text;
2065 /* The three completion selectors
2068 cmpl_this_completion (PossibleCompletion* pc)
2074 cmpl_is_directory (PossibleCompletion* pc)
2076 return pc->is_directory;
2080 cmpl_is_a_completion (PossibleCompletion* pc)
2082 return pc->is_a_completion;
2085 /**********************************************************************/
2086 /* Construction, deletion */
2087 /**********************************************************************/
2089 static CompletionState*
2090 cmpl_init_state (void)
2092 gchar getcwd_buf[2*MAXPATHLEN];
2093 CompletionState *new_state;
2095 new_state = g_new (CompletionState, 1);
2097 /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
2098 * and, if that wasn't bad enough, hangs in doing so.
2100 #if defined(sun) && !defined(__SVR4)
2101 if (!getwd (getcwd_buf))
2103 if (!getcwd (getcwd_buf, MAXPATHLEN))
2106 /* Oh joy, we can't get the current directory. Um..., we should have
2107 * a root directory, right? Right? (Probably not portable to non-Unix)
2109 strcpy (getcwd_buf, "/");
2114 new_state->reference_dir = NULL;
2115 new_state->completion_dir = NULL;
2116 new_state->active_completion_dir = NULL;
2117 new_state->directory_storage = NULL;
2118 new_state->directory_sent_storage = NULL;
2119 new_state->last_valid_char = 0;
2120 new_state->updated_text = g_new (gchar, MAXPATHLEN);
2121 new_state->updated_text_alloc = MAXPATHLEN;
2122 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
2123 new_state->the_completion.text_alloc = MAXPATHLEN;
2124 new_state->user_dir_name_buffer = NULL;
2125 new_state->user_directories = NULL;
2127 new_state->reference_dir = open_dir (getcwd_buf, new_state);
2129 if (!new_state->reference_dir)
2131 /* Directories changing from underneath us, grumble */
2132 strcpy (getcwd_buf, "/");
2140 cmpl_free_dir_list(GList* dp0)
2145 free_dir (dp->data);
2153 cmpl_free_dir_sent_list(GList* dp0)
2158 free_dir_sent (dp->data);
2166 cmpl_free_state (CompletionState* cmpl_state)
2168 cmpl_free_dir_list (cmpl_state->directory_storage);
2169 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
2171 if (cmpl_state->user_dir_name_buffer)
2172 g_free (cmpl_state->user_dir_name_buffer);
2173 if (cmpl_state->user_directories)
2174 g_free (cmpl_state->user_directories);
2175 if (cmpl_state->the_completion.text)
2176 g_free (cmpl_state->the_completion.text);
2177 if (cmpl_state->updated_text)
2178 g_free (cmpl_state->updated_text);
2180 g_free (cmpl_state);
2184 free_dir(CompletionDir* dir)
2186 g_free(dir->fullname);
2191 free_dir_sent(CompletionDirSent* sent)
2193 g_free(sent->name_buffer);
2194 g_free(sent->entries);
2199 prune_memory_usage(CompletionState *cmpl_state)
2201 GList* cdsl = cmpl_state->directory_sent_storage;
2202 GList* cdl = cmpl_state->directory_storage;
2206 for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
2210 cmpl_free_dir_sent_list(cdsl->next);
2214 cmpl_state->directory_storage = NULL;
2216 if (cdl->data == cmpl_state->reference_dir)
2217 cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data);
2219 free_dir (cdl->data);
2226 /**********************************************************************/
2227 /* The main entrances. */
2228 /**********************************************************************/
2230 static PossibleCompletion*
2231 cmpl_completion_matches (gchar* text_to_complete,
2232 gchar** remaining_text,
2233 CompletionState* cmpl_state)
2236 PossibleCompletion *poss;
2238 prune_memory_usage(cmpl_state);
2240 g_assert (text_to_complete != NULL);
2242 cmpl_state->user_completion_index = -1;
2243 cmpl_state->last_completion_text = text_to_complete;
2244 cmpl_state->the_completion.text[0] = 0;
2245 cmpl_state->last_valid_char = 0;
2246 cmpl_state->updated_text_len = -1;
2247 cmpl_state->updated_text[0] = 0;
2248 cmpl_state->re_complete = FALSE;
2250 first_slash = strchr (text_to_complete, '/');
2252 if (text_to_complete[0] == '~' && !first_slash)
2254 /* Text starts with ~ and there is no slash, show all the
2255 * home directory completions.
2257 poss = attempt_homedir_completion (text_to_complete, cmpl_state);
2259 update_cmpl(poss, cmpl_state);
2264 cmpl_state->reference_dir =
2265 open_ref_dir (text_to_complete, remaining_text, cmpl_state);
2267 if(!cmpl_state->reference_dir)
2270 cmpl_state->completion_dir =
2271 find_completion_dir (*remaining_text, remaining_text, cmpl_state);
2273 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2275 if(!cmpl_state->completion_dir)
2278 cmpl_state->completion_dir->cmpl_index = -1;
2279 cmpl_state->completion_dir->cmpl_parent = NULL;
2280 cmpl_state->completion_dir->cmpl_text = *remaining_text;
2282 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2284 cmpl_state->reference_dir = cmpl_state->completion_dir;
2286 poss = attempt_file_completion(cmpl_state);
2288 update_cmpl(poss, cmpl_state);
2293 static PossibleCompletion*
2294 cmpl_next_completion (CompletionState* cmpl_state)
2296 PossibleCompletion* poss = NULL;
2298 cmpl_state->the_completion.text[0] = 0;
2300 if(cmpl_state->user_completion_index >= 0)
2301 poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state);
2303 poss = attempt_file_completion(cmpl_state);
2305 update_cmpl(poss, cmpl_state);
2310 /**********************************************************************/
2311 /* Directory Operations */
2312 /**********************************************************************/
2314 /* Open the directory where completion will begin from, if possible. */
2315 static CompletionDir*
2316 open_ref_dir(gchar* text_to_complete,
2317 gchar** remaining_text,
2318 CompletionState* cmpl_state)
2321 CompletionDir *new_dir;
2323 first_slash = strchr(text_to_complete, '/');
2325 if (text_to_complete[0] == '~')
2327 new_dir = open_user_dir(text_to_complete, cmpl_state);
2332 *remaining_text = first_slash + 1;
2334 *remaining_text = text_to_complete + strlen(text_to_complete);
2341 else if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
2343 gchar *tmp = g_strdup(text_to_complete);
2347 while (*p && *p != '*' && *p != '?')
2351 p = strrchr(tmp, '/');
2359 new_dir = open_dir(tmp, cmpl_state);
2362 *remaining_text = text_to_complete +
2363 ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp));
2367 /* If no possible candidates, use the cwd */
2368 gchar *curdir = g_get_current_dir ();
2370 new_dir = open_dir(curdir, cmpl_state);
2373 *remaining_text = text_to_complete;
2382 *remaining_text = text_to_complete;
2384 new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state);
2389 new_dir->cmpl_index = -1;
2390 new_dir->cmpl_parent = NULL;
2396 /* open a directory by user name */
2397 static CompletionDir*
2398 open_user_dir(gchar* text_to_complete,
2399 CompletionState *cmpl_state)
2404 g_assert(text_to_complete && text_to_complete[0] == '~');
2406 first_slash = strchr(text_to_complete, '/');
2409 cmp_len = first_slash - text_to_complete - 1;
2411 cmp_len = strlen(text_to_complete + 1);
2416 gchar *homedir = g_get_home_dir ();
2419 return open_dir(homedir, cmpl_state);
2426 char* copy = g_new(char, cmp_len + 1);
2428 strncpy(copy, text_to_complete + 1, cmp_len);
2430 pwd = getpwnam(copy);
2438 return open_dir(pwd->pw_dir, cmpl_state);
2442 /* open a directory relative the the current relative directory */
2443 static CompletionDir*
2444 open_relative_dir(gchar* dir_name,
2446 CompletionState *cmpl_state)
2448 gchar path_buf[2*MAXPATHLEN];
2450 if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN)
2452 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2456 strcpy(path_buf, dir->fullname);
2458 if(dir->fullname_len > 1)
2460 path_buf[dir->fullname_len] = '/';
2461 strcpy(path_buf + dir->fullname_len + 1, dir_name);
2465 strcpy(path_buf + dir->fullname_len, dir_name);
2468 return open_dir(path_buf, cmpl_state);
2471 /* after the cache lookup fails, really open a new directory */
2472 static CompletionDirSent*
2473 open_new_dir(gchar* dir_name, struct stat* sbuf, gboolean stat_subdirs)
2475 CompletionDirSent* sent;
2478 struct dirent *dirent_ptr;
2479 gint buffer_size = 0;
2480 gint entry_count = 0;
2482 struct stat ent_sbuf;
2483 char path_buf[MAXPATHLEN*2];
2486 sent = g_new(CompletionDirSent, 1);
2487 sent->mtime = sbuf->st_mtime;
2488 sent->inode = sbuf->st_ino;
2489 sent->device = sbuf->st_dev;
2491 path_buf_len = strlen(dir_name);
2493 if (path_buf_len > MAXPATHLEN)
2495 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2499 strcpy(path_buf, dir_name);
2501 directory = opendir(dir_name);
2509 while((dirent_ptr = readdir(directory)) != NULL)
2511 int entry_len = strlen(dirent_ptr->d_name);
2512 buffer_size += entry_len + 1;
2515 if(path_buf_len + entry_len + 2 >= MAXPATHLEN)
2517 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2518 closedir(directory);
2523 sent->name_buffer = g_new(gchar, buffer_size);
2524 sent->entries = g_new(CompletionDirEntry, entry_count);
2525 sent->entry_count = entry_count;
2527 buffer_ptr = sent->name_buffer;
2529 rewinddir(directory);
2531 for(i = 0; i < entry_count; i += 1)
2533 dirent_ptr = readdir(directory);
2538 closedir(directory);
2542 strcpy(buffer_ptr, dirent_ptr->d_name);
2543 sent->entries[i].entry_name = buffer_ptr;
2544 buffer_ptr += strlen(dirent_ptr->d_name);
2548 path_buf[path_buf_len] = '/';
2549 strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name);
2553 if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
2554 sent->entries[i].is_dir = 1;
2556 /* stat may fail, and we don't mind, since it could be a
2557 * dangling symlink. */
2558 sent->entries[i].is_dir = 0;
2561 sent->entries[i].is_dir = 1;
2564 qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir);
2566 closedir(directory);
2572 check_dir(gchar *dir_name, struct stat *result, gboolean *stat_subdirs)
2574 /* A list of directories that we know only contain other directories.
2575 * Trying to stat every file in these directories would be very
2582 struct stat statbuf;
2583 } no_stat_dirs[] = {
2584 { "/afs", FALSE, { 0 } },
2585 { "/net", FALSE, { 0 } }
2588 static const gint n_no_stat_dirs = sizeof(no_stat_dirs) / sizeof(no_stat_dirs[0]);
2589 static gboolean initialized = FALSE;
2596 for (i = 0; i < n_no_stat_dirs; i++)
2598 if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0)
2599 no_stat_dirs[i].present = TRUE;
2603 if(stat(dir_name, result) < 0)
2609 *stat_subdirs = TRUE;
2610 for (i=0; i<n_no_stat_dirs; i++)
2612 if (no_stat_dirs[i].present &&
2613 (no_stat_dirs[i].statbuf.st_dev == result->st_dev) &&
2614 (no_stat_dirs[i].statbuf.st_ino == result->st_ino))
2616 *stat_subdirs = FALSE;
2624 /* open a directory by absolute pathname */
2625 static CompletionDir*
2626 open_dir(gchar* dir_name, CompletionState* cmpl_state)
2629 gboolean stat_subdirs;
2630 CompletionDirSent *sent;
2633 if (!check_dir (dir_name, &sbuf, &stat_subdirs))
2636 cdsl = cmpl_state->directory_sent_storage;
2642 if(sent->inode == sbuf.st_ino &&
2643 sent->mtime == sbuf.st_mtime &&
2644 sent->device == sbuf.st_dev)
2645 return attach_dir(sent, dir_name, cmpl_state);
2650 sent = open_new_dir(dir_name, &sbuf, stat_subdirs);
2653 cmpl_state->directory_sent_storage =
2654 g_list_prepend(cmpl_state->directory_sent_storage, sent);
2656 return attach_dir(sent, dir_name, cmpl_state);
2662 static CompletionDir*
2663 attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state)
2665 CompletionDir* new_dir;
2667 new_dir = g_new(CompletionDir, 1);
2669 cmpl_state->directory_storage =
2670 g_list_prepend(cmpl_state->directory_storage, new_dir);
2672 new_dir->sent = sent;
2673 new_dir->fullname = g_strdup(dir_name);
2674 new_dir->fullname_len = strlen(dir_name);
2680 correct_dir_fullname(CompletionDir* cmpl_dir)
2682 gint length = strlen(cmpl_dir->fullname);
2685 if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0)
2689 strcpy(cmpl_dir->fullname, "/");
2690 cmpl_dir->fullname_len = 1;
2693 cmpl_dir->fullname[length - 2] = 0;
2696 else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0)
2697 cmpl_dir->fullname[length - 2] = 0;
2698 else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0)
2702 strcpy(cmpl_dir->fullname, "/");
2703 cmpl_dir->fullname_len = 1;
2707 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2713 cmpl_dir->fullname[length - 2] = 0;
2715 if(!correct_parent(cmpl_dir, &sbuf))
2718 else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0)
2722 strcpy(cmpl_dir->fullname, "/");
2723 cmpl_dir->fullname_len = 1;
2727 if(stat(cmpl_dir->fullname, &sbuf) < 0)
2733 cmpl_dir->fullname[length - 3] = 0;
2735 if(!correct_parent(cmpl_dir, &sbuf))
2739 cmpl_dir->fullname_len = strlen(cmpl_dir->fullname);
2745 correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf)
2752 last_slash = strrchr(cmpl_dir->fullname, '/');
2754 g_assert(last_slash);
2756 if(last_slash != cmpl_dir->fullname)
2757 { /* last_slash[0] = 0; */ }
2764 if (stat(cmpl_dir->fullname, &parbuf) < 0)
2770 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2771 /* it wasn't a link */
2777 last_slash[0] = '/'; */
2779 /* it was a link, have to figure it out the hard way */
2781 new_name = find_parent_dir_fullname(cmpl_dir->fullname);
2786 g_free(cmpl_dir->fullname);
2788 cmpl_dir->fullname = new_name;
2794 find_parent_dir_fullname(gchar* dirname)
2796 gchar buffer[MAXPATHLEN];
2797 gchar buffer2[MAXPATHLEN];
2799 #if defined(sun) && !defined(__SVR4)
2802 if(!getcwd(buffer, MAXPATHLEN))
2809 if(chdir(dirname) != 0 || chdir("..") != 0)
2815 #if defined(sun) && !defined(__SVR4)
2818 if(!getcwd(buffer2, MAXPATHLEN))
2827 if(chdir(buffer) != 0)
2833 return g_strdup(buffer2);
2836 /**********************************************************************/
2837 /* Completion Operations */
2838 /**********************************************************************/
2840 static PossibleCompletion*
2841 attempt_homedir_completion(gchar* text_to_complete,
2842 CompletionState *cmpl_state)
2846 if (!cmpl_state->user_dir_name_buffer &&
2847 !get_pwdb(cmpl_state))
2849 length = strlen(text_to_complete) - 1;
2851 cmpl_state->user_completion_index += 1;
2853 while(cmpl_state->user_completion_index < cmpl_state->user_directories_len)
2855 index = first_diff_index(text_to_complete + 1,
2856 cmpl_state->user_directories
2857 [cmpl_state->user_completion_index].login);
2864 if(cmpl_state->last_valid_char < (index + 1))
2865 cmpl_state->last_valid_char = index + 1;
2866 cmpl_state->user_completion_index += 1;
2870 cmpl_state->the_completion.is_a_completion = 1;
2871 cmpl_state->the_completion.is_directory = 1;
2873 append_completion_text("~", cmpl_state);
2875 append_completion_text(cmpl_state->
2876 user_directories[cmpl_state->user_completion_index].login,
2879 return append_completion_text("/", cmpl_state);
2882 if(text_to_complete[1] ||
2883 cmpl_state->user_completion_index > cmpl_state->user_directories_len)
2885 cmpl_state->user_completion_index = -1;
2890 cmpl_state->user_completion_index += 1;
2891 cmpl_state->the_completion.is_a_completion = 1;
2892 cmpl_state->the_completion.is_directory = 1;
2894 return append_completion_text("~/", cmpl_state);
2898 /* returns the index (>= 0) of the first differing character,
2899 * PATTERN_MATCH if the completion matches */
2901 first_diff_index(gchar* pat, gchar* text)
2905 while(*pat && *text && *text == *pat)
2915 return PATTERN_MATCH;
2918 static PossibleCompletion*
2919 append_completion_text(gchar* text, CompletionState* cmpl_state)
2923 if(!cmpl_state->the_completion.text)
2926 len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1;
2928 if(cmpl_state->the_completion.text_alloc > len)
2930 strcat(cmpl_state->the_completion.text, text);
2931 return &cmpl_state->the_completion;
2934 while(i < len) { i <<= 1; }
2936 cmpl_state->the_completion.text_alloc = i;
2938 cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i);
2940 if(!cmpl_state->the_completion.text)
2944 strcat(cmpl_state->the_completion.text, text);
2945 return &cmpl_state->the_completion;
2949 static CompletionDir*
2950 find_completion_dir(gchar* text_to_complete,
2951 gchar** remaining_text,
2952 CompletionState* cmpl_state)
2954 gchar* first_slash = strchr(text_to_complete, '/');
2955 CompletionDir* dir = cmpl_state->reference_dir;
2956 CompletionDir* next;
2957 *remaining_text = text_to_complete;
2961 gint len = first_slash - *remaining_text;
2963 gchar *found_name = NULL; /* Quiet gcc */
2965 gchar* pat_buf = g_new (gchar, len + 1);
2967 strncpy(pat_buf, *remaining_text, len);
2970 for(i = 0; i < dir->sent->entry_count; i += 1)
2972 if(dir->sent->entries[i].is_dir &&
2973 fnmatch(pat_buf, dir->sent->entries[i].entry_name,
2974 FNMATCH_FLAGS)!= FNM_NOMATCH)
2984 found_name = dir->sent->entries[i].entry_name;
2991 /* Perhaps we are trying to open an automount directory */
2992 found_name = pat_buf;
2995 next = open_relative_dir(found_name, dir, cmpl_state);
3003 next->cmpl_parent = dir;
3007 if(!correct_dir_fullname(dir))
3013 *remaining_text = first_slash + 1;
3014 first_slash = strchr(*remaining_text, '/');
3023 update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state)
3027 if(!poss || !cmpl_is_a_completion(poss))
3030 cmpl_len = strlen(cmpl_this_completion(poss));
3032 if(cmpl_state->updated_text_alloc < cmpl_len + 1)
3034 cmpl_state->updated_text =
3035 (gchar*)g_realloc(cmpl_state->updated_text,
3036 cmpl_state->updated_text_alloc);
3037 cmpl_state->updated_text_alloc = 2*cmpl_len;
3040 if(cmpl_state->updated_text_len < 0)
3042 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
3043 cmpl_state->updated_text_len = cmpl_len;
3044 cmpl_state->re_complete = cmpl_is_directory(poss);
3046 else if(cmpl_state->updated_text_len == 0)
3048 cmpl_state->re_complete = FALSE;
3053 first_diff_index(cmpl_state->updated_text,
3054 cmpl_this_completion(poss));
3056 cmpl_state->re_complete = FALSE;
3058 if(first_diff == PATTERN_MATCH)
3061 if(first_diff > cmpl_state->updated_text_len)
3062 strcpy(cmpl_state->updated_text, cmpl_this_completion(poss));
3064 cmpl_state->updated_text_len = first_diff;
3065 cmpl_state->updated_text[first_diff] = 0;
3069 static PossibleCompletion*
3070 attempt_file_completion(CompletionState *cmpl_state)
3072 gchar *pat_buf, *first_slash;
3073 CompletionDir *dir = cmpl_state->active_completion_dir;
3075 dir->cmpl_index += 1;
3077 if(dir->cmpl_index == dir->sent->entry_count)
3079 if(dir->cmpl_parent == NULL)
3081 cmpl_state->active_completion_dir = NULL;
3087 cmpl_state->active_completion_dir = dir->cmpl_parent;
3089 return attempt_file_completion(cmpl_state);
3093 g_assert(dir->cmpl_text);
3095 first_slash = strchr(dir->cmpl_text, '/');
3099 gint len = first_slash - dir->cmpl_text;
3101 pat_buf = g_new (gchar, len + 1);
3102 strncpy(pat_buf, dir->cmpl_text, len);
3107 gint len = strlen(dir->cmpl_text);
3109 pat_buf = g_new (gchar, len + 2);
3110 strcpy(pat_buf, dir->cmpl_text);
3111 strcpy(pat_buf + len, "*");
3116 if(dir->sent->entries[dir->cmpl_index].is_dir)
3118 if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3119 FNMATCH_FLAGS) != FNM_NOMATCH)
3121 CompletionDir* new_dir;
3123 new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name,
3132 new_dir->cmpl_parent = dir;
3134 new_dir->cmpl_index = -1;
3135 new_dir->cmpl_text = first_slash + 1;
3137 cmpl_state->active_completion_dir = new_dir;
3140 return attempt_file_completion(cmpl_state);
3145 return attempt_file_completion(cmpl_state);
3151 return attempt_file_completion(cmpl_state);
3156 if(dir->cmpl_parent != NULL)
3158 append_completion_text(dir->fullname +
3159 strlen(cmpl_state->completion_dir->fullname) + 1,
3161 append_completion_text("/", cmpl_state);
3164 append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
3166 cmpl_state->the_completion.is_a_completion =
3167 (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3168 FNMATCH_FLAGS) != FNM_NOMATCH);
3170 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3171 if(dir->sent->entries[dir->cmpl_index].is_dir)
3172 append_completion_text("/", cmpl_state);
3175 return &cmpl_state->the_completion;
3181 get_pwdb(CompletionState* cmpl_state)
3183 struct passwd *pwd_ptr;
3185 gint len = 0, i, count = 0;
3187 if(cmpl_state->user_dir_name_buffer)
3191 while ((pwd_ptr = getpwent()) != NULL)
3193 len += strlen(pwd_ptr->pw_name);
3194 len += strlen(pwd_ptr->pw_dir);
3201 cmpl_state->user_dir_name_buffer = g_new(gchar, len);
3202 cmpl_state->user_directories = g_new(CompletionUserDir, count);
3203 cmpl_state->user_directories_len = count;
3205 buf_ptr = cmpl_state->user_dir_name_buffer;
3207 for(i = 0; i < count; i += 1)
3209 pwd_ptr = getpwent();
3216 strcpy(buf_ptr, pwd_ptr->pw_name);
3217 cmpl_state->user_directories[i].login = buf_ptr;
3218 buf_ptr += strlen(buf_ptr);
3220 strcpy(buf_ptr, pwd_ptr->pw_dir);
3221 cmpl_state->user_directories[i].homedir = buf_ptr;
3222 buf_ptr += strlen(buf_ptr);
3226 qsort(cmpl_state->user_directories,
3227 cmpl_state->user_directories_len,
3228 sizeof(CompletionUserDir),
3237 if(cmpl_state->user_dir_name_buffer)
3238 g_free(cmpl_state->user_dir_name_buffer);
3239 if(cmpl_state->user_directories)
3240 g_free(cmpl_state->user_directories);
3242 cmpl_state->user_dir_name_buffer = NULL;
3243 cmpl_state->user_directories = NULL;
3249 compare_user_dir(const void* a, const void* b)
3251 return strcmp((((CompletionUserDir*)a))->login,
3252 (((CompletionUserDir*)b))->login);
3256 compare_cmpl_dir(const void* a, const void* b)
3258 return strcmp((((CompletionDirEntry*)a))->entry_name,
3259 (((CompletionDirEntry*)b))->entry_name);
3263 cmpl_state_okay(CompletionState* cmpl_state)
3265 return cmpl_state && cmpl_state->reference_dir;
3269 cmpl_strerror(gint err)
3271 if(err == CMPL_ERRNO_TOO_LONG)
3272 return "Name too long";
3274 return g_strerror (err);
3281 /* Get the selected filename and print it to the console */
3282 void file_ok_sel( GtkWidget *w,
3283 GtkFileSelection *fs )
3285 g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
3288 void destroy( GtkWidget *widget,
3299 gtk_init (&argc, &argv);
3301 /* Create a new file selection widget */
3302 filew = gtk_file_selection_new ("Michael's Glorious File Selector");
3303 // gtk_file_selection_complete(GTK_FILE_SELECTION(filew),"bob");
3306 gtk_signal_connect (GTK_OBJECT (filew), "destroy",
3307 (GtkSignalFunc) destroy, &filew);
3308 /* Connect the ok_button to file_ok_sel function */
3309 gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
3310 "clicked", (GtkSignalFunc) file_ok_sel, filew );
3312 /* Connect the cancel_button to destroy the widget */
3313 gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION
3314 (filew)->cancel_button),
3315 "clicked", (GtkSignalFunc) gtk_widget_destroy,
3316 GTK_OBJECT (filew));
3319 gtk_widget_show(filew);
3322 g_print("%d",gtk_file_selection_match_mask("mask.c","m*.c"));
3323 g_print("%d",gtk_file_selection_match_mask("mask.c","m???.c"));
3324 g_print("%d",gtk_file_selection_match_mask("mask.c","m??*.c"));
3325 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c"));
3326 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c???"));
3327 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c*"));
3328 g_print("%d",gtk_file_selection_match_mask("mask.cout","n*.c???"));
3329 g_print("%d",gtk_file_selection_match_mask("mask.c","[mn]*"));
3330 g_print("%d",gtk_file_selection_match_mask("COPYING","*.xpm"));