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,
337 gpointer user_data );
339 static void gtk_file_selection_file_button( GtkWidget *widget,
342 GdkEventButton *bevent,
343 gpointer user_data );
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,
363 gchar *current_dir );
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,
372 gpointer user_data );
373 static gboolean gtk_file_selection_history_combo_list_callback( GtkWidget *thelist,
374 GdkEventButton *event,
375 gpointer user_data );
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 ){
422 if ( mask[0] == '[' ) {
423 if ( !strchr( mask,']' ) ) {
426 maskc = g_strdup( mask + 1 ); /* get the portion of mask inside []*/
428 ( *( strchr( maskc,']' ) ) ) = 0;
429 s = strlen( (char *)maskc );
431 for ( x = 0; x < s; x++ ) {
432 if ( text == maskc[x] ) {
441 if ( mask[0] == '?' ) {
444 if ( mask[0] == text ) {
452 static gint gtk_file_selection_match_mask( gchar *text, gchar *mask ){
459 if ( mask[0] == 0 && text[0] == 0 ) {
463 if ( mask[0] == '*' ) {
464 for ( tc = 0; tc <= strlen( text ); tc++ )
466 if ( gtk_file_selection_match_mask( text + tc, mask + 1 ) ) {
472 mc = gtk_file_selection_match_char( text[0], mask );
475 return gtk_file_selection_match_mask( text + 1, mask + mc );
483 gtk_file_selection_get_type( void ){
484 static GtkType file_selection_type = 0;
486 if ( !file_selection_type ) {
487 static const GtkTypeInfo filesel_info =
490 sizeof( GtkFileSelection ),
491 sizeof( GtkFileSelectionClass ),
492 (GtkClassInitFunc) gtk_file_selection_class_init,
493 (GtkObjectInitFunc) gtk_file_selection_init,
494 /* reserved_1 */ NULL,
495 /* reserved_2 */ NULL,
496 (GtkClassInitFunc) NULL,
499 file_selection_type = gtk_type_unique( GTK_TYPE_WINDOW, &filesel_info );
502 return file_selection_type;
506 gtk_file_selection_class_init( GtkFileSelectionClass *class ){
507 GtkObjectClass *object_class;
509 object_class = (GtkObjectClass*) class;
511 parent_class = gtk_type_class( GTK_TYPE_WINDOW );
513 object_class->destroy = gtk_file_selection_destroy;
517 gtk_file_selection_init( GtkFileSelection *filesel ){
518 GtkWidget *entry_vbox;
520 GtkWidget *list_hbox;
521 GtkWidget *confirm_area;
524 GtkWidget *pulldown_hbox;
525 GtkWidget *scrolled_win;
526 GtkWidget *mask_label;
528 GtkWidget *label_lookingin;
529 GtkWidget *up_button;
530 GtkWidget *home_button;
531 GtkWidget *prev_button;
532 GtkWidget *next_button;
533 GtkWidget *refresh_button;
536 char *file_title [2];
538 filesel->cmpl_state = cmpl_init_state();
540 filesel->mask = NULL;
541 filesel->prev_history = NULL;
542 filesel->next_history = NULL;
543 filesel->saved_entry = NULL;
545 /* The dialog-sized vertical box */
546 filesel->main_vbox = gtk_vbox_new( FALSE, 10 );
547 gtk_container_set_border_width( GTK_CONTAINER( filesel ), 10 );
548 gtk_container_add( GTK_CONTAINER( filesel ), filesel->main_vbox );
549 gtk_widget_show( filesel->main_vbox );
551 /* The horizontal box containing create, rename etc. buttons */
552 filesel->button_area = gtk_hbutton_box_new();
553 gtk_button_box_set_layout( GTK_BUTTON_BOX( filesel->button_area ), GTK_BUTTONBOX_START );
554 gtk_button_box_set_spacing( GTK_BUTTON_BOX( filesel->button_area ), 0 );
555 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), filesel->button_area,
557 gtk_widget_show( filesel->button_area );
559 gtk_file_selection_show_fileop_buttons( filesel );
561 /* hbox for pulldown menu */
562 pulldown_hbox = gtk_hbox_new( FALSE, 5 );
563 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), pulldown_hbox, FALSE, FALSE, 0 );
564 gtk_widget_show( pulldown_hbox );
566 /* The combo box that replaces the pulldown menu */
567 label_lookingin = gtk_label_new( _( "Looking in:" ) );
568 gtk_widget_show( label_lookingin );
569 gtk_box_pack_start( GTK_BOX( pulldown_hbox ), label_lookingin, FALSE, FALSE, 0 );
571 filesel->history_combo = gtk_combo_new();
572 gtk_widget_show( filesel->history_combo );
573 gtk_combo_set_value_in_list( GTK_COMBO( filesel->history_combo ),FALSE,FALSE );
574 gtk_box_pack_start( GTK_BOX( pulldown_hbox ),filesel->history_combo,
576 gtk_signal_connect( GTK_OBJECT( ( (GtkCombo *)filesel->history_combo )->entry ),"key-press-event",
577 (GtkSignalFunc) gtk_file_selection_history_combo_callback,
578 (gpointer) filesel );
580 gtk_signal_connect( GTK_OBJECT( ( (GtkCombo *)filesel->history_combo )->list ),"button-press-event",
581 (GtkSignalFunc) gtk_file_selection_history_combo_list_callback,
582 (gpointer) filesel );
584 gtk_signal_connect( GTK_OBJECT( ( (GtkCombo *)filesel->history_combo )->list ),"key-press-event",
585 (GtkSignalFunc) gtk_file_selection_history_combo_list_key_handler,
586 (gpointer) filesel );
588 /* frame to put the following hbox in */
589 bigframe = gtk_frame_new( NULL );
590 gtk_widget_show( bigframe );
591 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), bigframe, TRUE, TRUE, 0 );
593 /* The horizontal box containing the directory and file listboxes */
594 list_hbox = gtk_hbox_new( FALSE, 5 );
595 gtk_container_add( GTK_CONTAINER( bigframe ), list_hbox );
596 gtk_container_set_border_width( GTK_CONTAINER( list_hbox ), 5 );
597 gtk_widget_show( list_hbox );
599 /* vbox to put the buttons and directory listing in */
600 vbox = gtk_vbox_new( FALSE, 0 );
601 gtk_widget_show( vbox );
602 gtk_box_pack_start( GTK_BOX( list_hbox ), vbox, FALSE, FALSE, 0 );
604 hbox = gtk_hbox_new( FALSE, 0 );
605 gtk_widget_show( hbox );
606 gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
608 home_button = gtk_button_new_with_label( _( "Home" ) );
609 gtk_widget_show( home_button );
610 gtk_signal_connect( GTK_OBJECT( home_button ), "clicked",
611 (GtkSignalFunc) gtk_file_selection_home_button,
612 (gpointer) filesel );
613 gtk_box_pack_start( GTK_BOX( hbox ), home_button, TRUE,TRUE, 0 );
615 prev_button = gtk_button_new_with_label( _( "Prev" ) );
616 gtk_signal_connect( GTK_OBJECT( prev_button ), "clicked",
617 (GtkSignalFunc) gtk_file_selection_prev_button,
618 (gpointer) filesel );
619 gtk_widget_show( prev_button );
620 gtk_box_pack_start( GTK_BOX( hbox ), prev_button, TRUE,TRUE, 0 );
622 up_button = gtk_button_new_with_label( _( "Up" ) );
623 gtk_signal_connect( GTK_OBJECT( up_button ), "clicked",
624 (GtkSignalFunc) gtk_file_selection_up_button,
625 (gpointer) filesel );
626 gtk_widget_show( up_button );
627 gtk_box_pack_start( GTK_BOX( hbox ), up_button, TRUE,TRUE, 0 );
629 next_button = gtk_button_new_with_label( _( "Next" ) );
630 gtk_widget_show( next_button );
631 gtk_signal_connect( GTK_OBJECT( next_button ), "clicked",
632 (GtkSignalFunc) gtk_file_selection_next_button,
633 (gpointer) filesel );
634 gtk_box_pack_start( GTK_BOX( hbox ), next_button, TRUE,TRUE, 0 );
636 refresh_button = gtk_button_new_with_label( _( "Refresh" ) );
637 gtk_widget_show( refresh_button );
638 gtk_signal_connect( GTK_OBJECT( refresh_button ), "clicked",
639 (GtkSignalFunc) gtk_file_selection_refresh_button,
640 (gpointer) filesel );
641 gtk_box_pack_start( GTK_BOX( hbox ), refresh_button, TRUE, TRUE, 0 );
643 /* The directories clist */
644 dir_title[0] = _( "Directories" );
646 filesel->dir_list = gtk_clist_new_with_titles( 1, (gchar**) dir_title );
647 gtk_widget_set_usize( filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT );
648 gtk_signal_connect( GTK_OBJECT( filesel->dir_list ), "select_row",
649 (GtkSignalFunc) gtk_file_selection_dir_button,
650 (gpointer) filesel );
651 gtk_signal_connect( GTK_OBJECT( filesel->dir_list ), "unselect_row",
652 (GtkSignalFunc) gtk_file_selection_undir_button,
653 (gpointer) filesel );
654 gtk_clist_column_titles_passive( GTK_CLIST( filesel->dir_list ) );
656 scrolled_win = gtk_scrolled_window_new( NULL, NULL );
657 gtk_container_add( GTK_CONTAINER( scrolled_win ), filesel->dir_list );
658 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ),
659 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
660 gtk_box_pack_start( GTK_BOX( vbox ), scrolled_win, TRUE,TRUE, 5 );
661 gtk_widget_show( filesel->dir_list );
662 gtk_widget_show( scrolled_win );
664 /* vbox area for mask entry and files clist */
665 vbox = gtk_vbox_new( FALSE, 0 );
666 gtk_widget_show( vbox );
667 gtk_box_pack_start( GTK_BOX( list_hbox ), vbox, TRUE, TRUE, 0 );
669 hbox = gtk_hbox_new( FALSE, 5 );
670 gtk_widget_show( hbox );
671 gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
673 mask_label = gtk_label_new( _( "Mask:" ) );
674 gtk_widget_show( mask_label );
675 gtk_box_pack_start( GTK_BOX( hbox ), mask_label, FALSE, FALSE, 0 );
677 filesel->mask_entry = gtk_entry_new();
678 gtk_widget_show( filesel->mask_entry );
679 gtk_signal_connect( GTK_OBJECT( filesel->mask_entry ),"activate",
680 (GtkSignalFunc) gtk_file_selection_mask_entry_callback,
681 (gpointer) filesel );
682 gtk_box_pack_start( GTK_BOX( hbox ),filesel->mask_entry, TRUE, TRUE, 0 );
685 /* The files clist */
686 file_title[0] = _( "Files" );
687 file_title[1] = NULL;
688 filesel->file_list = gtk_clist_new_with_titles( 1, (gchar**) file_title );
689 gtk_widget_set_usize( filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT );
690 gtk_signal_connect( GTK_OBJECT( filesel->file_list ), "select_row",
691 (GtkSignalFunc) gtk_file_selection_file_button,
692 (gpointer) filesel );
693 gtk_clist_column_titles_passive( GTK_CLIST( filesel->file_list ) );
695 scrolled_win = gtk_scrolled_window_new( NULL, NULL );
696 gtk_container_add( GTK_CONTAINER( scrolled_win ), filesel->file_list );
697 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ),
698 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
699 gtk_box_pack_start( GTK_BOX( vbox ), scrolled_win, TRUE, TRUE, 5 );
700 gtk_widget_show( filesel->file_list );
701 gtk_widget_show( scrolled_win );
703 /* action area for packing buttons into. */
704 filesel->action_area = gtk_hbox_new( TRUE, 0 );
705 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), filesel->action_area,
707 gtk_widget_show( filesel->action_area );
709 /* The OK/Cancel button area */
710 confirm_area = gtk_hbutton_box_new();
711 gtk_button_box_set_layout( GTK_BUTTON_BOX( confirm_area ), GTK_BUTTONBOX_END );
712 gtk_button_box_set_spacing( GTK_BUTTON_BOX( confirm_area ), 5 );
713 gtk_box_pack_end( GTK_BOX( filesel->main_vbox ), confirm_area, FALSE, FALSE, 0 );
714 gtk_widget_show( confirm_area );
717 filesel->ok_button = gtk_button_new_with_label( _( "OK" ) );
718 GTK_WIDGET_SET_FLAGS( filesel->ok_button, GTK_CAN_DEFAULT );
719 gtk_box_pack_start( GTK_BOX( confirm_area ), filesel->ok_button, TRUE, TRUE, 0 );
720 gtk_widget_grab_default( filesel->ok_button );
721 gtk_widget_show( filesel->ok_button );
723 /* The Cancel button */
724 filesel->cancel_button = gtk_button_new_with_label( _( "Cancel" ) );
725 GTK_WIDGET_SET_FLAGS( filesel->cancel_button, GTK_CAN_DEFAULT );
726 gtk_box_pack_start( GTK_BOX( confirm_area ), filesel->cancel_button, TRUE, TRUE, 0 );
727 gtk_widget_show( filesel->cancel_button );
729 /* The selection entry widget */
730 entry_vbox = gtk_vbox_new( FALSE, 2 );
731 gtk_box_pack_end( GTK_BOX( filesel->main_vbox ), entry_vbox, FALSE, FALSE, 0 );
732 gtk_widget_show( entry_vbox );
734 filesel->selection_text = label = gtk_label_new( "" );
735 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
736 gtk_box_pack_start( GTK_BOX( entry_vbox ), label, FALSE, FALSE, 0 );
737 gtk_widget_show( label );
739 filesel->selection_entry = gtk_entry_new();
740 gtk_signal_connect( GTK_OBJECT( filesel->selection_entry ), "key_press_event",
741 (GtkSignalFunc) gtk_file_selection_key_press, filesel );
742 gtk_signal_connect_object( GTK_OBJECT( filesel->selection_entry ), "focus_in_event",
743 (GtkSignalFunc) gtk_widget_grab_default,
744 GTK_OBJECT( filesel->ok_button ) );
745 gtk_signal_connect_object( GTK_OBJECT( filesel->selection_entry ), "activate",
746 (GtkSignalFunc) gtk_button_clicked,
747 GTK_OBJECT( filesel->ok_button ) );
748 gtk_box_pack_start( GTK_BOX( entry_vbox ), filesel->selection_entry, TRUE, TRUE, 0 );
749 gtk_widget_show( filesel->selection_entry );
751 if ( !cmpl_state_okay( filesel->cmpl_state ) ) {
754 sprintf( err_buf, _( "Directory unreadable: %s" ), cmpl_strerror( cmpl_errno ) );
756 gtk_label_set_text( GTK_LABEL( filesel->selection_text ), err_buf );
760 gtk_file_selection_populate( filesel, "", FALSE );
763 gtk_widget_grab_focus( filesel->selection_entry );
767 gtk_file_selection_new( const gchar *title ){
768 GtkFileSelection *filesel;
770 filesel = gtk_type_new( GTK_TYPE_FILE_SELECTION );
771 gtk_window_set_title( GTK_WINDOW( filesel ), title );
773 return GTK_WIDGET( filesel );
777 gtk_file_selection_show_fileop_buttons( GtkFileSelection *filesel ){
778 g_return_if_fail( filesel != NULL );
779 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
781 /* delete, create directory, and rename */
782 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,
786 (gpointer) filesel );
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 ) {
793 filesel->fileop_del_file = gtk_button_new_with_label( _( "Delete File" ) );
794 gtk_signal_connect( GTK_OBJECT( filesel->fileop_del_file ), "clicked",
795 (GtkSignalFunc) gtk_file_selection_delete_file,
796 (gpointer) filesel );
797 gtk_box_pack_start( GTK_BOX( filesel->button_area ),
798 filesel->fileop_del_file, TRUE, TRUE, 0 );
799 gtk_widget_show( filesel->fileop_del_file );
802 if ( !filesel->fileop_ren_file ) {
803 filesel->fileop_ren_file = gtk_button_new_with_label( _( "Rename File" ) );
804 gtk_signal_connect( GTK_OBJECT( filesel->fileop_ren_file ), "clicked",
805 (GtkSignalFunc) gtk_file_selection_rename_file,
806 (gpointer) filesel );
807 gtk_box_pack_start( GTK_BOX( filesel->button_area ),
808 filesel->fileop_ren_file, TRUE, TRUE, 0 );
809 gtk_widget_show( filesel->fileop_ren_file );
812 gtk_widget_queue_resize( GTK_WIDGET( filesel ) );
816 gtk_file_selection_hide_fileop_buttons( GtkFileSelection *filesel ){
817 g_return_if_fail( filesel != NULL );
818 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
820 if ( filesel->fileop_ren_file ) {
821 gtk_widget_destroy( filesel->fileop_ren_file );
822 filesel->fileop_ren_file = NULL;
825 if ( filesel->fileop_del_file ) {
826 gtk_widget_destroy( filesel->fileop_del_file );
827 filesel->fileop_del_file = NULL;
830 if ( filesel->fileop_c_dir ) {
831 gtk_widget_destroy( filesel->fileop_c_dir );
832 filesel->fileop_c_dir = NULL;
839 gtk_file_selection_set_filename( GtkFileSelection *filesel,
840 const gchar *filename ){
841 char buf[MAXPATHLEN];
842 const char *name, *last_slash;
844 g_return_if_fail( filesel != NULL );
845 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
846 g_return_if_fail( filename != NULL );
848 last_slash = strrchr( filename, '/' );
856 gint len = MIN( MAXPATHLEN - 1, last_slash - filename + 1 );
858 strncpy( buf, filename, len );
861 name = last_slash + 1;
864 gtk_file_selection_populate( filesel, buf, FALSE );
866 if ( filesel->selection_entry ) {
867 gtk_entry_set_text( GTK_ENTRY( filesel->selection_entry ), name );
872 gtk_file_selection_get_filename( GtkFileSelection *filesel ){
873 static char nothing[2] = "";
877 g_return_val_if_fail( filesel != NULL, nothing );
878 g_return_val_if_fail( GTK_IS_FILE_SELECTION( filesel ), nothing );
880 text = gtk_entry_get_text( GTK_ENTRY( filesel->selection_entry ) );
882 filename = cmpl_completion_fullname( text, filesel->cmpl_state );
890 gtk_file_selection_complete( GtkFileSelection *filesel,
891 const gchar *pattern ){
895 g_return_if_fail( filesel != NULL );
896 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
897 g_return_if_fail( pattern != NULL );
899 if ( filesel->selection_entry ) {
900 gtk_entry_set_text( GTK_ENTRY( filesel->selection_entry ), pattern );
903 if ( strchr( pattern,'*' ) || strchr( pattern,'?' ) ) {
904 for ( x = strlen( pattern ); x >= 0; x-- )
906 if ( pattern[x] == '/' ) {
910 gtk_entry_set_text( GTK_ENTRY( filesel->mask_entry ),g_strdup( pattern + x + 1 ) );
912 if ( filesel->mask ) {
913 g_free( filesel->mask );
916 filesel->mask = g_strdup( pattern + x + 1 );
917 new_pattern = g_strdup( pattern );
918 new_pattern[x + 1] = 0;
919 gtk_file_selection_populate( filesel, (gchar*) new_pattern, TRUE );
920 g_free( new_pattern );
924 gtk_file_selection_populate( filesel, (gchar*) pattern, TRUE );
929 gtk_file_selection_destroy( GtkObject *object ){
930 GtkFileSelection *filesel;
933 g_return_if_fail( object != NULL );
934 g_return_if_fail( GTK_IS_FILE_SELECTION( object ) );
936 filesel = GTK_FILE_SELECTION( object );
938 if ( filesel->fileop_dialog ) {
939 gtk_widget_destroy( filesel->fileop_dialog );
942 if ( filesel->next_history ) {
943 list = filesel->next_history;
946 g_free( list->data );
950 g_list_free( filesel->next_history );
951 filesel->next_history = NULL;
953 if ( filesel->prev_history ) {
954 list = filesel->prev_history;
957 g_free( list->data );
961 g_list_free( filesel->prev_history );
962 filesel->prev_history = NULL;
964 if ( filesel->mask ) {
965 g_free( filesel->mask );
966 filesel->mask = NULL;
969 cmpl_free_state( filesel->cmpl_state );
970 filesel->cmpl_state = NULL;
972 if ( GTK_OBJECT_CLASS( parent_class )->destroy ) {
973 ( *GTK_OBJECT_CLASS( parent_class )->destroy )( object );
977 /* Begin file operations callbacks */
980 gtk_file_selection_fileop_error( GtkFileSelection *fs, gchar *error_message ){
986 g_return_if_fail( error_message != NULL );
989 dialog = gtk_dialog_new();
991 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
992 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
995 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Error" ) );
996 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
998 /* If file dialog is grabbed, make this dialog modal too */
999 /* When error dialog is closed, file dialog will be grabbed again */
1000 if ( GTK_WINDOW( fs )->modal ) {
1001 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1004 vbox = gtk_vbox_new( FALSE, 0 );
1005 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1006 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1008 gtk_widget_show( vbox );
1010 label = gtk_label_new( error_message );
1011 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1012 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1013 gtk_widget_show( label );
1015 /* yes, we free it */
1016 g_free( error_message );
1019 button = gtk_button_new_with_label( _( "Close" ) );
1020 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1021 (GtkSignalFunc) gtk_widget_destroy,
1022 (gpointer) dialog );
1023 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1024 button, TRUE, TRUE, 0 );
1025 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1026 gtk_widget_grab_default( button );
1027 gtk_widget_show( button );
1029 gtk_widget_show( dialog );
1033 gtk_file_selection_fileop_destroy( GtkWidget *widget, gpointer data ){
1034 GtkFileSelection *fs = data;
1036 g_return_if_fail( fs != NULL );
1037 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1039 fs->fileop_dialog = NULL;
1044 gtk_file_selection_create_dir_confirmed( GtkWidget *widget, gpointer data ){
1045 GtkFileSelection *fs = data;
1050 CompletionState *cmpl_state;
1052 g_return_if_fail( fs != NULL );
1053 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1055 dirname = gtk_entry_get_text( GTK_ENTRY( fs->fileop_entry ) );
1056 cmpl_state = (CompletionState*) fs->cmpl_state;
1057 path = cmpl_reference_position( cmpl_state );
1059 full_path = g_strconcat( path, "/", dirname, NULL );
1060 if ( ( mkdir( full_path, 0755 ) < 0 ) ) {
1061 buf = g_strconcat( "Error creating directory \"", dirname, "\": ",
1062 g_strerror( errno ), NULL );
1063 gtk_file_selection_fileop_error( fs, buf );
1065 g_free( full_path );
1067 gtk_widget_destroy( fs->fileop_dialog );
1068 gtk_file_selection_populate( fs, "", FALSE );
1072 gtk_file_selection_create_dir( GtkWidget *widget, gpointer data ){
1073 GtkFileSelection *fs = data;
1079 g_return_if_fail( fs != NULL );
1080 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1082 if ( fs->fileop_dialog ) {
1087 fs->fileop_dialog = dialog = gtk_dialog_new();
1088 gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1089 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1091 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Create Directory" ) );
1092 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1094 /* If file dialog is grabbed, grab option dialog */
1095 /* When option dialog is closed, file dialog will be grabbed again */
1096 if ( GTK_WINDOW( fs )->modal ) {
1097 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1100 vbox = gtk_vbox_new( FALSE, 0 );
1101 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1102 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1104 gtk_widget_show( vbox );
1106 label = gtk_label_new( _( "Directory name:" ) );
1107 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1108 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1109 gtk_widget_show( label );
1111 /* The directory entry widget */
1112 fs->fileop_entry = gtk_entry_new();
1113 gtk_box_pack_start( GTK_BOX( vbox ), fs->fileop_entry,
1115 GTK_WIDGET_SET_FLAGS( fs->fileop_entry, GTK_CAN_DEFAULT );
1116 gtk_widget_show( fs->fileop_entry );
1119 button = gtk_button_new_with_label( _( "Create" ) );
1120 gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1121 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
1123 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1124 button, TRUE, TRUE, 0 );
1125 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1126 gtk_widget_show( button );
1128 button = gtk_button_new_with_label( _( "Cancel" ) );
1129 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1130 (GtkSignalFunc) gtk_widget_destroy,
1131 (gpointer) dialog );
1132 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1133 button, TRUE, TRUE, 0 );
1134 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1135 gtk_widget_grab_default( button );
1136 gtk_widget_show( button );
1138 gtk_widget_show( dialog );
1142 gtk_file_selection_delete_file_confirmed( GtkWidget *widget, gpointer data ){
1143 GtkFileSelection *fs = data;
1144 CompletionState *cmpl_state;
1149 g_return_if_fail( fs != NULL );
1150 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1152 cmpl_state = (CompletionState*) fs->cmpl_state;
1153 path = cmpl_reference_position( cmpl_state );
1155 full_path = g_strconcat( path, "/", fs->fileop_file, NULL );
1156 if ( ( unlink( full_path ) < 0 ) ) {
1157 buf = g_strconcat( "Error deleting file \"", fs->fileop_file, "\": ",
1158 g_strerror( errno ), NULL );
1159 gtk_file_selection_fileop_error( fs, buf );
1161 g_free( full_path );
1163 gtk_widget_destroy( fs->fileop_dialog );
1164 gtk_file_selection_populate( fs, "", FALSE );
1168 gtk_file_selection_delete_file( GtkWidget *widget, gpointer data ){
1169 GtkFileSelection *fs = data;
1177 g_return_if_fail( fs != NULL );
1178 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1180 if ( fs->fileop_dialog ) {
1184 filename = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1185 if ( strlen( filename ) < 1 ) {
1189 fs->fileop_file = filename;
1192 fs->fileop_dialog = dialog = gtk_dialog_new();
1193 gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1194 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1196 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Delete File" ) );
1197 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1199 /* If file dialog is grabbed, grab option dialog */
1200 /* When option dialog is closed, file dialog will be grabbed again */
1201 if ( GTK_WINDOW( fs )->modal ) {
1202 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1205 vbox = gtk_vbox_new( FALSE, 0 );
1206 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1207 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1209 gtk_widget_show( vbox );
1211 buf = g_strconcat( "Really delete file \"", filename, "\" ?", NULL );
1212 label = gtk_label_new( buf );
1213 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1214 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1215 gtk_widget_show( label );
1219 button = gtk_button_new_with_label( _( "Delete" ) );
1220 gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1221 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
1223 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1224 button, TRUE, TRUE, 0 );
1225 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1226 gtk_widget_show( button );
1228 button = gtk_button_new_with_label( _( "Cancel" ) );
1229 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1230 (GtkSignalFunc) gtk_widget_destroy,
1231 (gpointer) dialog );
1232 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1233 button, TRUE, TRUE, 0 );
1234 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1235 gtk_widget_grab_default( button );
1236 gtk_widget_show( button );
1238 gtk_widget_show( dialog );
1243 gtk_file_selection_rename_file_confirmed( GtkWidget *widget, gpointer data ){
1244 GtkFileSelection *fs = data;
1248 gchar *new_filename;
1249 gchar *old_filename;
1250 CompletionState *cmpl_state;
1252 g_return_if_fail( fs != NULL );
1253 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1255 file = gtk_entry_get_text( GTK_ENTRY( fs->fileop_entry ) );
1256 cmpl_state = (CompletionState*) fs->cmpl_state;
1257 path = cmpl_reference_position( cmpl_state );
1259 new_filename = g_strconcat( path, "/", file, NULL );
1260 old_filename = g_strconcat( path, "/", fs->fileop_file, NULL );
1262 if ( ( rename( old_filename, new_filename ) ) < 0 ) {
1263 buf = g_strconcat( "Error renaming file \"", file, "\": ",
1264 g_strerror( errno ), NULL );
1265 gtk_file_selection_fileop_error( fs, buf );
1267 g_free( new_filename );
1268 g_free( old_filename );
1270 gtk_widget_destroy( fs->fileop_dialog );
1271 gtk_file_selection_populate( fs, "", FALSE );
1275 gtk_file_selection_rename_file( GtkWidget *widget, gpointer data ){
1276 GtkFileSelection *fs = data;
1283 g_return_if_fail( fs != NULL );
1284 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1286 if ( fs->fileop_dialog ) {
1290 fs->fileop_file = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1291 if ( strlen( fs->fileop_file ) < 1 ) {
1296 fs->fileop_dialog = dialog = gtk_dialog_new();
1297 gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1298 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1300 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Rename File" ) );
1301 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1303 /* If file dialog is grabbed, grab option dialog */
1304 /* When option dialog closed, file dialog will be grabbed again */
1305 if ( GTK_WINDOW( fs )->modal ) {
1306 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1309 vbox = gtk_vbox_new( FALSE, 0 );
1310 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1311 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1313 gtk_widget_show( vbox );
1315 buf = g_strconcat( "Rename file \"", fs->fileop_file, "\" to:", NULL );
1316 label = gtk_label_new( buf );
1317 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1318 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1319 gtk_widget_show( label );
1322 /* New filename entry */
1323 fs->fileop_entry = gtk_entry_new();
1324 gtk_box_pack_start( GTK_BOX( vbox ), fs->fileop_entry,
1326 GTK_WIDGET_SET_FLAGS( fs->fileop_entry, GTK_CAN_DEFAULT );
1327 gtk_widget_show( fs->fileop_entry );
1329 gtk_entry_set_text( GTK_ENTRY( fs->fileop_entry ), fs->fileop_file );
1330 gtk_editable_select_region( GTK_EDITABLE( fs->fileop_entry ),
1331 0, strlen( fs->fileop_file ) );
1334 button = gtk_button_new_with_label( _( "Rename" ) );
1335 gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1336 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
1338 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1339 button, TRUE, TRUE, 0 );
1340 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1341 gtk_widget_show( button );
1343 button = gtk_button_new_with_label( _( "Cancel" ) );
1344 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1345 (GtkSignalFunc) gtk_widget_destroy,
1346 (gpointer) dialog );
1347 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1348 button, TRUE, TRUE, 0 );
1349 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1350 gtk_widget_grab_default( button );
1351 gtk_widget_show( button );
1353 gtk_widget_show( dialog );
1358 gtk_file_selection_key_press( GtkWidget *widget,
1360 gpointer user_data ){
1361 GtkFileSelection *fs;
1364 g_return_val_if_fail( widget != NULL, FALSE );
1365 g_return_val_if_fail( event != NULL, FALSE );
1367 fs = GTK_FILE_SELECTION( user_data );
1369 if ( event->keyval == GDK_Tab ) {
1370 text = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1372 text = g_strdup( text );
1374 gtk_file_selection_populate( fs, text, TRUE );
1378 gtk_signal_emit_stop_by_name( GTK_OBJECT( widget ), "key_press_event" );
1382 if ( fs->saved_entry ) {
1383 gtk_clist_unselect_all( (GtkCList *) ( fs->dir_list ) );
1384 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),fs->saved_entry );
1385 g_free( fs->saved_entry );
1386 fs->saved_entry = NULL;
1394 gtk_file_selection_home_button( GtkWidget *widget, gpointer data ){
1397 GtkFileSelection *fs = data;
1399 list = fs->next_history;
1401 g_free( list->data );
1404 g_list_free( fs->next_history );
1405 fs->next_history = NULL;
1407 gtk_file_selection_populate( fs,"~/",FALSE );
1411 gtk_file_selection_up_button( GtkWidget *widget, gpointer data ){
1412 GtkFileSelection *fs = data;
1415 g_return_if_fail( fs != NULL );
1416 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1418 list = fs->next_history;
1420 g_free( list->data );
1423 g_list_free( fs->next_history );
1424 fs->next_history = NULL;
1426 gtk_file_selection_populate( fs, "../", FALSE ); /*change directories. */
1431 gtk_file_selection_prev_button( GtkWidget *widget, gpointer data ){
1432 GtkFileSelection *fs = data;
1437 g_return_if_fail( fs != NULL );
1438 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1440 list = fs->prev_history;
1442 if ( list && g_list_length( list ) > 1 ) {
1443 first = list; /* get first element */
1444 list = list->next; /* pop off current directory */
1446 list->prev = NULL; /* make this the new head. */
1448 fs->prev_history = list; /* update prev_history list */
1449 fs->next_history = g_list_prepend( fs->next_history,first->data ); /* put it on next_history */
1451 first->next = NULL; /* orphan the old first node */
1452 g_list_free( first ); /* free the node (data is now in use by next_history) */
1456 path = g_malloc( strlen( list->data ) + 4 ); /* plenty of space */
1457 strcpy( path,list->data ); /* get the 2nd path in the history */
1458 strcat( path,"/" ); /* append a '/' */
1459 gtk_file_selection_populate( fs, path, FALSE ); /* change directories. */
1465 gtk_file_selection_next_button( GtkWidget *widget, gpointer data ){
1466 GtkFileSelection *fs = data;
1471 g_return_if_fail( fs != NULL );
1472 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1474 list = fs->next_history;
1476 if ( list && g_list_length( list ) > 0 ) {
1477 first = list; /*get first element*/
1478 list = list->next; /*pop off current directory*/
1484 fs->next_history = list; /*update prev_history list*/
1486 path = g_malloc( strlen( first->data ) + 4 ); /*plenty of space*/
1487 strcpy( path,first->data );
1488 strcat( path,"/" ); /*append a / */
1489 gtk_file_selection_populate( fs, path, FALSE ); /*change directories.*/
1492 first->next = NULL; /* orphan the old first node */
1493 g_list_free( first ); /* free the node (data is now in use by next_history) */
1499 gtk_file_selection_refresh_button( GtkWidget *widget, gpointer data ){
1500 GtkFileSelection *fs = data;
1502 g_return_if_fail( fs != NULL );
1503 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1505 gtk_file_selection_populate( fs,"",FALSE );
1509 gtk_file_selection_mask_entry_callback( GtkWidget *widget, gpointer data ){
1510 GtkFileSelection *fs = data;
1516 fs->mask = g_strdup( gtk_entry_get_text( GTK_ENTRY( fs->mask_entry ) ) );
1518 if ( strlen( fs->mask ) == 0 ) {
1523 gtk_file_selection_refresh_button( widget,data );
1526 static gboolean gtk_file_selection_history_combo_list_key_handler( GtkWidget *widget,
1528 gpointer user_data ){
1530 g_print("Key pressed! \n");
1536 static gboolean gtk_file_selection_history_combo_list_callback( GtkWidget *thelist,
1537 GdkEventButton *event,
1538 gpointer user_data ){
1540 GtkFileSelection *fs = user_data;
1544 list = fs->next_history;
1546 g_free( list->data );
1549 g_list_free( fs->next_history );
1550 fs->next_history = NULL;
1552 path = g_malloc( strlen( gtk_entry_get_text( GTK_ENTRY( ( (GtkCombo *)fs->history_combo )->entry ) ) ) + 4 );
1553 strcpy( path,gtk_entry_get_text( GTK_ENTRY( ( (GtkCombo *)fs->history_combo )->entry ) ) );
1556 gtk_file_selection_populate( fs,path,TRUE );
1564 gtk_file_selection_history_combo_callback( GtkWidget *widget, GdkEventKey *event, gpointer data ){
1565 GtkEntry *entry = (GtkEntry *)widget;
1566 GtkFileSelection *fs = data;
1570 g_return_val_if_fail( fs != NULL,FALSE );
1571 g_return_val_if_fail( GTK_IS_FILE_SELECTION( fs ),FALSE );
1574 if ( event->keyval == GDK_Return ) {
1575 list = fs->next_history;
1577 g_free( list->data );
1580 g_list_free( fs->next_history );
1581 fs->next_history = NULL;
1583 path = g_malloc( strlen( gtk_entry_get_text( entry ) ) + 4 );
1584 strcpy( path,gtk_entry_get_text( entry ) );
1586 gtk_file_selection_populate( fs,path,TRUE );
1588 gtk_signal_emit_stop_by_name( GTK_OBJECT( widget ), "key_press_event" );
1599 gtk_file_selection_update_history_menu( GtkFileSelection *fs,
1600 gchar *current_directory ){
1603 g_return_if_fail( fs != NULL );
1604 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1605 g_return_if_fail( current_directory != NULL );
1607 current_dir = g_strdup( current_directory );
1609 if ( fs->prev_history ) {
1610 if ( strcmp( ( fs->prev_history )->data,current_dir ) ) { /*if this item isn't on the top of the list */
1611 fs->prev_history = g_list_prepend( fs->prev_history,g_strdup( current_dir ) );
1615 fs->prev_history = g_list_prepend( fs->prev_history,g_strdup( current_dir ) );
1618 gtk_combo_set_popdown_strings( GTK_COMBO( fs->history_combo ),fs->prev_history );
1620 g_free( current_dir );
1624 gtk_file_selection_file_button( GtkWidget *widget,
1627 GdkEventButton *bevent,
1628 gpointer user_data ){
1629 GtkFileSelection *fs = NULL;
1630 gchar *filename, *temp = NULL;
1632 g_return_if_fail( GTK_IS_CLIST( widget ) );
1635 g_return_if_fail( fs != NULL );
1636 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1638 gtk_clist_get_text( GTK_CLIST( fs->file_list ), row, 0, &temp );
1639 filename = g_strdup( temp );
1643 switch ( bevent->type )
1645 case GDK_2BUTTON_PRESS:
1646 gtk_button_clicked( GTK_BUTTON( fs->ok_button ) );
1650 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1655 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1663 gtk_file_selection_dir_button( GtkWidget *widget,
1666 GdkEventButton *bevent,
1667 gpointer user_data ){
1669 GtkFileSelection *fs = NULL;
1670 gchar *filename, *temp = NULL;
1672 g_return_if_fail( GTK_IS_CLIST( widget ) );
1674 fs = GTK_FILE_SELECTION( user_data );
1675 g_return_if_fail( fs != NULL );
1676 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1678 gtk_clist_get_text( GTK_CLIST( fs->dir_list ), row, 0, &temp );
1679 filename = g_strdup( temp );
1683 switch ( bevent->type )
1685 case GDK_2BUTTON_PRESS:
1686 list = fs->next_history;
1688 g_free( list->data );
1691 g_list_free( fs->next_history );
1692 fs->next_history = NULL;
1694 gtk_file_selection_populate( fs, filename, FALSE );
1695 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),fs->saved_entry );
1696 g_free( fs->saved_entry );
1697 fs->saved_entry = NULL;
1701 /* here we need to add the "filename" to the beginning of what's already
1702 in the entry. Save what's in the entry, then restore it on the double click
1704 if ( fs->saved_entry ) {
1705 g_free( fs->saved_entry );
1707 fs->saved_entry = g_strdup( gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) ) );
1709 temp = g_strconcat( filename,fs->saved_entry,NULL );
1710 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), temp );
1717 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1725 gtk_file_selection_undir_button( GtkWidget *widget,
1728 GdkEventButton *bevent,
1729 gpointer user_data ){
1730 GtkFileSelection *fs = NULL;
1731 gchar *filename, *temp = NULL;
1733 g_return_if_fail( GTK_IS_CLIST( widget ) );
1735 fs = GTK_FILE_SELECTION( user_data );
1736 g_return_if_fail( fs != NULL );
1737 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1739 gtk_clist_get_text( GTK_CLIST( fs->dir_list ), row, 0, &temp );
1740 filename = g_strdup( temp );
1744 switch ( bevent->type )
1747 /* here we need to add the "filename" to the beginning of what's already
1748 in the entry. Save what's in the entry, then restore it on the double click
1750 if ( fs->saved_entry ) {
1751 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),fs->saved_entry );
1752 g_free( fs->saved_entry );
1753 fs->saved_entry = NULL;
1759 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename ); //?????
1767 gtk_file_selection_populate( GtkFileSelection *fs,
1769 gint try_complete ){
1770 CompletionState *cmpl_state;
1771 PossibleCompletion* poss;
1774 gchar* rem_path = rel_path;
1777 gint did_recurse = FALSE;
1778 gint possible_count = 0;
1779 gint selection_index = -1;
1780 gint file_list_width;
1781 gint dir_list_width;
1783 g_return_if_fail( fs != NULL );
1784 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1786 cmpl_state = (CompletionState*) fs->cmpl_state;
1787 poss = cmpl_completion_matches( rel_path, &rem_path, cmpl_state );
1789 if ( !cmpl_state_okay( cmpl_state ) ) {
1790 /* Something went wrong. */
1791 gtk_file_selection_abort( fs );
1795 g_assert( cmpl_state->reference_dir );
1797 gtk_clist_freeze( GTK_CLIST( fs->dir_list ) );
1798 gtk_clist_clear( GTK_CLIST( fs->dir_list ) );
1799 gtk_clist_freeze( GTK_CLIST( fs->file_list ) );
1800 gtk_clist_clear( GTK_CLIST( fs->file_list ) );
1802 /* Set the dir_list to include ./ and ../ */
1805 row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1808 row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1810 /*reset the max widths of the lists*/
1811 dir_list_width = gdk_string_width( fs->dir_list->style->font,"../" );
1812 gtk_clist_set_column_width( GTK_CLIST( fs->dir_list ),0,dir_list_width );
1813 file_list_width = 1;
1814 gtk_clist_set_column_width( GTK_CLIST( fs->file_list ),0,file_list_width );
1818 if ( cmpl_is_a_completion( poss ) ) {
1819 possible_count += 1;
1821 filename = cmpl_this_completion( poss );
1825 if ( cmpl_is_directory( poss ) ) {
1826 if ( strcmp( filename, "./" ) != 0 &&
1827 strcmp( filename, "../" ) != 0 ) {
1828 int width = gdk_string_width( fs->dir_list->style->font,
1830 row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1831 if ( width > dir_list_width ) {
1832 dir_list_width = width;
1833 gtk_clist_set_column_width( GTK_CLIST( fs->dir_list ),0,
1841 if ( gtk_file_selection_match_mask( filename,fs->mask ) ) {
1842 int width = gdk_string_width( fs->file_list->style->font,
1844 row = gtk_clist_append( GTK_CLIST( fs->file_list ), text );
1845 if ( width > file_list_width ) {
1846 file_list_width = width;
1847 gtk_clist_set_column_width( GTK_CLIST( fs->file_list ),0,
1854 int width = gdk_string_width( fs->file_list->style->font,
1856 row = gtk_clist_append( GTK_CLIST( fs->file_list ), text );
1857 if ( width > file_list_width ) {
1858 file_list_width = width;
1859 gtk_clist_set_column_width( GTK_CLIST( fs->file_list ),0,
1866 poss = cmpl_next_completion( cmpl_state );
1869 gtk_clist_thaw( GTK_CLIST( fs->dir_list ) );
1870 gtk_clist_thaw( GTK_CLIST( fs->file_list ) );
1872 /* File lists are set. */
1874 g_assert( cmpl_state->reference_dir );
1876 if ( try_complete ) {
1878 /* User is trying to complete filenames, so advance the user's input
1879 * string to the updated_text, which is the common leading substring
1880 * of all possible completions, and if its a directory attempt
1881 * attempt completions in it. */
1883 if ( cmpl_updated_text( cmpl_state )[0] ) {
1885 if ( cmpl_updated_dir( cmpl_state ) ) {
1886 gchar* dir_name = g_strdup( cmpl_updated_text( cmpl_state ) );
1890 gtk_file_selection_populate( fs, dir_name, TRUE );
1896 if ( fs->selection_entry ) {
1897 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),
1898 cmpl_updated_text( cmpl_state ) );
1904 selection_index = cmpl_last_valid_char( cmpl_state ) -
1905 ( strlen( rel_path ) - strlen( rem_path ) );
1906 if ( fs->selection_entry ) {
1907 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), rem_path );
1913 if ( fs->selection_entry ) {
1914 /* Here we need to take the old filename and keep it!*/
1915 /*gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");*/
1920 if ( !did_recurse ) {
1921 if ( fs->selection_entry ) {
1922 gtk_entry_set_position( GTK_ENTRY( fs->selection_entry ), selection_index );
1925 if ( fs->selection_entry ) {
1926 sel_text = g_strconcat( _( "Selection: " ),
1927 cmpl_reference_position( cmpl_state ),
1930 gtk_label_set_text( GTK_LABEL( fs->selection_text ), sel_text );
1934 gtk_file_selection_update_history_menu( fs, cmpl_reference_position( cmpl_state ) );
1940 gtk_file_selection_abort( GtkFileSelection *fs ){
1943 sprintf( err_buf, _( "Directory unreadable: %s" ), cmpl_strerror( cmpl_errno ) );
1945 /* BEEP gdk_beep(); */
1947 if ( fs->selection_entry ) {
1948 gtk_label_set_text( GTK_LABEL( fs->selection_text ), err_buf );
1952 /**********************************************************************/
1953 /* External Interface */
1954 /**********************************************************************/
1956 /* The four completion state selectors
1959 cmpl_updated_text( CompletionState* cmpl_state ){
1960 return cmpl_state->updated_text;
1964 cmpl_updated_dir( CompletionState* cmpl_state ){
1965 return cmpl_state->re_complete;
1969 cmpl_reference_position( CompletionState* cmpl_state ){
1970 return cmpl_state->reference_dir->fullname;
1974 cmpl_last_valid_char( CompletionState* cmpl_state ){
1975 return cmpl_state->last_valid_char;
1979 cmpl_completion_fullname( gchar* text, CompletionState* cmpl_state ){
1980 static char nothing[2] = "";
1982 if ( !cmpl_state_okay( cmpl_state ) ) {
1985 else if ( text[0] == '/' ) {
1986 strcpy( cmpl_state->updated_text, text );
1988 else if ( text[0] == '~' ) {
1992 dir = open_user_dir( text, cmpl_state );
1995 /* spencer says just return ~something, so
1996 * for now just do it. */
1997 strcpy( cmpl_state->updated_text, text );
2002 strcpy( cmpl_state->updated_text, dir->fullname );
2004 slash = strchr( text, '/' );
2007 strcat( cmpl_state->updated_text, slash );
2013 strcpy( cmpl_state->updated_text, cmpl_state->reference_dir->fullname );
2014 if ( strcmp( cmpl_state->reference_dir->fullname, "/" ) != 0 ) {
2015 strcat( cmpl_state->updated_text, "/" );
2017 strcat( cmpl_state->updated_text, text );
2020 return cmpl_state->updated_text;
2023 /* The three completion selectors
2026 cmpl_this_completion( PossibleCompletion* pc ){
2031 cmpl_is_directory( PossibleCompletion* pc ){
2032 return pc->is_directory;
2036 cmpl_is_a_completion( PossibleCompletion* pc ){
2037 return pc->is_a_completion;
2040 /**********************************************************************/
2041 /* Construction, deletion */
2042 /**********************************************************************/
2044 static CompletionState*
2045 cmpl_init_state( void ){
2046 gchar getcwd_buf[2 * MAXPATHLEN];
2047 CompletionState *new_state;
2049 new_state = g_new( CompletionState, 1 );
2051 /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
2052 * and, if that wasn't bad enough, hangs in doing so.
2054 #if defined( sun ) && !defined( __SVR4 )
2055 if ( !getwd( getcwd_buf ) )
2057 if ( !getcwd( getcwd_buf, MAXPATHLEN ) )
2060 /* Oh joy, we can't get the current directory. Um..., we should have
2061 * a root directory, right? Right? (Probably not portable to non-Unix)
2063 strcpy( getcwd_buf, "/" );
2068 new_state->reference_dir = NULL;
2069 new_state->completion_dir = NULL;
2070 new_state->active_completion_dir = NULL;
2071 new_state->directory_storage = NULL;
2072 new_state->directory_sent_storage = NULL;
2073 new_state->last_valid_char = 0;
2074 new_state->updated_text = g_new( gchar, MAXPATHLEN );
2075 new_state->updated_text_alloc = MAXPATHLEN;
2076 new_state->the_completion.text = g_new( gchar, MAXPATHLEN );
2077 new_state->the_completion.text_alloc = MAXPATHLEN;
2078 new_state->user_dir_name_buffer = NULL;
2079 new_state->user_directories = NULL;
2081 new_state->reference_dir = open_dir( getcwd_buf, new_state );
2083 if ( !new_state->reference_dir ) {
2084 /* Directories changing from underneath us, grumble */
2085 strcpy( getcwd_buf, "/" );
2093 cmpl_free_dir_list( GList* dp0 ){
2097 free_dir( dp->data );
2105 cmpl_free_dir_sent_list( GList* dp0 ){
2109 free_dir_sent( dp->data );
2117 cmpl_free_state( CompletionState* cmpl_state ){
2118 cmpl_free_dir_list( cmpl_state->directory_storage );
2119 cmpl_free_dir_sent_list( cmpl_state->directory_sent_storage );
2121 if ( cmpl_state->user_dir_name_buffer ) {
2122 g_free( cmpl_state->user_dir_name_buffer );
2124 if ( cmpl_state->user_directories ) {
2125 g_free( cmpl_state->user_directories );
2127 if ( cmpl_state->the_completion.text ) {
2128 g_free( cmpl_state->the_completion.text );
2130 if ( cmpl_state->updated_text ) {
2131 g_free( cmpl_state->updated_text );
2134 g_free( cmpl_state );
2138 free_dir( CompletionDir* dir ){
2139 g_free( dir->fullname );
2144 free_dir_sent( CompletionDirSent* sent ){
2145 g_free( sent->name_buffer );
2146 g_free( sent->entries );
2151 prune_memory_usage( CompletionState *cmpl_state ){
2152 GList* cdsl = cmpl_state->directory_sent_storage;
2153 GList* cdl = cmpl_state->directory_storage;
2157 for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1 )
2161 cmpl_free_dir_sent_list( cdsl->next );
2165 cmpl_state->directory_storage = NULL;
2167 if ( cdl->data == cmpl_state->reference_dir ) {
2168 cmpl_state->directory_storage = g_list_prepend( NULL, cdl->data );
2171 free_dir( cdl->data );
2176 g_list_free( cdl0 );
2179 /**********************************************************************/
2180 /* The main entrances. */
2181 /**********************************************************************/
2183 static PossibleCompletion*
2184 cmpl_completion_matches( gchar* text_to_complete,
2185 gchar** remaining_text,
2186 CompletionState* cmpl_state ){
2188 PossibleCompletion *poss;
2190 prune_memory_usage( cmpl_state );
2192 g_assert( text_to_complete != NULL );
2194 cmpl_state->user_completion_index = -1;
2195 cmpl_state->last_completion_text = text_to_complete;
2196 cmpl_state->the_completion.text[0] = 0;
2197 cmpl_state->last_valid_char = 0;
2198 cmpl_state->updated_text_len = -1;
2199 cmpl_state->updated_text[0] = 0;
2200 cmpl_state->re_complete = FALSE;
2202 first_slash = strchr( text_to_complete, '/' );
2204 if ( text_to_complete[0] == '~' && !first_slash ) {
2205 /* Text starts with ~ and there is no slash, show all the
2206 * home directory completions.
2208 poss = attempt_homedir_completion( text_to_complete, cmpl_state );
2210 update_cmpl( poss, cmpl_state );
2215 cmpl_state->reference_dir =
2216 open_ref_dir( text_to_complete, remaining_text, cmpl_state );
2218 if ( !cmpl_state->reference_dir ) {
2222 cmpl_state->completion_dir =
2223 find_completion_dir( *remaining_text, remaining_text, cmpl_state );
2225 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2227 if ( !cmpl_state->completion_dir ) {
2231 cmpl_state->completion_dir->cmpl_index = -1;
2232 cmpl_state->completion_dir->cmpl_parent = NULL;
2233 cmpl_state->completion_dir->cmpl_text = *remaining_text;
2235 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2237 cmpl_state->reference_dir = cmpl_state->completion_dir;
2239 poss = attempt_file_completion( cmpl_state );
2241 update_cmpl( poss, cmpl_state );
2246 static PossibleCompletion*
2247 cmpl_next_completion( CompletionState* cmpl_state ){
2248 PossibleCompletion* poss = NULL;
2250 cmpl_state->the_completion.text[0] = 0;
2252 if ( cmpl_state->user_completion_index >= 0 ) {
2253 poss = attempt_homedir_completion( cmpl_state->last_completion_text, cmpl_state );
2256 poss = attempt_file_completion( cmpl_state );
2259 update_cmpl( poss, cmpl_state );
2264 /**********************************************************************/
2265 /* Directory Operations */
2266 /**********************************************************************/
2268 /* Open the directory where completion will begin from, if possible. */
2269 static CompletionDir*
2270 open_ref_dir( gchar* text_to_complete,
2271 gchar** remaining_text,
2272 CompletionState* cmpl_state ){
2274 CompletionDir *new_dir;
2276 first_slash = strchr( text_to_complete, '/' );
2278 if ( text_to_complete[0] == '~' ) {
2279 new_dir = open_user_dir( text_to_complete, cmpl_state );
2282 if ( first_slash ) {
2283 *remaining_text = first_slash + 1;
2286 *remaining_text = text_to_complete + strlen( text_to_complete );
2294 else if ( text_to_complete[0] == '/' || !cmpl_state->reference_dir ) {
2295 gchar *tmp = g_strdup( text_to_complete );
2299 while ( *p && *p != '*' && *p != '?' )
2303 p = strrchr( tmp, '/' );
2311 new_dir = open_dir( tmp, cmpl_state );
2314 *remaining_text = text_to_complete +
2315 ( ( p == tmp + 1 ) ? ( p - tmp ) : ( p + 1 - tmp ) );
2320 /* If no possible candidates, use the cwd */
2321 gchar *curdir = g_get_current_dir();
2323 new_dir = open_dir( curdir, cmpl_state );
2326 *remaining_text = text_to_complete;
2336 *remaining_text = text_to_complete;
2338 new_dir = open_dir( cmpl_state->reference_dir->fullname, cmpl_state );
2342 new_dir->cmpl_index = -1;
2343 new_dir->cmpl_parent = NULL;
2349 /* open a directory by user name */
2350 static CompletionDir*
2351 open_user_dir( gchar* text_to_complete,
2352 CompletionState *cmpl_state ){
2356 g_assert( text_to_complete && text_to_complete[0] == '~' );
2358 first_slash = strchr( text_to_complete, '/' );
2360 if ( first_slash ) {
2361 cmp_len = first_slash - text_to_complete - 1;
2364 cmp_len = strlen( text_to_complete + 1 );
2369 gchar *homedir = g_get_home_dir();
2372 return open_dir( homedir, cmpl_state );
2381 char* copy = g_new( char, cmp_len + 1 );
2383 strncpy( copy, text_to_complete + 1, cmp_len );
2385 pwd = getpwnam( copy );
2392 return open_dir( pwd->pw_dir, cmpl_state );
2396 /* open a directory relative the the current relative directory */
2397 static CompletionDir*
2398 open_relative_dir( gchar* dir_name,
2400 CompletionState *cmpl_state ){
2401 gchar path_buf[2 * MAXPATHLEN];
2403 if ( dir->fullname_len + strlen( dir_name ) + 2 >= MAXPATHLEN ) {
2404 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2408 strcpy( path_buf, dir->fullname );
2410 if ( dir->fullname_len > 1 ) {
2411 path_buf[dir->fullname_len] = '/';
2412 strcpy( path_buf + dir->fullname_len + 1, dir_name );
2416 strcpy( path_buf + dir->fullname_len, dir_name );
2419 return open_dir( path_buf, cmpl_state );
2422 /* after the cache lookup fails, really open a new directory */
2423 static CompletionDirSent*
2424 open_new_dir( gchar* dir_name, struct stat* sbuf, gboolean stat_subdirs ){
2425 CompletionDirSent* sent;
2428 struct dirent *dirent_ptr;
2429 gint buffer_size = 0;
2430 gint entry_count = 0;
2432 struct stat ent_sbuf;
2433 char path_buf[MAXPATHLEN * 2];
2436 sent = g_new( CompletionDirSent, 1 );
2437 sent->mtime = sbuf->st_mtime;
2438 sent->inode = sbuf->st_ino;
2439 sent->device = sbuf->st_dev;
2441 path_buf_len = strlen( dir_name );
2443 if ( path_buf_len > MAXPATHLEN ) {
2444 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2448 strcpy( path_buf, dir_name );
2450 directory = opendir( dir_name );
2457 while ( ( dirent_ptr = readdir( directory ) ) != NULL )
2459 int entry_len = strlen( dirent_ptr->d_name );
2460 buffer_size += entry_len + 1;
2463 if ( path_buf_len + entry_len + 2 >= MAXPATHLEN ) {
2464 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2465 closedir( directory );
2470 sent->name_buffer = g_new( gchar, buffer_size );
2471 sent->entries = g_new( CompletionDirEntry, entry_count );
2472 sent->entry_count = entry_count;
2474 buffer_ptr = sent->name_buffer;
2476 rewinddir( directory );
2478 for ( i = 0; i < entry_count; i += 1 )
2480 dirent_ptr = readdir( directory );
2482 if ( !dirent_ptr ) {
2484 closedir( directory );
2488 strcpy( buffer_ptr, dirent_ptr->d_name );
2489 sent->entries[i].entry_name = buffer_ptr;
2490 buffer_ptr += strlen( dirent_ptr->d_name );
2494 path_buf[path_buf_len] = '/';
2495 strcpy( path_buf + path_buf_len + 1, dirent_ptr->d_name );
2497 if ( stat_subdirs ) {
2498 if ( stat( path_buf, &ent_sbuf ) >= 0 && S_ISDIR( ent_sbuf.st_mode ) ) {
2499 sent->entries[i].is_dir = 1;
2502 /* stat may fail, and we don't mind, since it could be a
2503 * dangling symlink. */
2504 sent->entries[i].is_dir = 0;
2508 sent->entries[i].is_dir = 1;
2512 qsort( sent->entries, sent->entry_count, sizeof( CompletionDirEntry ), compare_cmpl_dir );
2514 closedir( directory );
2520 check_dir( gchar *dir_name, struct stat *result, gboolean *stat_subdirs ){
2521 /* A list of directories that we know only contain other directories.
2522 * Trying to stat every file in these directories would be very
2529 struct stat statbuf;
2530 } no_stat_dirs[] = {
2531 { "/afs", FALSE, { 0 } },
2532 { "/net", FALSE, { 0 } }
2535 static const gint n_no_stat_dirs = sizeof( no_stat_dirs ) / sizeof( no_stat_dirs[0] );
2536 static gboolean initialized = FALSE;
2540 if ( !initialized ) {
2542 for ( i = 0; i < n_no_stat_dirs; i++ )
2544 if ( stat( no_stat_dirs[i].name, &no_stat_dirs[i].statbuf ) == 0 ) {
2545 no_stat_dirs[i].present = TRUE;
2550 if ( stat( dir_name, result ) < 0 ) {
2555 *stat_subdirs = TRUE;
2556 for ( i = 0; i < n_no_stat_dirs; i++ )
2558 if ( no_stat_dirs[i].present &&
2559 ( no_stat_dirs[i].statbuf.st_dev == result->st_dev ) &&
2560 ( no_stat_dirs[i].statbuf.st_ino == result->st_ino ) ) {
2561 *stat_subdirs = FALSE;
2569 /* open a directory by absolute pathname */
2570 static CompletionDir*
2571 open_dir( gchar* dir_name, CompletionState* cmpl_state ){
2573 gboolean stat_subdirs;
2574 CompletionDirSent *sent;
2577 if ( !check_dir( dir_name, &sbuf, &stat_subdirs ) ) {
2581 cdsl = cmpl_state->directory_sent_storage;
2587 if ( sent->inode == sbuf.st_ino &&
2588 sent->mtime == sbuf.st_mtime &&
2589 sent->device == sbuf.st_dev ) {
2590 return attach_dir( sent, dir_name, cmpl_state );
2596 sent = open_new_dir( dir_name, &sbuf, stat_subdirs );
2599 cmpl_state->directory_sent_storage =
2600 g_list_prepend( cmpl_state->directory_sent_storage, sent );
2602 return attach_dir( sent, dir_name, cmpl_state );
2608 static CompletionDir*
2609 attach_dir( CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state ){
2610 CompletionDir* new_dir;
2612 new_dir = g_new( CompletionDir, 1 );
2614 cmpl_state->directory_storage =
2615 g_list_prepend( cmpl_state->directory_storage, new_dir );
2617 new_dir->sent = sent;
2618 new_dir->fullname = g_strdup( dir_name );
2619 new_dir->fullname_len = strlen( dir_name );
2625 correct_dir_fullname( CompletionDir* cmpl_dir ){
2626 gint length = strlen( cmpl_dir->fullname );
2629 if ( strcmp( cmpl_dir->fullname + length - 2, "/." ) == 0 ) {
2630 if ( length == 2 ) {
2631 strcpy( cmpl_dir->fullname, "/" );
2632 cmpl_dir->fullname_len = 1;
2636 cmpl_dir->fullname[length - 2] = 0;
2639 else if ( strcmp( cmpl_dir->fullname + length - 3, "/./" ) == 0 ) {
2640 cmpl_dir->fullname[length - 2] = 0;
2642 else if ( strcmp( cmpl_dir->fullname + length - 3, "/.." ) == 0 ) {
2643 if ( length == 3 ) {
2644 strcpy( cmpl_dir->fullname, "/" );
2645 cmpl_dir->fullname_len = 1;
2649 if ( stat( cmpl_dir->fullname, &sbuf ) < 0 ) {
2654 cmpl_dir->fullname[length - 2] = 0;
2656 if ( !correct_parent( cmpl_dir, &sbuf ) ) {
2660 else if ( strcmp( cmpl_dir->fullname + length - 4, "/../" ) == 0 ) {
2661 if ( length == 4 ) {
2662 strcpy( cmpl_dir->fullname, "/" );
2663 cmpl_dir->fullname_len = 1;
2667 if ( stat( cmpl_dir->fullname, &sbuf ) < 0 ) {
2672 cmpl_dir->fullname[length - 3] = 0;
2674 if ( !correct_parent( cmpl_dir, &sbuf ) ) {
2679 cmpl_dir->fullname_len = strlen( cmpl_dir->fullname );
2685 correct_parent( CompletionDir* cmpl_dir, struct stat *sbuf ){
2691 last_slash = strrchr( cmpl_dir->fullname, '/' );
2693 g_assert( last_slash );
2695 if ( last_slash != cmpl_dir->fullname ) { /* last_slash[0] = 0; */
2703 if ( stat( cmpl_dir->fullname, &parbuf ) < 0 ) {
2708 if ( parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev ) {
2709 /* it wasn't a link */
2717 last_slash[0] = '/'; */
2719 /* it was a link, have to figure it out the hard way */
2721 new_name = find_parent_dir_fullname( cmpl_dir->fullname );
2727 g_free( cmpl_dir->fullname );
2729 cmpl_dir->fullname = new_name;
2735 find_parent_dir_fullname( gchar* dirname ){
2736 gchar buffer[MAXPATHLEN];
2737 gchar buffer2[MAXPATHLEN];
2739 #if defined( sun ) && !defined( __SVR4 )
2740 if ( !getwd( buffer ) )
2742 if ( !getcwd( buffer, MAXPATHLEN ) )
2749 if ( chdir( dirname ) != 0 || chdir( ".." ) != 0 ) {
2754 #if defined( sun ) && !defined( __SVR4 )
2755 if ( !getwd( buffer2 ) )
2757 if ( !getcwd( buffer2, MAXPATHLEN ) )
2766 if ( chdir( buffer ) != 0 ) {
2771 return g_strdup( buffer2 );
2774 /**********************************************************************/
2775 /* Completion Operations */
2776 /**********************************************************************/
2778 static PossibleCompletion*
2779 attempt_homedir_completion( gchar* text_to_complete,
2780 CompletionState *cmpl_state ){
2783 if ( !cmpl_state->user_dir_name_buffer &&
2784 !get_pwdb( cmpl_state ) ) {
2787 length = strlen( text_to_complete ) - 1;
2789 cmpl_state->user_completion_index += 1;
2791 while ( cmpl_state->user_completion_index < cmpl_state->user_directories_len )
2793 index = first_diff_index( text_to_complete + 1,
2794 cmpl_state->user_directories
2795 [cmpl_state->user_completion_index].login );
2802 if ( cmpl_state->last_valid_char < ( index + 1 ) ) {
2803 cmpl_state->last_valid_char = index + 1;
2805 cmpl_state->user_completion_index += 1;
2809 cmpl_state->the_completion.is_a_completion = 1;
2810 cmpl_state->the_completion.is_directory = 1;
2812 append_completion_text( "~", cmpl_state );
2814 append_completion_text( cmpl_state->
2815 user_directories[cmpl_state->user_completion_index].login,
2818 return append_completion_text( "/", cmpl_state );
2821 if ( text_to_complete[1] ||
2822 cmpl_state->user_completion_index > cmpl_state->user_directories_len ) {
2823 cmpl_state->user_completion_index = -1;
2828 cmpl_state->user_completion_index += 1;
2829 cmpl_state->the_completion.is_a_completion = 1;
2830 cmpl_state->the_completion.is_directory = 1;
2832 return append_completion_text( "~/", cmpl_state );
2836 /* returns the index (>= 0) of the first differing character,
2837 * PATTERN_MATCH if the completion matches */
2839 first_diff_index( gchar* pat, gchar* text ){
2842 while ( *pat && *text && *text == *pat )
2853 return PATTERN_MATCH;
2856 static PossibleCompletion*
2857 append_completion_text( gchar* text, CompletionState* cmpl_state ){
2860 if ( !cmpl_state->the_completion.text ) {
2864 len = strlen( text ) + strlen( cmpl_state->the_completion.text ) + 1;
2866 if ( cmpl_state->the_completion.text_alloc > len ) {
2867 strcat( cmpl_state->the_completion.text, text );
2868 return &cmpl_state->the_completion;
2871 while ( i < len ) { i <<= 1; }
2873 cmpl_state->the_completion.text_alloc = i;
2875 cmpl_state->the_completion.text = (gchar*)g_realloc( cmpl_state->the_completion.text, i );
2877 if ( !cmpl_state->the_completion.text ) {
2882 strcat( cmpl_state->the_completion.text, text );
2883 return &cmpl_state->the_completion;
2887 static CompletionDir*
2888 find_completion_dir( gchar* text_to_complete,
2889 gchar** remaining_text,
2890 CompletionState* cmpl_state ){
2891 gchar* first_slash = strchr( text_to_complete, '/' );
2892 CompletionDir* dir = cmpl_state->reference_dir;
2893 CompletionDir* next;
2894 *remaining_text = text_to_complete;
2896 while ( first_slash )
2898 gint len = first_slash - *remaining_text;
2900 gchar *found_name = NULL; /* Quiet gcc */
2902 gchar* pat_buf = g_new( gchar, len + 1 );
2904 strncpy( pat_buf, *remaining_text, len );
2907 for ( i = 0; i < dir->sent->entry_count; i += 1 )
2909 if ( dir->sent->entries[i].is_dir &&
2910 fnmatch( pat_buf, dir->sent->entries[i].entry_name,
2911 FNMATCH_FLAGS ) != FNM_NOMATCH ) {
2919 found_name = dir->sent->entries[i].entry_name;
2925 /* Perhaps we are trying to open an automount directory */
2926 found_name = pat_buf;
2929 next = open_relative_dir( found_name, dir, cmpl_state );
2936 next->cmpl_parent = dir;
2940 if ( !correct_dir_fullname( dir ) ) {
2945 *remaining_text = first_slash + 1;
2946 first_slash = strchr( *remaining_text, '/' );
2955 update_cmpl( PossibleCompletion* poss, CompletionState* cmpl_state ){
2958 if ( !poss || !cmpl_is_a_completion( poss ) ) {
2962 cmpl_len = strlen( cmpl_this_completion( poss ) );
2964 if ( cmpl_state->updated_text_alloc < cmpl_len + 1 ) {
2965 cmpl_state->updated_text =
2966 (gchar*)g_realloc( cmpl_state->updated_text,
2967 cmpl_state->updated_text_alloc );
2968 cmpl_state->updated_text_alloc = 2 * cmpl_len;
2971 if ( cmpl_state->updated_text_len < 0 ) {
2972 strcpy( cmpl_state->updated_text, cmpl_this_completion( poss ) );
2973 cmpl_state->updated_text_len = cmpl_len;
2974 cmpl_state->re_complete = cmpl_is_directory( poss );
2976 else if ( cmpl_state->updated_text_len == 0 ) {
2977 cmpl_state->re_complete = FALSE;
2982 first_diff_index( cmpl_state->updated_text,
2983 cmpl_this_completion( poss ) );
2985 cmpl_state->re_complete = FALSE;
2987 if ( first_diff == PATTERN_MATCH ) {
2991 if ( first_diff > cmpl_state->updated_text_len ) {
2992 strcpy( cmpl_state->updated_text, cmpl_this_completion( poss ) );
2995 cmpl_state->updated_text_len = first_diff;
2996 cmpl_state->updated_text[first_diff] = 0;
3000 static PossibleCompletion*
3001 attempt_file_completion( CompletionState *cmpl_state ){
3002 gchar *pat_buf, *first_slash;
3003 CompletionDir *dir = cmpl_state->active_completion_dir;
3005 dir->cmpl_index += 1;
3007 if ( dir->cmpl_index == dir->sent->entry_count ) {
3008 if ( dir->cmpl_parent == NULL ) {
3009 cmpl_state->active_completion_dir = NULL;
3015 cmpl_state->active_completion_dir = dir->cmpl_parent;
3017 return attempt_file_completion( cmpl_state );
3021 g_assert( dir->cmpl_text );
3023 first_slash = strchr( dir->cmpl_text, '/' );
3025 if ( first_slash ) {
3026 gint len = first_slash - dir->cmpl_text;
3028 pat_buf = g_new( gchar, len + 1 );
3029 strncpy( pat_buf, dir->cmpl_text, len );
3034 gint len = strlen( dir->cmpl_text );
3036 pat_buf = g_new( gchar, len + 2 );
3037 strcpy( pat_buf, dir->cmpl_text );
3038 strcpy( pat_buf + len, "*" );
3041 if ( first_slash ) {
3042 if ( dir->sent->entries[dir->cmpl_index].is_dir ) {
3043 if ( fnmatch( pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3044 FNMATCH_FLAGS ) != FNM_NOMATCH ) {
3045 CompletionDir* new_dir;
3047 new_dir = open_relative_dir( dir->sent->entries[dir->cmpl_index].entry_name,
3055 new_dir->cmpl_parent = dir;
3057 new_dir->cmpl_index = -1;
3058 new_dir->cmpl_text = first_slash + 1;
3060 cmpl_state->active_completion_dir = new_dir;
3063 return attempt_file_completion( cmpl_state );
3068 return attempt_file_completion( cmpl_state );
3074 return attempt_file_completion( cmpl_state );
3079 if ( dir->cmpl_parent != NULL ) {
3080 append_completion_text( dir->fullname +
3081 strlen( cmpl_state->completion_dir->fullname ) + 1,
3083 append_completion_text( "/", cmpl_state );
3086 append_completion_text( dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state );
3088 cmpl_state->the_completion.is_a_completion =
3089 ( fnmatch( pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3090 FNMATCH_FLAGS ) != FNM_NOMATCH );
3092 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3093 if ( dir->sent->entries[dir->cmpl_index].is_dir ) {
3094 append_completion_text( "/", cmpl_state );
3098 return &cmpl_state->the_completion;
3104 get_pwdb( CompletionState* cmpl_state ){
3105 struct passwd *pwd_ptr;
3107 gint len = 0, i, count = 0;
3109 if ( cmpl_state->user_dir_name_buffer ) {
3114 while ( ( pwd_ptr = getpwent() ) != NULL )
3116 len += strlen( pwd_ptr->pw_name );
3117 len += strlen( pwd_ptr->pw_dir );
3124 cmpl_state->user_dir_name_buffer = g_new( gchar, len );
3125 cmpl_state->user_directories = g_new( CompletionUserDir, count );
3126 cmpl_state->user_directories_len = count;
3128 buf_ptr = cmpl_state->user_dir_name_buffer;
3130 for ( i = 0; i < count; i += 1 )
3132 pwd_ptr = getpwent();
3138 strcpy( buf_ptr, pwd_ptr->pw_name );
3139 cmpl_state->user_directories[i].login = buf_ptr;
3140 buf_ptr += strlen( buf_ptr );
3142 strcpy( buf_ptr, pwd_ptr->pw_dir );
3143 cmpl_state->user_directories[i].homedir = buf_ptr;
3144 buf_ptr += strlen( buf_ptr );
3148 qsort( cmpl_state->user_directories,
3149 cmpl_state->user_directories_len,
3150 sizeof( CompletionUserDir ),
3159 if ( cmpl_state->user_dir_name_buffer ) {
3160 g_free( cmpl_state->user_dir_name_buffer );
3162 if ( cmpl_state->user_directories ) {
3163 g_free( cmpl_state->user_directories );
3166 cmpl_state->user_dir_name_buffer = NULL;
3167 cmpl_state->user_directories = NULL;
3173 compare_user_dir( const void* a, const void* b ){
3174 return strcmp( ( ( (CompletionUserDir*)a ) )->login,
3175 ( ( (CompletionUserDir*)b ) )->login );
3179 compare_cmpl_dir( const void* a, const void* b ){
3180 return strcmp( ( ( (CompletionDirEntry*)a ) )->entry_name,
3181 ( ( (CompletionDirEntry*)b ) )->entry_name );
3185 cmpl_state_okay( CompletionState* cmpl_state ){
3186 return cmpl_state && cmpl_state->reference_dir;
3190 cmpl_strerror( gint err ){
3191 if ( err == CMPL_ERRNO_TOO_LONG ) {
3192 return "Name too long";
3195 return g_strerror( err );
3203 /* Get the selected filename and print it to the console */
3204 void file_ok_sel( GtkWidget *w,
3205 GtkFileSelection *fs ){
3206 g_print( "%s\n", gtk_file_selection_get_filename( GTK_FILE_SELECTION( fs ) ) );
3209 void destroy( GtkWidget *widget,
3218 gtk_init( &argc, &argv );
3220 /* Create a new file selection widget */
3221 filew = gtk_file_selection_new( "Michael's Glorious File Selector" );
3222 // gtk_file_selection_complete(GTK_FILE_SELECTION(filew),"bob");
3225 gtk_signal_connect( GTK_OBJECT( filew ), "destroy",
3226 (GtkSignalFunc) destroy, &filew );
3227 /* Connect the ok_button to file_ok_sel function */
3228 gtk_signal_connect( GTK_OBJECT( GTK_FILE_SELECTION( filew )->ok_button ),
3229 "clicked", (GtkSignalFunc) file_ok_sel, filew );
3231 /* Connect the cancel_button to destroy the widget */
3232 gtk_signal_connect_object( GTK_OBJECT( GTK_FILE_SELECTION
3233 ( filew )->cancel_button ),
3234 "clicked", (GtkSignalFunc) gtk_widget_destroy,
3235 GTK_OBJECT( filew ) );
3238 gtk_widget_show( filew );
3241 g_print("%d",gtk_file_selection_match_mask("mask.c","m*.c"));
3242 g_print("%d",gtk_file_selection_match_mask("mask.c","m???.c"));
3243 g_print("%d",gtk_file_selection_match_mask("mask.c","m??*.c"));
3244 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c"));
3245 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c???"));
3246 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c*"));
3247 g_print("%d",gtk_file_selection_match_mask("mask.cout","n*.c???"));
3248 g_print("%d",gtk_file_selection_match_mask("mask.c","[mn]*"));
3249 g_print("%d",gtk_file_selection_match_mask("COPYING","*.xpm"));