1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS
22 * file for a list of people on the GTK+ Team. See the ChangeLog
23 * files for a list of changes. These files are distributed with
24 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
28 // leo FIXME: if we keep this file then we'll need to ask permission to the author, this is LGPL
29 // This file is from the Advanced File Selector widget
30 // by Michael Torrie <torriem@byu.edu>
31 // http://students.cs.byu.edu/~torriem/gtk/
33 // common files win32/linux
35 #include <sys/types.h>
42 // NOTE: the mkdir stuff etc. is in <direct.h> .. but I don't know what's the best strategy yet.
43 // just including <direct.h> here doesn't cut it
45 #if defined ( __linux__ ) || ( __APPLE__ )
46 #include <sys/param.h>
54 #include "gdk/gdkkeysyms.h"
55 #include "gtk/gtkbutton.h"
56 #include "gtk/gtkentry.h"
57 #include "gtkfilesel-darwin.h"
58 #include "gtk/gtkhbox.h"
59 #include "gtk/gtkhbbox.h"
60 #include "gtk/gtklabel.h"
61 #include "gtk/gtklist.h"
62 #include "gtk/gtklistitem.h"
63 #include "gtk/gtkmain.h"
64 #include "gtk/gtkscrolledwindow.h"
65 #include "gtk/gtksignal.h"
66 #include "gtk/gtkvbox.h"
67 #include "gtk/gtkmenu.h"
68 #include "gtk/gtkmenuitem.h"
69 #include "gtk/gtkoptionmenu.h"
70 #include "gtk/gtkclist.h"
71 #include "gtk/gtkdialog.h"
72 #include "gtk/gtkcombo.h"
73 #include "gtk/gtkframe.h"
76 //#include "gtk/gtkintl.h"
77 #define _( String ) ( String )
79 #define DIR_LIST_WIDTH 180
80 #define DIR_LIST_HEIGHT 180
81 #define FILE_LIST_WIDTH 180
82 #define FILE_LIST_HEIGHT 180
84 /* I've put this here so it doesn't get confused with the
85 * file completion interface */
86 typedef struct _HistoryCallbackArg HistoryCallbackArg;
88 struct _HistoryCallbackArg
95 typedef struct _CompletionState CompletionState;
96 typedef struct _CompletionDir CompletionDir;
97 typedef struct _CompletionDirSent CompletionDirSent;
98 typedef struct _CompletionDirEntry CompletionDirEntry;
99 typedef struct _CompletionUserDir CompletionUserDir;
100 typedef struct _PossibleCompletion PossibleCompletion;
102 /* Non-external file completion decls and structures */
104 /* A contant telling PRCS how many directories to cache. Its actually
105 * kept in a list, so the geometry isn't important. */
106 #define CMPL_DIRECTORY_CACHE_SIZE 10
108 /* A constant used to determine whether a substring was an exact
109 * match by first_diff_index()
111 #define PATTERN_MATCH -1
112 /* The arguments used by all fnmatch() calls below
114 #define FNMATCH_FLAGS ( FNM_PATHNAME | FNM_PERIOD )
116 #define CMPL_ERRNO_TOO_LONG ( ( 1 << 16 ) - 1 )
118 /* This structure contains all the useful information about a directory
119 * for the purposes of filename completion. These structures are cached
120 * in the CompletionState struct. CompletionDir's are reference counted.
122 struct _CompletionDirSent
129 gchar *name_buffer; /* memory segment containing names of all entries */
131 struct _CompletionDirEntry *entries;
134 struct _CompletionDir
136 CompletionDirSent *sent;
141 struct _CompletionDir *cmpl_parent;
146 /* This structure contains pairs of directory entry names with a flag saying
147 * whether or not they are a valid directory. NOTE: This information is used
148 * to provide the caller with information about whether to update its completions
149 * or try to open a file. Since directories are cached by the directory mtime,
150 * a symlink which points to an invalid file (which will not be a directory),
151 * will not be reevaluated if that file is created, unless the containing
152 * directory is touched. I consider this case to be worth ignoring (josh).
154 struct _CompletionDirEntry
160 struct _CompletionUserDir
166 struct _PossibleCompletion
168 /* accessible fields, all are accessed externally by functions
172 gint is_a_completion;
184 struct _CompletionState
186 gint last_valid_char;
188 gint updated_text_len;
189 gint updated_text_alloc;
192 gchar *user_dir_name_buffer;
193 gint user_directories_len;
195 gchar *last_completion_text;
197 gint user_completion_index; /* if >= 0, currently completing ~user */
199 struct _CompletionDir *completion_dir; /* directory completing from */
200 struct _CompletionDir *active_completion_dir;
202 struct _PossibleCompletion the_completion;
204 struct _CompletionDir *reference_dir; /* initial directory */
206 GList* directory_storage;
207 GList* directory_sent_storage;
209 struct _CompletionUserDir *user_directories;
213 /* File completion functions which would be external, were they used
214 * outside of this file.
217 static CompletionState* cmpl_init_state( void );
218 static void cmpl_free_state( CompletionState *cmpl_state );
219 static gint cmpl_state_okay( CompletionState* cmpl_state );
220 static gchar* cmpl_strerror( gint );
222 static PossibleCompletion* cmpl_completion_matches( gchar *text_to_complete,
223 gchar **remaining_text,
224 CompletionState *cmpl_state );
226 /* Returns a name for consideration, possibly a completion, this name
227 * will be invalid after the next call to cmpl_next_completion.
229 static char* cmpl_this_completion( PossibleCompletion* );
231 /* True if this completion matches the given text. Otherwise, this
232 * output can be used to have a list of non-completions.
234 static gint cmpl_is_a_completion( PossibleCompletion* );
236 /* True if the completion is a directory
238 static gint cmpl_is_directory( PossibleCompletion* );
240 /* Obtains the next completion, or NULL
242 static PossibleCompletion* cmpl_next_completion( CompletionState* );
244 /* Updating completions: the return value of cmpl_updated_text() will
245 * be text_to_complete completed as much as possible after the most
246 * recent call to cmpl_completion_matches. For the present
247 * application, this is the suggested replacement for the user's input
248 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
251 static gchar* cmpl_updated_text( CompletionState* cmpl_state );
253 /* After updating, to see if the completion was a directory, call
254 * this. If it was, you should consider re-calling completion_matches.
256 static gint cmpl_updated_dir( CompletionState* cmpl_state );
258 /* Current location: if using file completion, return the current
259 * directory, from which file completion begins. More specifically,
260 * the cwd concatenated with all exact completions up to the last
261 * directory delimiter('/').
263 static gchar* cmpl_reference_position( CompletionState* cmpl_state );
265 /* backing up: if cmpl_completion_matches returns NULL, you may query
266 * the index of the last completable character into cmpl_updated_text.
268 static gint cmpl_last_valid_char( CompletionState* cmpl_state );
270 /* When the user selects a non-directory, call cmpl_completion_fullname
271 * to get the full name of the selected file.
273 static gchar* cmpl_completion_fullname( gchar*, CompletionState* cmpl_state );
276 /* Directory operations. */
277 static CompletionDir* open_ref_dir( gchar* text_to_complete,
278 gchar** remaining_text,
279 CompletionState* cmpl_state );
280 static gboolean check_dir( gchar *dir_name,
282 gboolean *stat_subdirs );
283 static CompletionDir* open_dir( gchar* dir_name,
284 CompletionState* cmpl_state );
285 static CompletionDir* open_user_dir( gchar* text_to_complete,
286 CompletionState *cmpl_state );
287 static CompletionDir* open_relative_dir( gchar* dir_name, CompletionDir* dir,
288 CompletionState *cmpl_state );
289 static CompletionDirSent* open_new_dir( gchar* dir_name,
291 gboolean stat_subdirs );
292 static gint correct_dir_fullname( CompletionDir* cmpl_dir );
293 static gint correct_parent( CompletionDir* cmpl_dir,
295 static gchar* find_parent_dir_fullname( gchar* dirname );
296 static CompletionDir* attach_dir( CompletionDirSent* sent,
298 CompletionState *cmpl_state );
299 static void free_dir_sent( CompletionDirSent* sent );
300 static void free_dir( CompletionDir *dir );
301 static void prune_memory_usage( CompletionState *cmpl_state );
303 /* Completion operations */
304 static PossibleCompletion* attempt_homedir_completion( gchar* text_to_complete,
305 CompletionState *cmpl_state );
306 static PossibleCompletion* attempt_file_completion( CompletionState *cmpl_state );
307 static CompletionDir* find_completion_dir( gchar* text_to_complete,
308 gchar** remaining_text,
309 CompletionState* cmpl_state );
310 static PossibleCompletion* append_completion_text( gchar* text,
311 CompletionState* cmpl_state );
312 static gint get_pwdb( CompletionState* cmpl_state );
313 static gint first_diff_index( gchar* pat, gchar* text );
314 static gint compare_user_dir( const void* a, const void* b );
315 static gint compare_cmpl_dir( const void* a, const void* b );
316 static void update_cmpl( PossibleCompletion* poss,
317 CompletionState* cmpl_state );
319 static void gtk_file_selection_class_init( GtkFileSelectionClass *klass );
320 static void gtk_file_selection_init( GtkFileSelection *filesel );
321 static void gtk_file_selection_destroy( GtkObject *object );
322 static gint gtk_file_selection_key_press( GtkWidget *widget,
324 gpointer user_data );
326 static void gtk_file_selection_file_button( GtkWidget *widget,
329 GdkEventButton *bevent,
330 gpointer user_data );
332 static void gtk_file_selection_dir_button( GtkWidget *widget,
335 GdkEventButton *bevent,
338 static void gtk_file_selection_undir_button( GtkWidget *widget,
341 GdkEventButton *bevent,
344 static void gtk_file_selection_populate( GtkFileSelection *fs,
347 static void gtk_file_selection_abort( GtkFileSelection *fs );
349 static void gtk_file_selection_update_history_menu( GtkFileSelection *fs,
350 gchar *current_dir );
352 static void gtk_file_selection_create_dir( GtkWidget *widget, gpointer data );
353 static void gtk_file_selection_delete_file( GtkWidget *widget, gpointer data );
354 static void gtk_file_selection_rename_file( GtkWidget *widget, gpointer data );
356 static gboolean gtk_file_selection_history_combo_callback( GtkWidget *widget, GdkEventKey *event, gpointer data );
357 static gboolean gtk_file_selection_history_combo_list_key_handler( GtkWidget *widget,
359 gpointer user_data );
360 static gboolean gtk_file_selection_history_combo_list_callback( GtkWidget *thelist,
361 GdkEventButton *event,
362 gpointer user_data );
363 static void gtk_file_selection_mask_entry_callback( GtkWidget *widget, gpointer data );
364 static void gtk_file_selection_create_dir( GtkWidget *widget, gpointer data );
365 static void gtk_file_selection_delete_file( GtkWidget *widget, gpointer data );
366 static void gtk_file_selection_rename_file( GtkWidget *widget, gpointer data );
367 static void gtk_file_selection_home_button( GtkWidget *widget, gpointer data );
368 static void gtk_file_selection_up_button( GtkWidget *widget, gpointer data );
369 static void gtk_file_selection_prev_button( GtkWidget *widget, gpointer data );
370 static void gtk_file_selection_next_button( GtkWidget *widget, gpointer data );
371 static void gtk_file_selection_refresh_button( GtkWidget *widget, gpointer data );
373 static gint gtk_file_selection_match_char( gchar, gchar *mask );
374 static gint gtk_file_selection_match_mask( gchar *,gchar * );
377 static GtkWindowClass *parent_class = NULL;
379 /* Saves errno when something cmpl does fails. */
380 static gint cmpl_errno;
383 void gtk_file_selection_clear_masks( GtkFileSelection *filesel ){
386 g_return_if_fail( filesel != NULL );
387 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
389 list = filesel->masks;
392 g_free( list->data );
395 filesel->masks = NULL;
397 gtk_list_clear_items( GTK_LIST( GTK_COMBO( filesel->mask_entry )->list ), 0, -1 );
400 void gtk_file_selection_set_masks( GtkFileSelection *filesel, const gchar **masks ){
401 g_return_if_fail( filesel != NULL );
402 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
406 filesel->masks = g_list_append( filesel->masks, ( gpointer ) * masks );
410 if ( filesel->masks ) {
411 gtk_combo_set_popdown_strings( GTK_COMBO( filesel->mask_entry ), filesel->masks );
417 * Make prev and next inactive if their respective *
418 * histories are empty.
419 * Add facilities for handling hidden files and *
421 * Add an api to access the mask, and hidden files *
422 * check box? (prob not in 1.2.x series) *
425 /* Routine for applying mask to filenames *
426 * Need to be optimized to minimize recursion *
427 * help the for loop by looking for the next *
428 * instance of the mask character following *
429 * the '*'. ei *.c -- look for '.' *
430 * Also, swap all *? pairs (-> ?*), as that *
431 * will make it possible to look ahead (? *
432 * makes it very nondeterministic as in *?.c *
433 * which really is ?*.c *
434 * Allow multiply masks, separted by commas *
435 * Allow more flexible [] handling (ie [a-zA-Z] *
438 static gint gtk_file_selection_match_char( gchar text, gchar *mask ){
443 if ( mask[0] == '[' ) {
444 if ( !strchr( mask,']' ) ) {
447 maskc = g_strdup( mask + 1 ); /* get the portion of mask inside []*/
449 ( *( strchr( maskc,']' ) ) ) = 0;
450 s = strlen( (char *)maskc );
452 for ( x = 0; x < s; x++ ) {
453 if ( text == maskc[x] ) {
462 if ( mask[0] == '?' ) {
465 if ( mask[0] == text ) {
473 static gint gtk_file_selection_match_mask( gchar *text, gchar *mask ){
480 if ( mask[0] == 0 && text[0] == 0 ) {
484 if ( mask[0] == '*' ) {
485 for ( tc = 0; tc <= strlen( text ); tc++ )
487 if ( gtk_file_selection_match_mask( text + tc, mask + 1 ) ) {
493 mc = gtk_file_selection_match_char( text[0], mask );
496 return gtk_file_selection_match_mask( text + 1, mask + mc );
504 gtk_file_selection_get_type( void ){
505 static GtkType file_selection_type = 0;
507 if ( !file_selection_type ) {
508 static const GtkTypeInfo filesel_info =
511 sizeof( GtkFileSelection ),
512 sizeof( GtkFileSelectionClass ),
513 (GtkClassInitFunc) gtk_file_selection_class_init,
514 (GtkObjectInitFunc) gtk_file_selection_init,
515 /* reserved_1 */ NULL,
516 /* reserved_2 */ NULL,
517 (GtkClassInitFunc) NULL,
520 file_selection_type = gtk_type_unique( GTK_TYPE_WINDOW, &filesel_info );
523 return file_selection_type;
527 gtk_file_selection_class_init( GtkFileSelectionClass *klass ){ //tigital
528 GtkObjectClass *object_class;
530 object_class = (GtkObjectClass*) klass;
532 parent_class = gtk_type_class( GTK_TYPE_WINDOW );
534 object_class->destroy = gtk_file_selection_destroy;
538 gtk_file_selection_init( GtkFileSelection *filesel ){
539 GtkWidget *entry_vbox;
541 GtkWidget *list_hbox;
542 GtkWidget *confirm_area;
545 GtkWidget *pulldown_hbox;
546 GtkWidget *scrolled_win;
547 GtkWidget *mask_label;
549 GtkWidget *label_lookingin;
550 GtkWidget *up_button;
551 GtkWidget *home_button;
552 GtkWidget *prev_button;
553 GtkWidget *next_button;
554 GtkWidget *refresh_button;
557 char *file_title [2];
559 filesel->cmpl_state = cmpl_init_state();
561 filesel->mask = NULL;
562 filesel->prev_history = NULL;
563 filesel->next_history = NULL;
564 filesel->saved_entry = NULL;
566 /* The dialog-sized vertical box */
567 filesel->main_vbox = gtk_vbox_new( FALSE, 10 );
568 gtk_container_set_border_width( GTK_CONTAINER( filesel ), 10 );
569 gtk_container_add( GTK_CONTAINER( filesel ), filesel->main_vbox );
570 gtk_widget_show( filesel->main_vbox );
572 /* The horizontal box containing create, rename etc. buttons */
573 filesel->button_area = gtk_hbutton_box_new();
574 gtk_button_box_set_layout( GTK_BUTTON_BOX( filesel->button_area ), GTK_BUTTONBOX_START );
575 gtk_button_box_set_spacing( GTK_BUTTON_BOX( filesel->button_area ), 0 );
576 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), filesel->button_area,
578 gtk_widget_show( filesel->button_area );
580 gtk_file_selection_show_fileop_buttons( filesel );
582 /* hbox for pulldown menu */
583 pulldown_hbox = gtk_hbox_new( FALSE, 5 );
584 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), pulldown_hbox, FALSE, FALSE, 0 );
585 gtk_widget_show( pulldown_hbox );
587 /* The combo box that replaces the pulldown menu */
588 label_lookingin = gtk_label_new( _( "Looking in:" ) );
589 gtk_widget_show( label_lookingin );
590 gtk_box_pack_start( GTK_BOX( pulldown_hbox ), label_lookingin, FALSE, FALSE, 0 );
592 filesel->history_combo = gtk_combo_new();
593 gtk_widget_show( filesel->history_combo );
594 gtk_combo_set_value_in_list( GTK_COMBO( filesel->history_combo ),FALSE,FALSE );
595 gtk_box_pack_start( GTK_BOX( pulldown_hbox ),filesel->history_combo,
597 gtk_signal_connect( GTK_OBJECT( ( (GtkCombo *)filesel->history_combo )->entry ),"key-press-event",
598 (GtkSignalFunc) gtk_file_selection_history_combo_callback,
599 (gpointer) filesel );
601 gtk_signal_connect( GTK_OBJECT( ( (GtkCombo *)filesel->history_combo )->list ),"button-press-event",
602 (GtkSignalFunc) gtk_file_selection_history_combo_list_callback,
603 (gpointer) filesel );
605 gtk_signal_connect( GTK_OBJECT( ( (GtkCombo *)filesel->history_combo )->list ),"key-press-event",
606 (GtkSignalFunc) gtk_file_selection_history_combo_list_key_handler,
607 (gpointer) filesel );
609 /* frame to put the following hbox in */
610 bigframe = gtk_frame_new( NULL );
611 gtk_widget_show( bigframe );
612 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), bigframe, TRUE, TRUE, 0 );
614 /* The horizontal box containing the directory and file listboxes */
615 list_hbox = gtk_hbox_new( FALSE, 5 );
616 gtk_container_add( GTK_CONTAINER( bigframe ), list_hbox );
617 gtk_container_set_border_width( GTK_CONTAINER( list_hbox ), 5 );
618 gtk_widget_show( list_hbox );
620 /* vbox to put the buttons and directory listing in */
621 vbox = gtk_vbox_new( FALSE, 0 );
622 gtk_widget_show( vbox );
623 gtk_box_pack_start( GTK_BOX( list_hbox ), vbox, FALSE, FALSE, 0 );
625 hbox = gtk_hbox_new( FALSE, 0 );
626 gtk_widget_show( hbox );
627 gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
629 home_button = gtk_button_new_with_label( _( "Home" ) );
630 gtk_widget_show( home_button );
631 gtk_signal_connect( GTK_OBJECT( home_button ), "clicked",
632 (GtkSignalFunc) gtk_file_selection_home_button,
633 (gpointer) filesel );
634 gtk_box_pack_start( GTK_BOX( hbox ), home_button, TRUE,TRUE, 0 );
636 prev_button = gtk_button_new_with_label( _( "Prev" ) );
637 gtk_signal_connect( GTK_OBJECT( prev_button ), "clicked",
638 (GtkSignalFunc) gtk_file_selection_prev_button,
639 (gpointer) filesel );
640 gtk_widget_show( prev_button );
641 gtk_box_pack_start( GTK_BOX( hbox ), prev_button, TRUE,TRUE, 0 );
643 up_button = gtk_button_new_with_label( _( "Up" ) );
644 gtk_signal_connect( GTK_OBJECT( up_button ), "clicked",
645 (GtkSignalFunc) gtk_file_selection_up_button,
646 (gpointer) filesel );
647 gtk_widget_show( up_button );
648 gtk_box_pack_start( GTK_BOX( hbox ), up_button, TRUE,TRUE, 0 );
650 next_button = gtk_button_new_with_label( _( "Next" ) );
651 gtk_widget_show( next_button );
652 gtk_signal_connect( GTK_OBJECT( next_button ), "clicked",
653 (GtkSignalFunc) gtk_file_selection_next_button,
654 (gpointer) filesel );
655 gtk_box_pack_start( GTK_BOX( hbox ), next_button, TRUE,TRUE, 0 );
657 refresh_button = gtk_button_new_with_label( _( "Refresh" ) );
658 gtk_widget_show( refresh_button );
659 gtk_signal_connect( GTK_OBJECT( refresh_button ), "clicked",
660 (GtkSignalFunc) gtk_file_selection_refresh_button,
661 (gpointer) filesel );
662 gtk_box_pack_start( GTK_BOX( hbox ), refresh_button, TRUE, TRUE, 0 );
664 /* The directories clist */
665 dir_title[0] = _( "Directories" );
667 filesel->dir_list = gtk_clist_new_with_titles( 1, (gchar**) dir_title );
668 gtk_widget_set_usize( filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT );
669 gtk_signal_connect( GTK_OBJECT( filesel->dir_list ), "select_row",
670 (GtkSignalFunc) gtk_file_selection_dir_button,
671 (gpointer) filesel );
672 gtk_signal_connect( GTK_OBJECT( filesel->dir_list ), "unselect_row",
673 (GtkSignalFunc) gtk_file_selection_undir_button,
674 (gpointer) filesel );
675 gtk_clist_column_titles_passive( GTK_CLIST( filesel->dir_list ) );
677 scrolled_win = gtk_scrolled_window_new( NULL, NULL );
678 gtk_container_add( GTK_CONTAINER( scrolled_win ), filesel->dir_list );
679 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ),
680 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
681 gtk_box_pack_start( GTK_BOX( vbox ), scrolled_win, TRUE,TRUE, 5 );
682 gtk_widget_show( filesel->dir_list );
683 gtk_widget_show( scrolled_win );
685 /* vbox area for mask entry and files clist */
686 vbox = gtk_vbox_new( FALSE, 0 );
687 gtk_widget_show( vbox );
688 gtk_box_pack_start( GTK_BOX( list_hbox ), vbox, TRUE, TRUE, 0 );
690 hbox = gtk_hbox_new( FALSE, 5 );
691 gtk_widget_show( hbox );
692 gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
694 mask_label = gtk_label_new( _( "Mask:" ) );
695 gtk_widget_show( mask_label );
696 gtk_box_pack_start( GTK_BOX( hbox ), mask_label, FALSE, FALSE, 0 );
698 filesel->mask_entry = gtk_entry_new();
699 gtk_widget_show( filesel->mask_entry );
700 gtk_signal_connect( GTK_OBJECT( filesel->mask_entry ),"activate",
701 (GtkSignalFunc) gtk_file_selection_mask_entry_callback,
702 (gpointer) filesel );
703 gtk_box_pack_start( GTK_BOX( hbox ),filesel->mask_entry, TRUE, TRUE, 0 );
706 /* The files clist */
707 file_title[0] = _( "Files" );
708 file_title[1] = NULL;
709 filesel->file_list = gtk_clist_new_with_titles( 1, (gchar**) file_title );
710 gtk_widget_set_usize( filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT );
711 gtk_signal_connect( GTK_OBJECT( filesel->file_list ), "select_row",
712 (GtkSignalFunc) gtk_file_selection_file_button,
713 (gpointer) filesel );
714 gtk_clist_column_titles_passive( GTK_CLIST( filesel->file_list ) );
716 scrolled_win = gtk_scrolled_window_new( NULL, NULL );
717 gtk_container_add( GTK_CONTAINER( scrolled_win ), filesel->file_list );
718 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_win ),
719 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
720 gtk_box_pack_start( GTK_BOX( vbox ), scrolled_win, TRUE, TRUE, 5 );
721 gtk_widget_show( filesel->file_list );
722 gtk_widget_show( scrolled_win );
724 /* action area for packing buttons into. */
725 filesel->action_area = gtk_hbox_new( TRUE, 0 );
726 gtk_box_pack_start( GTK_BOX( filesel->main_vbox ), filesel->action_area,
728 gtk_widget_show( filesel->action_area );
730 /* The OK/Cancel button area */
731 confirm_area = gtk_hbutton_box_new();
732 gtk_button_box_set_layout( GTK_BUTTON_BOX( confirm_area ), GTK_BUTTONBOX_END );
733 gtk_button_box_set_spacing( GTK_BUTTON_BOX( confirm_area ), 5 );
734 gtk_box_pack_end( GTK_BOX( filesel->main_vbox ), confirm_area, FALSE, FALSE, 0 );
735 gtk_widget_show( confirm_area );
738 filesel->ok_button = gtk_button_new_with_label( _( "OK" ) );
739 GTK_WIDGET_SET_FLAGS( filesel->ok_button, GTK_CAN_DEFAULT );
740 gtk_box_pack_start( GTK_BOX( confirm_area ), filesel->ok_button, TRUE, TRUE, 0 );
741 gtk_widget_grab_default( filesel->ok_button );
742 gtk_widget_show( filesel->ok_button );
744 /* The Cancel button */
745 filesel->cancel_button = gtk_button_new_with_label( _( "Cancel" ) );
746 GTK_WIDGET_SET_FLAGS( filesel->cancel_button, GTK_CAN_DEFAULT );
747 gtk_box_pack_start( GTK_BOX( confirm_area ), filesel->cancel_button, TRUE, TRUE, 0 );
748 gtk_widget_show( filesel->cancel_button );
750 /* The selection entry widget */
751 entry_vbox = gtk_vbox_new( FALSE, 2 );
752 gtk_box_pack_end( GTK_BOX( filesel->main_vbox ), entry_vbox, FALSE, FALSE, 0 );
753 gtk_widget_show( entry_vbox );
755 filesel->selection_text = label = gtk_label_new( "" );
756 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
757 gtk_box_pack_start( GTK_BOX( entry_vbox ), label, FALSE, FALSE, 0 );
758 gtk_widget_show( label );
760 filesel->selection_entry = gtk_entry_new();
761 gtk_signal_connect( GTK_OBJECT( filesel->selection_entry ), "key_press_event",
762 (GtkSignalFunc) gtk_file_selection_key_press, filesel );
763 gtk_signal_connect_object( GTK_OBJECT( filesel->selection_entry ), "focus_in_event",
764 (GtkSignalFunc) gtk_widget_grab_default,
765 GTK_OBJECT( filesel->ok_button ) );
766 gtk_signal_connect_object( GTK_OBJECT( filesel->selection_entry ), "activate",
767 (GtkSignalFunc) gtk_button_clicked,
768 GTK_OBJECT( filesel->ok_button ) );
769 gtk_box_pack_start( GTK_BOX( entry_vbox ), filesel->selection_entry, TRUE, TRUE, 0 );
770 gtk_widget_show( filesel->selection_entry );
772 if ( !cmpl_state_okay( filesel->cmpl_state ) ) {
775 sprintf( err_buf, _( "Directory unreadable: %s" ), cmpl_strerror( cmpl_errno ) );
777 gtk_label_set_text( GTK_LABEL( filesel->selection_text ), err_buf );
781 gtk_file_selection_populate( filesel, "", FALSE );
784 gtk_widget_grab_focus( filesel->selection_entry );
788 gtk_file_selection_new( const gchar *title ){
789 GtkFileSelection *filesel;
791 filesel = gtk_type_new( GTK_TYPE_FILE_SELECTION );
792 gtk_window_set_title( GTK_WINDOW( filesel ), title );
794 return GTK_WIDGET( filesel );
798 gtk_file_selection_show_fileop_buttons( GtkFileSelection *filesel ){
799 g_return_if_fail( filesel != NULL );
800 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
802 /* delete, create directory, and rename */
803 if ( !filesel->fileop_c_dir ) {
804 filesel->fileop_c_dir = gtk_button_new_with_label( _( "Create Dir" ) );
805 gtk_signal_connect( GTK_OBJECT( filesel->fileop_c_dir ), "clicked",
806 (GtkSignalFunc) gtk_file_selection_create_dir,
807 (gpointer) filesel );
808 gtk_box_pack_start( GTK_BOX( filesel->button_area ),
809 filesel->fileop_c_dir, TRUE, TRUE, 0 );
810 gtk_widget_show( filesel->fileop_c_dir );
813 if ( !filesel->fileop_del_file ) {
814 filesel->fileop_del_file = gtk_button_new_with_label( _( "Delete File" ) );
815 gtk_signal_connect( GTK_OBJECT( filesel->fileop_del_file ), "clicked",
816 (GtkSignalFunc) gtk_file_selection_delete_file,
817 (gpointer) filesel );
818 gtk_box_pack_start( GTK_BOX( filesel->button_area ),
819 filesel->fileop_del_file, TRUE, TRUE, 0 );
820 gtk_widget_show( filesel->fileop_del_file );
823 if ( !filesel->fileop_ren_file ) {
824 filesel->fileop_ren_file = gtk_button_new_with_label( _( "Rename File" ) );
825 gtk_signal_connect( GTK_OBJECT( filesel->fileop_ren_file ), "clicked",
826 (GtkSignalFunc) gtk_file_selection_rename_file,
827 (gpointer) filesel );
828 gtk_box_pack_start( GTK_BOX( filesel->button_area ),
829 filesel->fileop_ren_file, TRUE, TRUE, 0 );
830 gtk_widget_show( filesel->fileop_ren_file );
833 gtk_widget_queue_resize( GTK_WIDGET( filesel ) );
837 gtk_file_selection_hide_fileop_buttons( GtkFileSelection *filesel ){
838 g_return_if_fail( filesel != NULL );
839 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
841 if ( filesel->fileop_ren_file ) {
842 gtk_widget_destroy( filesel->fileop_ren_file );
843 filesel->fileop_ren_file = NULL;
846 if ( filesel->fileop_del_file ) {
847 gtk_widget_destroy( filesel->fileop_del_file );
848 filesel->fileop_del_file = NULL;
851 if ( filesel->fileop_c_dir ) {
852 gtk_widget_destroy( filesel->fileop_c_dir );
853 filesel->fileop_c_dir = NULL;
860 gtk_file_selection_set_filename( GtkFileSelection *filesel,
861 const gchar *filename ){
862 char buf[MAXPATHLEN];
863 const char *name, *last_slash;
865 g_return_if_fail( filesel != NULL );
866 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
867 g_return_if_fail( filename != NULL );
869 last_slash = strrchr( filename, '/' );
877 gint len = MIN( MAXPATHLEN - 1, last_slash - filename + 1 );
879 strncpy( buf, filename, len );
882 name = last_slash + 1;
885 gtk_file_selection_populate( filesel, buf, FALSE );
887 if ( filesel->selection_entry ) {
888 gtk_entry_set_text( GTK_ENTRY( filesel->selection_entry ), name );
893 gtk_file_selection_get_filename( GtkFileSelection *filesel ){
894 static char nothing[2] = "";
898 g_return_val_if_fail( filesel != NULL, nothing );
899 g_return_val_if_fail( GTK_IS_FILE_SELECTION( filesel ), nothing );
901 text = gtk_entry_get_text( GTK_ENTRY( filesel->selection_entry ) );
903 filename = cmpl_completion_fullname( text, filesel->cmpl_state );
911 gtk_file_selection_complete( GtkFileSelection *filesel,
912 const gchar *pattern ){
916 g_return_if_fail( filesel != NULL );
917 g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
918 g_return_if_fail( pattern != NULL );
920 if ( filesel->selection_entry ) {
921 gtk_entry_set_text( GTK_ENTRY( filesel->selection_entry ), pattern );
924 if ( strchr( pattern,'*' ) || strchr( pattern,'?' ) ) {
925 for ( x = strlen( pattern ); x >= 0; x-- )
927 if ( pattern[x] == '/' ) {
931 gtk_entry_set_text( GTK_ENTRY( filesel->mask_entry ),g_strdup( pattern + x + 1 ) );
933 if ( filesel->mask ) {
934 g_free( filesel->mask );
937 filesel->mask = g_strdup( pattern + x + 1 );
938 new_pattern = g_strdup( pattern );
939 new_pattern[x + 1] = 0;
940 gtk_file_selection_populate( filesel, (gchar*) new_pattern, TRUE );
941 g_free( new_pattern );
945 gtk_file_selection_populate( filesel, (gchar*) pattern, TRUE );
950 gtk_file_selection_destroy( GtkObject *object ){
951 GtkFileSelection *filesel;
954 g_return_if_fail( object != NULL );
955 g_return_if_fail( GTK_IS_FILE_SELECTION( object ) );
957 filesel = GTK_FILE_SELECTION( object );
959 if ( filesel->fileop_dialog ) {
960 gtk_widget_destroy( filesel->fileop_dialog );
963 if ( filesel->next_history ) {
964 list = filesel->next_history;
967 g_free( list->data );
971 g_list_free( filesel->next_history );
972 filesel->next_history = NULL;
974 if ( filesel->prev_history ) {
975 list = filesel->prev_history;
978 g_free( list->data );
982 g_list_free( filesel->prev_history );
983 filesel->prev_history = NULL;
985 if ( filesel->mask ) {
986 g_free( filesel->mask );
987 filesel->mask = NULL;
990 cmpl_free_state( filesel->cmpl_state );
991 filesel->cmpl_state = NULL;
993 if ( GTK_OBJECT_CLASS( parent_class )->destroy ) {
994 ( *GTK_OBJECT_CLASS( parent_class )->destroy )( object );
998 /* Begin file operations callbacks */
1001 gtk_file_selection_fileop_error( GtkFileSelection *fs, gchar *error_message ){
1007 g_return_if_fail( error_message != NULL );
1010 dialog = gtk_dialog_new();
1012 gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1013 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1016 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Error" ) );
1017 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1019 /* If file dialog is grabbed, make this dialog modal too */
1020 /* When error dialog is closed, file dialog will be grabbed again */
1021 if ( GTK_WINDOW( fs )->modal ) {
1022 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1025 vbox = gtk_vbox_new( FALSE, 0 );
1026 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1027 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1029 gtk_widget_show( vbox );
1031 label = gtk_label_new( error_message );
1032 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1033 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1034 gtk_widget_show( label );
1036 /* yes, we free it */
1037 g_free( error_message );
1040 button = gtk_button_new_with_label( _( "Close" ) );
1041 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1042 (GtkSignalFunc) gtk_widget_destroy,
1043 (gpointer) dialog );
1044 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1045 button, TRUE, TRUE, 0 );
1046 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1047 gtk_widget_grab_default( button );
1048 gtk_widget_show( button );
1050 gtk_widget_show( dialog );
1054 gtk_file_selection_fileop_destroy( GtkWidget *widget, gpointer data ){
1055 GtkFileSelection *fs = data;
1057 g_return_if_fail( fs != NULL );
1058 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1060 fs->fileop_dialog = NULL;
1065 gtk_file_selection_create_dir_confirmed( GtkWidget *widget, gpointer data ){
1066 GtkFileSelection *fs = data;
1071 CompletionState *cmpl_state;
1073 g_return_if_fail( fs != NULL );
1074 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1076 dirname = gtk_entry_get_text( GTK_ENTRY( fs->fileop_entry ) );
1077 cmpl_state = (CompletionState*) fs->cmpl_state;
1078 path = cmpl_reference_position( cmpl_state );
1080 full_path = g_strconcat( path, "/", dirname, NULL );
1081 if ( ( mkdir( full_path, 0755 ) < 0 ) ) {
1082 buf = g_strconcat( "Error creating directory \"", dirname, "\": ",
1083 g_strerror( errno ), NULL );
1084 gtk_file_selection_fileop_error( fs, buf );
1086 g_free( full_path );
1088 gtk_widget_destroy( fs->fileop_dialog );
1089 gtk_file_selection_populate( fs, "", FALSE );
1093 gtk_file_selection_create_dir( GtkWidget *widget, gpointer data ){
1094 GtkFileSelection *fs = data;
1100 g_return_if_fail( fs != NULL );
1101 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1103 if ( fs->fileop_dialog ) {
1108 fs->fileop_dialog = dialog = gtk_dialog_new();
1109 gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1110 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1112 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Create Directory" ) );
1113 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1115 /* If file dialog is grabbed, grab option dialog */
1116 /* When option dialog is closed, file dialog will be grabbed again */
1117 if ( GTK_WINDOW( fs )->modal ) {
1118 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1121 vbox = gtk_vbox_new( FALSE, 0 );
1122 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1123 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1125 gtk_widget_show( vbox );
1127 label = gtk_label_new( _( "Directory name:" ) );
1128 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1129 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1130 gtk_widget_show( label );
1132 /* The directory entry widget */
1133 fs->fileop_entry = gtk_entry_new();
1134 gtk_box_pack_start( GTK_BOX( vbox ), fs->fileop_entry,
1136 GTK_WIDGET_SET_FLAGS( fs->fileop_entry, GTK_CAN_DEFAULT );
1137 gtk_widget_show( fs->fileop_entry );
1140 button = gtk_button_new_with_label( _( "Create" ) );
1141 gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1142 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
1144 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1145 button, TRUE, TRUE, 0 );
1146 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1147 gtk_widget_show( button );
1149 button = gtk_button_new_with_label( _( "Cancel" ) );
1150 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1151 (GtkSignalFunc) gtk_widget_destroy,
1152 (gpointer) dialog );
1153 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1154 button, TRUE, TRUE, 0 );
1155 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1156 gtk_widget_grab_default( button );
1157 gtk_widget_show( button );
1159 gtk_widget_show( dialog );
1163 gtk_file_selection_delete_file_confirmed( GtkWidget *widget, gpointer data ){
1164 GtkFileSelection *fs = data;
1165 CompletionState *cmpl_state;
1170 g_return_if_fail( fs != NULL );
1171 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1173 cmpl_state = (CompletionState*) fs->cmpl_state;
1174 path = cmpl_reference_position( cmpl_state );
1176 full_path = g_strconcat( path, "/", fs->fileop_file, NULL );
1177 if ( ( unlink( full_path ) < 0 ) ) {
1178 buf = g_strconcat( "Error deleting file \"", fs->fileop_file, "\": ",
1179 g_strerror( errno ), NULL );
1180 gtk_file_selection_fileop_error( fs, buf );
1182 g_free( full_path );
1184 gtk_widget_destroy( fs->fileop_dialog );
1185 gtk_file_selection_populate( fs, "", FALSE );
1189 gtk_file_selection_delete_file( GtkWidget *widget, gpointer data ){
1190 GtkFileSelection *fs = data;
1198 g_return_if_fail( fs != NULL );
1199 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1201 if ( fs->fileop_dialog ) {
1205 filename = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1206 if ( strlen( filename ) < 1 ) {
1210 fs->fileop_file = filename;
1213 fs->fileop_dialog = dialog = gtk_dialog_new();
1214 gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1215 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1217 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Delete File" ) );
1218 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1220 /* If file dialog is grabbed, grab option dialog */
1221 /* When option dialog is closed, file dialog will be grabbed again */
1222 if ( GTK_WINDOW( fs )->modal ) {
1223 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1226 vbox = gtk_vbox_new( FALSE, 0 );
1227 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1228 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1230 gtk_widget_show( vbox );
1232 buf = g_strconcat( "Really delete file \"", filename, "\" ?", NULL );
1233 label = gtk_label_new( buf );
1234 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1235 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1236 gtk_widget_show( label );
1240 button = gtk_button_new_with_label( _( "Delete" ) );
1241 gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1242 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
1244 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1245 button, TRUE, TRUE, 0 );
1246 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1247 gtk_widget_show( button );
1249 button = gtk_button_new_with_label( _( "Cancel" ) );
1250 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1251 (GtkSignalFunc) gtk_widget_destroy,
1252 (gpointer) dialog );
1253 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1254 button, TRUE, TRUE, 0 );
1255 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1256 gtk_widget_grab_default( button );
1257 gtk_widget_show( button );
1259 gtk_widget_show( dialog );
1264 gtk_file_selection_rename_file_confirmed( GtkWidget *widget, gpointer data ){
1265 GtkFileSelection *fs = data;
1269 gchar *new_filename;
1270 gchar *old_filename;
1271 CompletionState *cmpl_state;
1273 g_return_if_fail( fs != NULL );
1274 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1276 file = gtk_entry_get_text( GTK_ENTRY( fs->fileop_entry ) );
1277 cmpl_state = (CompletionState*) fs->cmpl_state;
1278 path = cmpl_reference_position( cmpl_state );
1280 new_filename = g_strconcat( path, "/", file, NULL );
1281 old_filename = g_strconcat( path, "/", fs->fileop_file, NULL );
1283 if ( ( rename( old_filename, new_filename ) ) < 0 ) {
1284 buf = g_strconcat( "Error renaming file \"", file, "\": ",
1285 g_strerror( errno ), NULL );
1286 gtk_file_selection_fileop_error( fs, buf );
1288 g_free( new_filename );
1289 g_free( old_filename );
1291 gtk_widget_destroy( fs->fileop_dialog );
1292 gtk_file_selection_populate( fs, "", FALSE );
1296 gtk_file_selection_rename_file( GtkWidget *widget, gpointer data ){
1297 GtkFileSelection *fs = data;
1304 g_return_if_fail( fs != NULL );
1305 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1307 if ( fs->fileop_dialog ) {
1311 fs->fileop_file = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1312 if ( strlen( fs->fileop_file ) < 1 ) {
1317 fs->fileop_dialog = dialog = gtk_dialog_new();
1318 gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1319 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1321 gtk_window_set_title( GTK_WINDOW( dialog ), _( "Rename File" ) );
1322 gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1324 /* If file dialog is grabbed, grab option dialog */
1325 /* When option dialog closed, file dialog will be grabbed again */
1326 if ( GTK_WINDOW( fs )->modal ) {
1327 gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE );
1330 vbox = gtk_vbox_new( FALSE, 0 );
1331 gtk_container_set_border_width( GTK_CONTAINER( vbox ), 8 );
1332 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->vbox ), vbox,
1334 gtk_widget_show( vbox );
1336 buf = g_strconcat( "Rename file \"", fs->fileop_file, "\" to:", NULL );
1337 label = gtk_label_new( buf );
1338 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.0 );
1339 gtk_box_pack_start( GTK_BOX( vbox ), label, FALSE, FALSE, 5 );
1340 gtk_widget_show( label );
1343 /* New filename entry */
1344 fs->fileop_entry = gtk_entry_new();
1345 gtk_box_pack_start( GTK_BOX( vbox ), fs->fileop_entry,
1347 GTK_WIDGET_SET_FLAGS( fs->fileop_entry, GTK_CAN_DEFAULT );
1348 gtk_widget_show( fs->fileop_entry );
1350 gtk_entry_set_text( GTK_ENTRY( fs->fileop_entry ), fs->fileop_file );
1351 gtk_editable_select_region( GTK_EDITABLE( fs->fileop_entry ),
1352 0, strlen( fs->fileop_file ) );
1355 button = gtk_button_new_with_label( _( "Rename" ) );
1356 gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1357 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
1359 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1360 button, TRUE, TRUE, 0 );
1361 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1362 gtk_widget_show( button );
1364 button = gtk_button_new_with_label( _( "Cancel" ) );
1365 gtk_signal_connect_object( GTK_OBJECT( button ), "clicked",
1366 (GtkSignalFunc) gtk_widget_destroy,
1367 (gpointer) dialog );
1368 gtk_box_pack_start( GTK_BOX( GTK_DIALOG( dialog )->action_area ),
1369 button, TRUE, TRUE, 0 );
1370 GTK_WIDGET_SET_FLAGS( button, GTK_CAN_DEFAULT );
1371 gtk_widget_grab_default( button );
1372 gtk_widget_show( button );
1374 gtk_widget_show( dialog );
1379 gtk_file_selection_key_press( GtkWidget *widget,
1381 gpointer user_data ){
1382 GtkFileSelection *fs;
1385 g_return_val_if_fail( widget != NULL, FALSE );
1386 g_return_val_if_fail( event != NULL, FALSE );
1388 fs = GTK_FILE_SELECTION( user_data );
1390 if ( event->keyval == GDK_Tab ) {
1391 text = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1393 text = g_strdup( text );
1395 gtk_file_selection_populate( fs, text, TRUE );
1399 gtk_signal_emit_stop_by_name( GTK_OBJECT( widget ), "key_press_event" );
1403 if ( fs->saved_entry ) {
1404 gtk_clist_unselect_all( (GtkCList *) ( fs->dir_list ) );
1405 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),fs->saved_entry );
1406 g_free( fs->saved_entry );
1407 fs->saved_entry = NULL;
1415 gtk_file_selection_home_button( GtkWidget *widget, gpointer data ){
1418 GtkFileSelection *fs = data;
1420 list = fs->next_history;
1422 g_free( list->data );
1425 g_list_free( fs->next_history );
1426 fs->next_history = NULL;
1428 gtk_file_selection_populate( fs,"~/",FALSE );
1432 gtk_file_selection_up_button( GtkWidget *widget, gpointer data ){
1433 GtkFileSelection *fs = data;
1436 g_return_if_fail( fs != NULL );
1437 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1439 list = fs->next_history;
1441 g_free( list->data );
1444 g_list_free( fs->next_history );
1445 fs->next_history = NULL;
1447 gtk_file_selection_populate( fs, "../", FALSE ); /*change directories. */
1452 gtk_file_selection_prev_button( GtkWidget *widget, gpointer data ){
1453 GtkFileSelection *fs = data;
1458 g_return_if_fail( fs != NULL );
1459 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1461 list = fs->prev_history;
1463 if ( list && g_list_length( list ) > 1 ) {
1464 first = list; /* get first element */
1465 list = list->next; /* pop off current directory */
1467 list->prev = NULL; /* make this the new head. */
1469 fs->prev_history = list; /* update prev_history list */
1470 fs->next_history = g_list_prepend( fs->next_history,first->data ); /* put it on next_history */
1472 first->next = NULL; /* orphan the old first node */
1473 g_list_free( first ); /* free the node (data is now in use by next_history) */
1477 path = g_malloc( strlen( list->data ) + 4 ); /* plenty of space */
1478 strcpy( path,list->data ); /* get the 2nd path in the history */
1479 strcat( path,"/" ); /* append a '/' */
1480 gtk_file_selection_populate( fs, path, FALSE ); /* change directories. */
1486 gtk_file_selection_next_button( GtkWidget *widget, gpointer data ){
1487 GtkFileSelection *fs = data;
1492 g_return_if_fail( fs != NULL );
1493 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1495 list = fs->next_history;
1497 if ( list && g_list_length( list ) > 0 ) {
1498 first = list; /*get first element*/
1499 list = list->next; /*pop off current directory*/
1505 fs->next_history = list; /*update prev_history list*/
1507 path = g_malloc( strlen( first->data ) + 4 ); /*plenty of space*/
1508 strcpy( path,first->data );
1509 strcat( path,"/" ); /*append a / */
1510 gtk_file_selection_populate( fs, path, FALSE ); /*change directories.*/
1513 first->next = NULL; /* orphan the old first node */
1514 g_list_free( first ); /* free the node (data is now in use by next_history) */
1520 gtk_file_selection_refresh_button( GtkWidget *widget, gpointer data ){
1521 GtkFileSelection *fs = data;
1523 g_return_if_fail( fs != NULL );
1524 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1526 gtk_file_selection_populate( fs,"",FALSE );
1530 gtk_file_selection_mask_entry_callback( GtkWidget *widget, gpointer data ){
1531 GtkFileSelection *fs = data;
1537 fs->mask = g_strdup( gtk_entry_get_text( GTK_ENTRY( fs->mask_entry ) ) );
1539 if ( strlen( fs->mask ) == 0 ) {
1544 gtk_file_selection_refresh_button( widget,data );
1547 static gboolean gtk_file_selection_history_combo_list_key_handler( GtkWidget *widget,
1549 gpointer user_data ){
1551 g_print("Key pressed! \n");
1557 static gboolean gtk_file_selection_history_combo_list_callback( GtkWidget *thelist,
1558 GdkEventButton *event,
1559 gpointer user_data ){
1561 GtkFileSelection *fs = user_data;
1565 list = fs->next_history;
1567 g_free( list->data );
1570 g_list_free( fs->next_history );
1571 fs->next_history = NULL;
1573 path = g_malloc( strlen( gtk_entry_get_text( GTK_ENTRY( ( (GtkCombo *)fs->history_combo )->entry ) ) ) + 4 );
1574 strcpy( path,gtk_entry_get_text( GTK_ENTRY( ( (GtkCombo *)fs->history_combo )->entry ) ) );
1577 gtk_file_selection_populate( fs,path,TRUE );
1585 gtk_file_selection_history_combo_callback( GtkWidget *widget, GdkEventKey *event, gpointer data ){
1586 GtkEntry *entry = (GtkEntry *)widget;
1587 GtkFileSelection *fs = data;
1591 g_return_val_if_fail( fs != NULL,FALSE );
1592 g_return_val_if_fail( GTK_IS_FILE_SELECTION( fs ),FALSE );
1595 if ( event->keyval == GDK_Return ) {
1596 list = fs->next_history;
1598 g_free( list->data );
1601 g_list_free( fs->next_history );
1602 fs->next_history = NULL;
1604 path = g_malloc( strlen( gtk_entry_get_text( entry ) ) + 4 );
1605 strcpy( path,gtk_entry_get_text( entry ) );
1607 gtk_file_selection_populate( fs,path,TRUE );
1609 gtk_signal_emit_stop_by_name( GTK_OBJECT( widget ), "key_press_event" );
1620 gtk_file_selection_update_history_menu( GtkFileSelection *fs,
1621 gchar *current_directory ){
1624 g_return_if_fail( fs != NULL );
1625 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1626 g_return_if_fail( current_directory != NULL );
1628 current_dir = g_strdup( current_directory );
1630 if ( fs->prev_history ) {
1631 if ( strcmp( ( fs->prev_history )->data,current_dir ) ) { /*if this item isn't on the top of the list */
1632 fs->prev_history = g_list_prepend( fs->prev_history,g_strdup( current_dir ) );
1636 fs->prev_history = g_list_prepend( fs->prev_history,g_strdup( current_dir ) );
1639 gtk_combo_set_popdown_strings( GTK_COMBO( fs->history_combo ),fs->prev_history );
1641 g_free( current_dir );
1645 gtk_file_selection_file_button( GtkWidget *widget,
1648 GdkEventButton *bevent,
1649 gpointer user_data ){
1650 GtkFileSelection *fs = NULL;
1651 gchar *filename, *temp = NULL;
1653 g_return_if_fail( GTK_IS_CLIST( widget ) );
1656 g_return_if_fail( fs != NULL );
1657 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1659 gtk_clist_get_text( GTK_CLIST( fs->file_list ), row, 0, &temp );
1660 filename = g_strdup( temp );
1664 switch ( bevent->type )
1666 case GDK_2BUTTON_PRESS:
1667 gtk_button_clicked( GTK_BUTTON( fs->ok_button ) );
1671 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1676 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1684 gtk_file_selection_dir_button( GtkWidget *widget,
1687 GdkEventButton *bevent,
1688 gpointer user_data ){
1690 GtkFileSelection *fs = NULL;
1691 gchar *filename, *temp = NULL;
1693 g_return_if_fail( GTK_IS_CLIST( widget ) );
1695 fs = GTK_FILE_SELECTION( user_data );
1696 g_return_if_fail( fs != NULL );
1697 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1699 gtk_clist_get_text( GTK_CLIST( fs->dir_list ), row, 0, &temp );
1700 filename = g_strdup( temp );
1704 switch ( bevent->type )
1706 case GDK_2BUTTON_PRESS:
1707 list = fs->next_history;
1709 g_free( list->data );
1712 g_list_free( fs->next_history );
1713 fs->next_history = NULL;
1715 gtk_file_selection_populate( fs, filename, FALSE );
1716 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),fs->saved_entry );
1717 g_free( fs->saved_entry );
1718 fs->saved_entry = NULL;
1722 /* here we need to add the "filename" to the beginning of what's already
1723 in the entry. Save what's in the entry, then restore it on the double click
1725 if ( fs->saved_entry ) {
1726 g_free( fs->saved_entry );
1728 fs->saved_entry = g_strdup( gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) ) );
1730 temp = g_strconcat( filename,fs->saved_entry,NULL );
1731 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), temp );
1738 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1746 gtk_file_selection_undir_button( GtkWidget *widget,
1749 GdkEventButton *bevent,
1750 gpointer user_data ){
1751 GtkFileSelection *fs = NULL;
1752 gchar *filename, *temp = NULL;
1754 g_return_if_fail( GTK_IS_CLIST( widget ) );
1756 fs = GTK_FILE_SELECTION( user_data );
1757 g_return_if_fail( fs != NULL );
1758 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1760 gtk_clist_get_text( GTK_CLIST( fs->dir_list ), row, 0, &temp );
1761 filename = g_strdup( temp );
1765 switch ( bevent->type )
1768 /* here we need to add the "filename" to the beginning of what's already
1769 in the entry. Save what's in the entry, then restore it on the double click
1771 if ( fs->saved_entry ) {
1772 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),fs->saved_entry );
1773 g_free( fs->saved_entry );
1774 fs->saved_entry = NULL;
1780 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename ); //?????
1788 gtk_file_selection_populate( GtkFileSelection *fs,
1790 gint try_complete ){
1791 CompletionState *cmpl_state;
1792 PossibleCompletion* poss;
1795 gchar* rem_path = rel_path;
1798 gint did_recurse = FALSE;
1799 gint possible_count = 0;
1800 gint selection_index = -1;
1801 gint file_list_width;
1802 gint dir_list_width;
1804 g_return_if_fail( fs != NULL );
1805 g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1807 cmpl_state = (CompletionState*) fs->cmpl_state;
1808 poss = cmpl_completion_matches( rel_path, &rem_path, cmpl_state );
1810 if ( !cmpl_state_okay( cmpl_state ) ) {
1811 /* Something went wrong. */
1812 gtk_file_selection_abort( fs );
1816 g_assert( cmpl_state->reference_dir );
1818 gtk_clist_freeze( GTK_CLIST( fs->dir_list ) );
1819 gtk_clist_clear( GTK_CLIST( fs->dir_list ) );
1820 gtk_clist_freeze( GTK_CLIST( fs->file_list ) );
1821 gtk_clist_clear( GTK_CLIST( fs->file_list ) );
1823 /* Set the dir_list to include ./ and ../ */
1826 row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1829 row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1831 /*reset the max widths of the lists*/
1832 dir_list_width = gdk_string_width( fs->dir_list->style->font,"../" );
1833 gtk_clist_set_column_width( GTK_CLIST( fs->dir_list ),0,dir_list_width );
1834 file_list_width = 1;
1835 gtk_clist_set_column_width( GTK_CLIST( fs->file_list ),0,file_list_width );
1839 if ( cmpl_is_a_completion( poss ) ) {
1840 possible_count += 1;
1842 filename = cmpl_this_completion( poss );
1846 if ( cmpl_is_directory( poss ) ) {
1847 if ( strcmp( filename, "./" ) != 0 &&
1848 strcmp( filename, "../" ) != 0 ) {
1849 int width = gdk_string_width( fs->dir_list->style->font,
1851 row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1852 if ( width > dir_list_width ) {
1853 dir_list_width = width;
1854 gtk_clist_set_column_width( GTK_CLIST( fs->dir_list ),0,
1862 if ( gtk_file_selection_match_mask( filename,fs->mask ) ) {
1863 int width = gdk_string_width( fs->file_list->style->font,
1865 row = gtk_clist_append( GTK_CLIST( fs->file_list ), text );
1866 if ( width > file_list_width ) {
1867 file_list_width = width;
1868 gtk_clist_set_column_width( GTK_CLIST( fs->file_list ),0,
1875 int width = gdk_string_width( fs->file_list->style->font,
1877 row = gtk_clist_append( GTK_CLIST( fs->file_list ), text );
1878 if ( width > file_list_width ) {
1879 file_list_width = width;
1880 gtk_clist_set_column_width( GTK_CLIST( fs->file_list ),0,
1887 poss = cmpl_next_completion( cmpl_state );
1890 gtk_clist_thaw( GTK_CLIST( fs->dir_list ) );
1891 gtk_clist_thaw( GTK_CLIST( fs->file_list ) );
1893 /* File lists are set. */
1895 g_assert( cmpl_state->reference_dir );
1897 if ( try_complete ) {
1899 /* User is trying to complete filenames, so advance the user's input
1900 * string to the updated_text, which is the common leading substring
1901 * of all possible completions, and if its a directory attempt
1902 * attempt completions in it. */
1904 if ( cmpl_updated_text( cmpl_state )[0] ) {
1906 if ( cmpl_updated_dir( cmpl_state ) ) {
1907 gchar* dir_name = g_strdup( cmpl_updated_text( cmpl_state ) );
1911 gtk_file_selection_populate( fs, dir_name, TRUE );
1917 if ( fs->selection_entry ) {
1918 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),
1919 cmpl_updated_text( cmpl_state ) );
1925 selection_index = cmpl_last_valid_char( cmpl_state ) -
1926 ( strlen( rel_path ) - strlen( rem_path ) );
1927 if ( fs->selection_entry ) {
1928 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), rem_path );
1934 if ( fs->selection_entry ) {
1935 /* Here we need to take the old filename and keep it!*/
1936 /*gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");*/
1941 if ( !did_recurse ) {
1942 if ( fs->selection_entry ) {
1943 gtk_entry_set_position( GTK_ENTRY( fs->selection_entry ), selection_index );
1946 if ( fs->selection_entry ) {
1947 sel_text = g_strconcat( _( "Selection: " ),
1948 cmpl_reference_position( cmpl_state ),
1951 gtk_label_set_text( GTK_LABEL( fs->selection_text ), sel_text );
1955 gtk_file_selection_update_history_menu( fs, cmpl_reference_position( cmpl_state ) );
1961 gtk_file_selection_abort( GtkFileSelection *fs ){
1964 sprintf( err_buf, _( "Directory unreadable: %s" ), cmpl_strerror( cmpl_errno ) );
1966 /* BEEP gdk_beep(); */
1968 if ( fs->selection_entry ) {
1969 gtk_label_set_text( GTK_LABEL( fs->selection_text ), err_buf );
1973 /**********************************************************************/
1974 /* External Interface */
1975 /**********************************************************************/
1977 /* The four completion state selectors
1980 cmpl_updated_text( CompletionState* cmpl_state ){
1981 return cmpl_state->updated_text;
1985 cmpl_updated_dir( CompletionState* cmpl_state ){
1986 return cmpl_state->re_complete;
1990 cmpl_reference_position( CompletionState* cmpl_state ){
1991 return cmpl_state->reference_dir->fullname;
1995 cmpl_last_valid_char( CompletionState* cmpl_state ){
1996 return cmpl_state->last_valid_char;
2000 cmpl_completion_fullname( gchar* text, CompletionState* cmpl_state ){
2001 static char nothing[2] = "";
2003 if ( !cmpl_state_okay( cmpl_state ) ) {
2006 else if ( text[0] == '/' ) {
2007 strcpy( cmpl_state->updated_text, text );
2009 else if ( text[0] == '~' ) {
2013 dir = open_user_dir( text, cmpl_state );
2016 /* spencer says just return ~something, so
2017 * for now just do it. */
2018 strcpy( cmpl_state->updated_text, text );
2023 strcpy( cmpl_state->updated_text, dir->fullname );
2025 slash = strchr( text, '/' );
2028 strcat( cmpl_state->updated_text, slash );
2034 strcpy( cmpl_state->updated_text, cmpl_state->reference_dir->fullname );
2035 if ( strcmp( cmpl_state->reference_dir->fullname, "/" ) != 0 ) {
2036 strcat( cmpl_state->updated_text, "/" );
2038 strcat( cmpl_state->updated_text, text );
2041 return cmpl_state->updated_text;
2044 /* The three completion selectors
2047 cmpl_this_completion( PossibleCompletion* pc ){
2052 cmpl_is_directory( PossibleCompletion* pc ){
2053 return pc->is_directory;
2057 cmpl_is_a_completion( PossibleCompletion* pc ){
2058 return pc->is_a_completion;
2061 /**********************************************************************/
2062 /* Construction, deletion */
2063 /**********************************************************************/
2065 static CompletionState*
2066 cmpl_init_state( void ){
2067 gchar getcwd_buf[2 * MAXPATHLEN];
2068 CompletionState *new_state;
2070 new_state = g_new( CompletionState, 1 );
2072 /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
2073 * and, if that wasn't bad enough, hangs in doing so.
2075 #if defined( sun ) && !defined( __SVR4 )
2076 if ( !getwd( getcwd_buf ) )
2078 if ( !getcwd( getcwd_buf, MAXPATHLEN ) )
2081 /* Oh joy, we can't get the current directory. Um..., we should have
2082 * a root directory, right? Right? (Probably not portable to non-Unix)
2084 strcpy( getcwd_buf, "/" );
2089 new_state->reference_dir = NULL;
2090 new_state->completion_dir = NULL;
2091 new_state->active_completion_dir = NULL;
2092 new_state->directory_storage = NULL;
2093 new_state->directory_sent_storage = NULL;
2094 new_state->last_valid_char = 0;
2095 new_state->updated_text = g_new( gchar, MAXPATHLEN );
2096 new_state->updated_text_alloc = MAXPATHLEN;
2097 new_state->the_completion.text = g_new( gchar, MAXPATHLEN );
2098 new_state->the_completion.text_alloc = MAXPATHLEN;
2099 new_state->user_dir_name_buffer = NULL;
2100 new_state->user_directories = NULL;
2102 new_state->reference_dir = open_dir( getcwd_buf, new_state );
2104 if ( !new_state->reference_dir ) {
2105 /* Directories changing from underneath us, grumble */
2106 strcpy( getcwd_buf, "/" );
2114 cmpl_free_dir_list( GList* dp0 ){
2118 free_dir( dp->data );
2126 cmpl_free_dir_sent_list( GList* dp0 ){
2130 free_dir_sent( dp->data );
2138 cmpl_free_state( CompletionState* cmpl_state ){
2139 cmpl_free_dir_list( cmpl_state->directory_storage );
2140 cmpl_free_dir_sent_list( cmpl_state->directory_sent_storage );
2142 if ( cmpl_state->user_dir_name_buffer ) {
2143 g_free( cmpl_state->user_dir_name_buffer );
2145 if ( cmpl_state->user_directories ) {
2146 g_free( cmpl_state->user_directories );
2148 if ( cmpl_state->the_completion.text ) {
2149 g_free( cmpl_state->the_completion.text );
2151 if ( cmpl_state->updated_text ) {
2152 g_free( cmpl_state->updated_text );
2155 g_free( cmpl_state );
2159 free_dir( CompletionDir* dir ){
2160 g_free( dir->fullname );
2165 free_dir_sent( CompletionDirSent* sent ){
2166 g_free( sent->name_buffer );
2167 g_free( sent->entries );
2172 prune_memory_usage( CompletionState *cmpl_state ){
2173 GList* cdsl = cmpl_state->directory_sent_storage;
2174 GList* cdl = cmpl_state->directory_storage;
2178 for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1 )
2182 cmpl_free_dir_sent_list( cdsl->next );
2186 cmpl_state->directory_storage = NULL;
2188 if ( cdl->data == cmpl_state->reference_dir ) {
2189 cmpl_state->directory_storage = g_list_prepend( NULL, cdl->data );
2192 free_dir( cdl->data );
2197 g_list_free( cdl0 );
2200 /**********************************************************************/
2201 /* The main entrances. */
2202 /**********************************************************************/
2204 static PossibleCompletion*
2205 cmpl_completion_matches( gchar* text_to_complete,
2206 gchar** remaining_text,
2207 CompletionState* cmpl_state ){
2209 PossibleCompletion *poss;
2211 prune_memory_usage( cmpl_state );
2213 g_assert( text_to_complete != NULL );
2215 cmpl_state->user_completion_index = -1;
2216 cmpl_state->last_completion_text = text_to_complete;
2217 cmpl_state->the_completion.text[0] = 0;
2218 cmpl_state->last_valid_char = 0;
2219 cmpl_state->updated_text_len = -1;
2220 cmpl_state->updated_text[0] = 0;
2221 cmpl_state->re_complete = FALSE;
2223 first_slash = strchr( text_to_complete, '/' );
2225 if ( text_to_complete[0] == '~' && !first_slash ) {
2226 /* Text starts with ~ and there is no slash, show all the
2227 * home directory completions.
2229 poss = attempt_homedir_completion( text_to_complete, cmpl_state );
2231 update_cmpl( poss, cmpl_state );
2236 cmpl_state->reference_dir =
2237 open_ref_dir( text_to_complete, remaining_text, cmpl_state );
2239 if ( !cmpl_state->reference_dir ) {
2243 cmpl_state->completion_dir =
2244 find_completion_dir( *remaining_text, remaining_text, cmpl_state );
2246 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2248 if ( !cmpl_state->completion_dir ) {
2252 cmpl_state->completion_dir->cmpl_index = -1;
2253 cmpl_state->completion_dir->cmpl_parent = NULL;
2254 cmpl_state->completion_dir->cmpl_text = *remaining_text;
2256 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2258 cmpl_state->reference_dir = cmpl_state->completion_dir;
2260 poss = attempt_file_completion( cmpl_state );
2262 update_cmpl( poss, cmpl_state );
2267 static PossibleCompletion*
2268 cmpl_next_completion( CompletionState* cmpl_state ){
2269 PossibleCompletion* poss = NULL;
2271 cmpl_state->the_completion.text[0] = 0;
2273 if ( cmpl_state->user_completion_index >= 0 ) {
2274 poss = attempt_homedir_completion( cmpl_state->last_completion_text, cmpl_state );
2277 poss = attempt_file_completion( cmpl_state );
2280 update_cmpl( poss, cmpl_state );
2285 /**********************************************************************/
2286 /* Directory Operations */
2287 /**********************************************************************/
2289 /* Open the directory where completion will begin from, if possible. */
2290 static CompletionDir*
2291 open_ref_dir( gchar* text_to_complete,
2292 gchar** remaining_text,
2293 CompletionState* cmpl_state ){
2295 CompletionDir *new_dir;
2297 first_slash = strchr( text_to_complete, '/' );
2299 if ( text_to_complete[0] == '~' ) {
2300 new_dir = open_user_dir( text_to_complete, cmpl_state );
2303 if ( first_slash ) {
2304 *remaining_text = first_slash + 1;
2307 *remaining_text = text_to_complete + strlen( text_to_complete );
2315 else if ( text_to_complete[0] == '/' || !cmpl_state->reference_dir ) {
2316 gchar *tmp = g_strdup( text_to_complete );
2320 while ( *p && *p != '*' && *p != '?' )
2324 p = strrchr( tmp, '/' );
2332 new_dir = open_dir( tmp, cmpl_state );
2335 *remaining_text = text_to_complete +
2336 ( ( p == tmp + 1 ) ? ( p - tmp ) : ( p + 1 - tmp ) );
2341 /* If no possible candidates, use the cwd */
2342 gchar *curdir = g_get_current_dir();
2344 new_dir = open_dir( curdir, cmpl_state );
2347 *remaining_text = text_to_complete;
2357 *remaining_text = text_to_complete;
2359 new_dir = open_dir( cmpl_state->reference_dir->fullname, cmpl_state );
2363 new_dir->cmpl_index = -1;
2364 new_dir->cmpl_parent = NULL;
2370 /* open a directory by user name */
2371 static CompletionDir*
2372 open_user_dir( gchar* text_to_complete,
2373 CompletionState *cmpl_state ){
2377 g_assert( text_to_complete && text_to_complete[0] == '~' );
2379 first_slash = strchr( text_to_complete, '/' );
2381 if ( first_slash ) {
2382 cmp_len = first_slash - text_to_complete - 1;
2385 cmp_len = strlen( text_to_complete + 1 );
2390 gchar *homedir = g_get_home_dir();
2393 return open_dir( homedir, cmpl_state );
2402 char* copy = g_new( char, cmp_len + 1 );
2404 strncpy( copy, text_to_complete + 1, cmp_len );
2406 pwd = getpwnam( copy );
2413 return open_dir( pwd->pw_dir, cmpl_state );
2417 /* open a directory relative the the current relative directory */
2418 static CompletionDir*
2419 open_relative_dir( gchar* dir_name,
2421 CompletionState *cmpl_state ){
2422 gchar path_buf[2 * MAXPATHLEN];
2424 if ( dir->fullname_len + strlen( dir_name ) + 2 >= MAXPATHLEN ) {
2425 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2429 strcpy( path_buf, dir->fullname );
2431 if ( dir->fullname_len > 1 ) {
2432 path_buf[dir->fullname_len] = '/';
2433 strcpy( path_buf + dir->fullname_len + 1, dir_name );
2437 strcpy( path_buf + dir->fullname_len, dir_name );
2440 return open_dir( path_buf, cmpl_state );
2443 /* after the cache lookup fails, really open a new directory */
2444 static CompletionDirSent*
2445 open_new_dir( gchar* dir_name, struct stat* sbuf, gboolean stat_subdirs ){
2446 CompletionDirSent* sent;
2449 struct dirent *dirent_ptr;
2450 gint buffer_size = 0;
2451 gint entry_count = 0;
2453 struct stat ent_sbuf;
2454 char path_buf[MAXPATHLEN * 2];
2457 sent = g_new( CompletionDirSent, 1 );
2458 sent->mtime = sbuf->st_mtime;
2459 sent->inode = sbuf->st_ino;
2460 sent->device = sbuf->st_dev;
2462 path_buf_len = strlen( dir_name );
2464 if ( path_buf_len > MAXPATHLEN ) {
2465 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2469 strcpy( path_buf, dir_name );
2471 directory = opendir( dir_name );
2478 while ( ( dirent_ptr = readdir( directory ) ) != NULL )
2480 int entry_len = strlen( dirent_ptr->d_name );
2481 buffer_size += entry_len + 1;
2484 if ( path_buf_len + entry_len + 2 >= MAXPATHLEN ) {
2485 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2486 closedir( directory );
2491 sent->name_buffer = g_new( gchar, buffer_size );
2492 sent->entries = g_new( CompletionDirEntry, entry_count );
2493 sent->entry_count = entry_count;
2495 buffer_ptr = sent->name_buffer;
2497 rewinddir( directory );
2499 for ( i = 0; i < entry_count; i += 1 )
2501 dirent_ptr = readdir( directory );
2503 if ( !dirent_ptr ) {
2505 closedir( directory );
2509 strcpy( buffer_ptr, dirent_ptr->d_name );
2510 sent->entries[i].entry_name = buffer_ptr;
2511 buffer_ptr += strlen( dirent_ptr->d_name );
2515 path_buf[path_buf_len] = '/';
2516 strcpy( path_buf + path_buf_len + 1, dirent_ptr->d_name );
2518 if ( stat_subdirs ) {
2519 if ( stat( path_buf, &ent_sbuf ) >= 0 && S_ISDIR( ent_sbuf.st_mode ) ) {
2520 sent->entries[i].is_dir = 1;
2523 /* stat may fail, and we don't mind, since it could be a
2524 * dangling symlink. */
2525 sent->entries[i].is_dir = 0;
2529 sent->entries[i].is_dir = 1;
2533 qsort( sent->entries, sent->entry_count, sizeof( CompletionDirEntry ), compare_cmpl_dir );
2535 closedir( directory );
2541 check_dir( gchar *dir_name, struct stat *result, gboolean *stat_subdirs ){
2542 /* A list of directories that we know only contain other directories.
2543 * Trying to stat every file in these directories would be very
2550 struct stat statbuf;
2551 } no_stat_dirs[] = {
2552 { "/afs", FALSE, { 0 } },
2553 { "/net", FALSE, { 0 } }
2556 static const gint n_no_stat_dirs = sizeof( no_stat_dirs ) / sizeof( no_stat_dirs[0] );
2557 static gboolean initialized = FALSE;
2561 if ( !initialized ) {
2563 for ( i = 0; i < n_no_stat_dirs; i++ )
2565 if ( stat( no_stat_dirs[i].name, &no_stat_dirs[i].statbuf ) == 0 ) {
2566 no_stat_dirs[i].present = TRUE;
2571 if ( stat( dir_name, result ) < 0 ) {
2576 *stat_subdirs = TRUE;
2577 for ( i = 0; i < n_no_stat_dirs; i++ )
2579 if ( no_stat_dirs[i].present &&
2580 ( no_stat_dirs[i].statbuf.st_dev == result->st_dev ) &&
2581 ( no_stat_dirs[i].statbuf.st_ino == result->st_ino ) ) {
2582 *stat_subdirs = FALSE;
2590 /* open a directory by absolute pathname */
2591 static CompletionDir*
2592 open_dir( gchar* dir_name, CompletionState* cmpl_state ){
2594 gboolean stat_subdirs;
2595 CompletionDirSent *sent;
2598 if ( !check_dir( dir_name, &sbuf, &stat_subdirs ) ) {
2602 cdsl = cmpl_state->directory_sent_storage;
2608 if ( sent->inode == sbuf.st_ino &&
2609 sent->mtime == sbuf.st_mtime &&
2610 sent->device == sbuf.st_dev ) {
2611 return attach_dir( sent, dir_name, cmpl_state );
2617 sent = open_new_dir( dir_name, &sbuf, stat_subdirs );
2620 cmpl_state->directory_sent_storage =
2621 g_list_prepend( cmpl_state->directory_sent_storage, sent );
2623 return attach_dir( sent, dir_name, cmpl_state );
2629 static CompletionDir*
2630 attach_dir( CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state ){
2631 CompletionDir* new_dir;
2633 new_dir = g_new( CompletionDir, 1 );
2635 cmpl_state->directory_storage =
2636 g_list_prepend( cmpl_state->directory_storage, new_dir );
2638 new_dir->sent = sent;
2639 new_dir->fullname = g_strdup( dir_name );
2640 new_dir->fullname_len = strlen( dir_name );
2646 correct_dir_fullname( CompletionDir* cmpl_dir ){
2647 gint length = strlen( cmpl_dir->fullname );
2650 if ( strcmp( cmpl_dir->fullname + length - 2, "/." ) == 0 ) {
2651 if ( length == 2 ) {
2652 strcpy( cmpl_dir->fullname, "/" );
2653 cmpl_dir->fullname_len = 1;
2657 cmpl_dir->fullname[length - 2] = 0;
2660 else if ( strcmp( cmpl_dir->fullname + length - 3, "/./" ) == 0 ) {
2661 cmpl_dir->fullname[length - 2] = 0;
2663 else if ( strcmp( cmpl_dir->fullname + length - 3, "/.." ) == 0 ) {
2664 if ( length == 3 ) {
2665 strcpy( cmpl_dir->fullname, "/" );
2666 cmpl_dir->fullname_len = 1;
2670 if ( stat( cmpl_dir->fullname, &sbuf ) < 0 ) {
2675 cmpl_dir->fullname[length - 2] = 0;
2677 if ( !correct_parent( cmpl_dir, &sbuf ) ) {
2681 else if ( strcmp( cmpl_dir->fullname + length - 4, "/../" ) == 0 ) {
2682 if ( length == 4 ) {
2683 strcpy( cmpl_dir->fullname, "/" );
2684 cmpl_dir->fullname_len = 1;
2688 if ( stat( cmpl_dir->fullname, &sbuf ) < 0 ) {
2693 cmpl_dir->fullname[length - 3] = 0;
2695 if ( !correct_parent( cmpl_dir, &sbuf ) ) {
2700 cmpl_dir->fullname_len = strlen( cmpl_dir->fullname );
2706 correct_parent( CompletionDir* cmpl_dir, struct stat *sbuf ){
2712 last_slash = strrchr( cmpl_dir->fullname, '/' );
2714 g_assert( last_slash );
2716 if ( last_slash != cmpl_dir->fullname ) { /* last_slash[0] = 0; */
2724 if ( stat( cmpl_dir->fullname, &parbuf ) < 0 ) {
2729 if ( parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev ) {
2730 /* it wasn't a link */
2738 last_slash[0] = '/'; */
2740 /* it was a link, have to figure it out the hard way */
2742 new_name = find_parent_dir_fullname( cmpl_dir->fullname );
2748 g_free( cmpl_dir->fullname );
2750 cmpl_dir->fullname = new_name;
2756 find_parent_dir_fullname( gchar* dirname ){
2757 gchar buffer[MAXPATHLEN];
2758 gchar buffer2[MAXPATHLEN];
2760 #if defined( sun ) && !defined( __SVR4 )
2761 if ( !getwd( buffer ) )
2763 if ( !getcwd( buffer, MAXPATHLEN ) )
2770 if ( chdir( dirname ) != 0 || chdir( ".." ) != 0 ) {
2775 #if defined( sun ) && !defined( __SVR4 )
2776 if ( !getwd( buffer2 ) )
2778 if ( !getcwd( buffer2, MAXPATHLEN ) )
2787 if ( chdir( buffer ) != 0 ) {
2792 return g_strdup( buffer2 );
2795 /**********************************************************************/
2796 /* Completion Operations */
2797 /**********************************************************************/
2799 static PossibleCompletion*
2800 attempt_homedir_completion( gchar* text_to_complete,
2801 CompletionState *cmpl_state ){
2804 if ( !cmpl_state->user_dir_name_buffer &&
2805 !get_pwdb( cmpl_state ) ) {
2808 length = strlen( text_to_complete ) - 1;
2810 cmpl_state->user_completion_index += 1;
2812 while ( cmpl_state->user_completion_index < cmpl_state->user_directories_len )
2814 index = first_diff_index( text_to_complete + 1,
2815 cmpl_state->user_directories
2816 [cmpl_state->user_completion_index].login );
2823 if ( cmpl_state->last_valid_char < ( index + 1 ) ) {
2824 cmpl_state->last_valid_char = index + 1;
2826 cmpl_state->user_completion_index += 1;
2830 cmpl_state->the_completion.is_a_completion = 1;
2831 cmpl_state->the_completion.is_directory = 1;
2833 append_completion_text( "~", cmpl_state );
2835 append_completion_text( cmpl_state->
2836 user_directories[cmpl_state->user_completion_index].login,
2839 return append_completion_text( "/", cmpl_state );
2842 if ( text_to_complete[1] ||
2843 cmpl_state->user_completion_index > cmpl_state->user_directories_len ) {
2844 cmpl_state->user_completion_index = -1;
2849 cmpl_state->user_completion_index += 1;
2850 cmpl_state->the_completion.is_a_completion = 1;
2851 cmpl_state->the_completion.is_directory = 1;
2853 return append_completion_text( "~/", cmpl_state );
2857 /* returns the index (>= 0) of the first differing character,
2858 * PATTERN_MATCH if the completion matches */
2860 first_diff_index( gchar* pat, gchar* text ){
2863 while ( *pat && *text && *text == *pat )
2874 return PATTERN_MATCH;
2877 static PossibleCompletion*
2878 append_completion_text( gchar* text, CompletionState* cmpl_state ){
2881 if ( !cmpl_state->the_completion.text ) {
2885 len = strlen( text ) + strlen( cmpl_state->the_completion.text ) + 1;
2887 if ( cmpl_state->the_completion.text_alloc > len ) {
2888 strcat( cmpl_state->the_completion.text, text );
2889 return &cmpl_state->the_completion;
2892 while ( i < len ) { i <<= 1; }
2894 cmpl_state->the_completion.text_alloc = i;
2896 cmpl_state->the_completion.text = (gchar*)g_realloc( cmpl_state->the_completion.text, i );
2898 if ( !cmpl_state->the_completion.text ) {
2903 strcat( cmpl_state->the_completion.text, text );
2904 return &cmpl_state->the_completion;
2908 static CompletionDir*
2909 find_completion_dir( gchar* text_to_complete,
2910 gchar** remaining_text,
2911 CompletionState* cmpl_state ){
2912 gchar* first_slash = strchr( text_to_complete, '/' );
2913 CompletionDir* dir = cmpl_state->reference_dir;
2914 CompletionDir* next;
2915 *remaining_text = text_to_complete;
2917 while ( first_slash )
2919 gint len = first_slash - *remaining_text;
2921 gchar *found_name = NULL; /* Quiet gcc */
2923 gchar* pat_buf = g_new( gchar, len + 1 );
2925 strncpy( pat_buf, *remaining_text, len );
2928 for ( i = 0; i < dir->sent->entry_count; i += 1 )
2930 if ( dir->sent->entries[i].is_dir &&
2931 fnmatch( pat_buf, dir->sent->entries[i].entry_name,
2932 FNMATCH_FLAGS ) != FNM_NOMATCH ) {
2940 found_name = dir->sent->entries[i].entry_name;
2946 /* Perhaps we are trying to open an automount directory */
2947 found_name = pat_buf;
2950 next = open_relative_dir( found_name, dir, cmpl_state );
2957 next->cmpl_parent = dir;
2961 if ( !correct_dir_fullname( dir ) ) {
2966 *remaining_text = first_slash + 1;
2967 first_slash = strchr( *remaining_text, '/' );
2976 update_cmpl( PossibleCompletion* poss, CompletionState* cmpl_state ){
2979 if ( !poss || !cmpl_is_a_completion( poss ) ) {
2983 cmpl_len = strlen( cmpl_this_completion( poss ) );
2985 if ( cmpl_state->updated_text_alloc < cmpl_len + 1 ) {
2986 cmpl_state->updated_text =
2987 (gchar*)g_realloc( cmpl_state->updated_text,
2988 cmpl_state->updated_text_alloc );
2989 cmpl_state->updated_text_alloc = 2 * cmpl_len;
2992 if ( cmpl_state->updated_text_len < 0 ) {
2993 strcpy( cmpl_state->updated_text, cmpl_this_completion( poss ) );
2994 cmpl_state->updated_text_len = cmpl_len;
2995 cmpl_state->re_complete = cmpl_is_directory( poss );
2997 else if ( cmpl_state->updated_text_len == 0 ) {
2998 cmpl_state->re_complete = FALSE;
3003 first_diff_index( cmpl_state->updated_text,
3004 cmpl_this_completion( poss ) );
3006 cmpl_state->re_complete = FALSE;
3008 if ( first_diff == PATTERN_MATCH ) {
3012 if ( first_diff > cmpl_state->updated_text_len ) {
3013 strcpy( cmpl_state->updated_text, cmpl_this_completion( poss ) );
3016 cmpl_state->updated_text_len = first_diff;
3017 cmpl_state->updated_text[first_diff] = 0;
3021 static PossibleCompletion*
3022 attempt_file_completion( CompletionState *cmpl_state ){
3023 gchar *pat_buf, *first_slash;
3024 CompletionDir *dir = cmpl_state->active_completion_dir;
3026 dir->cmpl_index += 1;
3028 if ( dir->cmpl_index == dir->sent->entry_count ) {
3029 if ( dir->cmpl_parent == NULL ) {
3030 cmpl_state->active_completion_dir = NULL;
3036 cmpl_state->active_completion_dir = dir->cmpl_parent;
3038 return attempt_file_completion( cmpl_state );
3042 g_assert( dir->cmpl_text );
3044 first_slash = strchr( dir->cmpl_text, '/' );
3046 if ( first_slash ) {
3047 gint len = first_slash - dir->cmpl_text;
3049 pat_buf = g_new( gchar, len + 1 );
3050 strncpy( pat_buf, dir->cmpl_text, len );
3055 gint len = strlen( dir->cmpl_text );
3057 pat_buf = g_new( gchar, len + 2 );
3058 strcpy( pat_buf, dir->cmpl_text );
3059 strcpy( pat_buf + len, "*" );
3062 if ( first_slash ) {
3063 if ( dir->sent->entries[dir->cmpl_index].is_dir ) {
3064 if ( fnmatch( pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3065 FNMATCH_FLAGS ) != FNM_NOMATCH ) {
3066 CompletionDir* new_dir;
3068 new_dir = open_relative_dir( dir->sent->entries[dir->cmpl_index].entry_name,
3076 new_dir->cmpl_parent = dir;
3078 new_dir->cmpl_index = -1;
3079 new_dir->cmpl_text = first_slash + 1;
3081 cmpl_state->active_completion_dir = new_dir;
3084 return attempt_file_completion( cmpl_state );
3089 return attempt_file_completion( cmpl_state );
3095 return attempt_file_completion( cmpl_state );
3100 if ( dir->cmpl_parent != NULL ) {
3101 append_completion_text( dir->fullname +
3102 strlen( cmpl_state->completion_dir->fullname ) + 1,
3104 append_completion_text( "/", cmpl_state );
3107 append_completion_text( dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state );
3109 cmpl_state->the_completion.is_a_completion =
3110 ( fnmatch( pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3111 FNMATCH_FLAGS ) != FNM_NOMATCH );
3113 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3114 if ( dir->sent->entries[dir->cmpl_index].is_dir ) {
3115 append_completion_text( "/", cmpl_state );
3119 return &cmpl_state->the_completion;
3125 get_pwdb( CompletionState* cmpl_state ){
3126 struct passwd *pwd_ptr;
3128 gint len = 0, i, count = 0;
3130 if ( cmpl_state->user_dir_name_buffer ) {
3135 while ( ( pwd_ptr = getpwent() ) != NULL )
3137 len += strlen( pwd_ptr->pw_name );
3138 len += strlen( pwd_ptr->pw_dir );
3145 cmpl_state->user_dir_name_buffer = g_new( gchar, len );
3146 cmpl_state->user_directories = g_new( CompletionUserDir, count );
3147 cmpl_state->user_directories_len = count;
3149 buf_ptr = cmpl_state->user_dir_name_buffer;
3151 for ( i = 0; i < count; i += 1 )
3153 pwd_ptr = getpwent();
3159 strcpy( buf_ptr, pwd_ptr->pw_name );
3160 cmpl_state->user_directories[i].login = buf_ptr;
3161 buf_ptr += strlen( buf_ptr );
3163 strcpy( buf_ptr, pwd_ptr->pw_dir );
3164 cmpl_state->user_directories[i].homedir = buf_ptr;
3165 buf_ptr += strlen( buf_ptr );
3169 qsort( cmpl_state->user_directories,
3170 cmpl_state->user_directories_len,
3171 sizeof( CompletionUserDir ),
3180 if ( cmpl_state->user_dir_name_buffer ) {
3181 g_free( cmpl_state->user_dir_name_buffer );
3183 if ( cmpl_state->user_directories ) {
3184 g_free( cmpl_state->user_directories );
3187 cmpl_state->user_dir_name_buffer = NULL;
3188 cmpl_state->user_directories = NULL;
3194 compare_user_dir( const void* a, const void* b ){
3195 return strcmp( ( ( (CompletionUserDir*)a ) )->login,
3196 ( ( (CompletionUserDir*)b ) )->login );
3200 compare_cmpl_dir( const void* a, const void* b ){
3201 return strcmp( ( ( (CompletionDirEntry*)a ) )->entry_name,
3202 ( ( (CompletionDirEntry*)b ) )->entry_name );
3206 cmpl_state_okay( CompletionState* cmpl_state ){
3207 return cmpl_state && cmpl_state->reference_dir;
3211 cmpl_strerror( gint err ){
3212 if ( err == CMPL_ERRNO_TOO_LONG ) {
3213 return "Name too long";
3216 return g_strerror( err );
3224 /* Get the selected filename and print it to the console */
3225 void file_ok_sel( GtkWidget *w,
3226 GtkFileSelection *fs ){
3227 g_print( "%s\n", gtk_file_selection_get_filename( GTK_FILE_SELECTION( fs ) ) );
3230 void destroy( GtkWidget *widget,
3239 gtk_init( &argc, &argv );
3241 /* Create a new file selection widget */
3242 filew = gtk_file_selection_new( "Michael's Glorious File Selector" );
3243 // gtk_file_selection_complete(GTK_FILE_SELECTION(filew),"bob");
3246 gtk_signal_connect( GTK_OBJECT( filew ), "destroy",
3247 (GtkSignalFunc) destroy, &filew );
3248 /* Connect the ok_button to file_ok_sel function */
3249 gtk_signal_connect( GTK_OBJECT( GTK_FILE_SELECTION( filew )->ok_button ),
3250 "clicked", (GtkSignalFunc) file_ok_sel, filew );
3252 /* Connect the cancel_button to destroy the widget */
3253 gtk_signal_connect_object( GTK_OBJECT( GTK_FILE_SELECTION
3254 ( filew )->cancel_button ),
3255 "clicked", (GtkSignalFunc) gtk_widget_destroy,
3256 GTK_OBJECT( filew ) );
3259 gtk_widget_show( filew );
3262 g_print("%d",gtk_file_selection_match_mask("mask.c","m*.c"));
3263 g_print("%d",gtk_file_selection_match_mask("mask.c","m???.c"));
3264 g_print("%d",gtk_file_selection_match_mask("mask.c","m??*.c"));
3265 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c"));
3266 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c???"));
3267 g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c*"));
3268 g_print("%d",gtk_file_selection_match_mask("mask.cout","n*.c???"));
3269 g_print("%d",gtk_file_selection_match_mask("mask.c","[mn]*"));
3270 g_print("%d",gtk_file_selection_match_mask("COPYING","*.xpm"));