]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/gtkfilesel-darwin.c
reorganized about dialog code, added updated 1.5.0 .ico
[xonotic/netradiant.git] / radiant / gtkfilesel-darwin.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 /*
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/.
25  */
26
27
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/
32
33 // common files win32/linux
34 #include <stdio.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40
41 // TTimo
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
44
45 #if defined ( __linux__ ) || ( __APPLE__ )
46 #include <sys/param.h>
47 #include <dirent.h>
48 #include <unistd.h>
49 #include <pwd.h>
50 #include "fnmatch.h"
51 #endif
52
53 // leo: added "gtk/"
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"
74
75 // leo: disable NLS
76 //#include "gtk/gtkintl.h"
77 #define _( String ) ( String )
78
79 #define DIR_LIST_WIDTH   180
80 #define DIR_LIST_HEIGHT  180
81 #define FILE_LIST_WIDTH  180
82 #define FILE_LIST_HEIGHT 180
83
84 /* I've put this here so it doesn't get confused with the
85  * file completion interface */
86 typedef struct _HistoryCallbackArg HistoryCallbackArg;
87
88 struct _HistoryCallbackArg
89 {
90         gchar *directory;
91         GtkWidget *menu_item;
92 };
93
94
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;
101
102 /* Non-external file completion decls and structures */
103
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
107
108 /* A constant used to determine whether a substring was an exact
109  * match by first_diff_index()
110  */
111 #define PATTERN_MATCH -1
112 /* The arguments used by all fnmatch() calls below
113  */
114 #define FNMATCH_FLAGS ( FNM_PATHNAME | FNM_PERIOD )
115
116 #define CMPL_ERRNO_TOO_LONG ( ( 1 << 16 ) - 1 )
117
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.
121  */
122 struct _CompletionDirSent
123 {
124         ino_t inode;
125         time_t mtime;
126         dev_t device;
127
128         gint entry_count;
129         gchar *name_buffer; /* memory segment containing names of all entries */
130
131         struct _CompletionDirEntry *entries;
132 };
133
134 struct _CompletionDir
135 {
136         CompletionDirSent *sent;
137
138         gchar *fullname;
139         gint fullname_len;
140
141         struct _CompletionDir *cmpl_parent;
142         gint cmpl_index;
143         gchar *cmpl_text;
144 };
145
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).
153  */
154 struct _CompletionDirEntry
155 {
156         gint is_dir;
157         gchar *entry_name;
158 };
159
160 struct _CompletionUserDir
161 {
162         gchar *login;
163         gchar *homedir;
164 };
165
166 struct _PossibleCompletion
167 {
168         /* accessible fields, all are accessed externally by functions
169          * declared above
170          */
171         gchar *text;
172         gint is_a_completion;
173         gint is_directory;
174
175         gint file_size;
176         gint file_time;
177         gint uid;
178         gint gid;
179         /* Private fields
180          */
181         gint text_alloc;
182 };
183
184 struct _CompletionState
185 {
186         gint last_valid_char;
187         gchar *updated_text;
188         gint updated_text_len;
189         gint updated_text_alloc;
190         gint re_complete;
191
192         gchar *user_dir_name_buffer;
193         gint user_directories_len;
194
195         gchar *last_completion_text;
196
197         gint user_completion_index; /* if >= 0, currently completing ~user */
198
199         struct _CompletionDir *completion_dir; /* directory completing from */
200         struct _CompletionDir *active_completion_dir;
201
202         struct _PossibleCompletion the_completion;
203
204         struct _CompletionDir *reference_dir; /* initial directory */
205
206         GList* directory_storage;
207         GList* directory_sent_storage;
208
209         struct _CompletionUserDir *user_directories;
210 };
211
212
213 /* File completion functions which would be external, were they used
214  * outside of this file.
215  */
216
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 );
221
222 static PossibleCompletion* cmpl_completion_matches( gchar           *text_to_complete,
223                                                                                                         gchar          **remaining_text,
224                                                                                                         CompletionState *cmpl_state );
225
226 /* Returns a name for consideration, possibly a completion, this name
227  * will be invalid after the next call to cmpl_next_completion.
228  */
229 static char*               cmpl_this_completion( PossibleCompletion* );
230
231 /* True if this completion matches the given text.  Otherwise, this
232  * output can be used to have a list of non-completions.
233  */
234 static gint                cmpl_is_a_completion( PossibleCompletion* );
235
236 /* True if the completion is a directory
237  */
238 static gint                cmpl_is_directory( PossibleCompletion* );
239
240 /* Obtains the next completion, or NULL
241  */
242 static PossibleCompletion* cmpl_next_completion( CompletionState* );
243
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
249  * been received.
250  */
251 static gchar*              cmpl_updated_text( CompletionState* cmpl_state );
252
253 /* After updating, to see if the completion was a directory, call
254  * this.  If it was, you should consider re-calling completion_matches.
255  */
256 static gint                cmpl_updated_dir( CompletionState* cmpl_state );
257
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('/').
262  */
263 static gchar*              cmpl_reference_position( CompletionState* cmpl_state );
264
265 /* backing up: if cmpl_completion_matches returns NULL, you may query
266  * the index of the last completable character into cmpl_updated_text.
267  */
268 static gint                cmpl_last_valid_char( CompletionState* cmpl_state );
269
270 /* When the user selects a non-directory, call cmpl_completion_fullname
271  * to get the full name of the selected file.
272  */
273 static gchar*              cmpl_completion_fullname( gchar*, CompletionState* cmpl_state );
274
275
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,
281                                                                  struct stat *result,
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,
290                                                                                 struct stat* sbuf,
291                                                                                 gboolean stat_subdirs );
292 static gint           correct_dir_fullname( CompletionDir* cmpl_dir );
293 static gint           correct_parent( CompletionDir* cmpl_dir,
294                                                                           struct stat *sbuf );
295 static gchar*         find_parent_dir_fullname( gchar* dirname );
296 static CompletionDir* attach_dir( CompletionDirSent* sent,
297                                                                   gchar* dir_name,
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 );
302
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 );
318
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,
323                                                                                   GdkEventKey           *event,
324                                                                                   gpointer user_data );
325
326 static void gtk_file_selection_file_button( GtkWidget *widget,
327                                                                                         gint row,
328                                                                                         gint column,
329                                                                                         GdkEventButton *bevent,
330                                                                                         gpointer user_data );
331
332 static void gtk_file_selection_dir_button( GtkWidget *widget,
333                                                                                    gint row,
334                                                                                    gint column,
335                                                                                    GdkEventButton *bevent,
336                                                                                    gpointer data );
337
338 static void gtk_file_selection_undir_button( GtkWidget *widget,
339                                                                                          gint row,
340                                                                                          gint column,
341                                                                                          GdkEventButton *bevent,
342                                                                                          gpointer data );
343
344 static void gtk_file_selection_populate( GtkFileSelection      *fs,
345                                                                                  gchar                 *rel_path,
346                                                                                  gint try_complete );
347 static void gtk_file_selection_abort( GtkFileSelection      *fs );
348
349 static void gtk_file_selection_update_history_menu( GtkFileSelection       *fs,
350                                                                                                         gchar                  *current_dir );
351
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 );
355
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,
358                                                                                                                                    GdkEventKey *event,
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 );
372
373 static gint gtk_file_selection_match_char( gchar, gchar *mask );
374 static gint gtk_file_selection_match_mask( gchar *,gchar * );
375
376
377 static GtkWindowClass *parent_class = NULL;
378
379 /* Saves errno when something cmpl does fails. */
380 static gint cmpl_errno;
381
382
383 void gtk_file_selection_clear_masks( GtkFileSelection *filesel ){
384         GList *list;
385
386         g_return_if_fail( filesel != NULL );
387         g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
388
389         list = filesel->masks;
390         while ( list )
391         {
392                 g_free( list->data );
393                 list = list->next;
394         }
395         filesel->masks = NULL;
396
397         gtk_list_clear_items( GTK_LIST( GTK_COMBO( filesel->mask_entry )->list ), 0, -1 );
398 }
399
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 ) );
403
404         while ( *masks )
405         {
406                 filesel->masks = g_list_append( filesel->masks, ( gpointer ) * masks );
407                 masks++;
408         }
409
410         if ( filesel->masks ) {
411                 gtk_combo_set_popdown_strings( GTK_COMBO( filesel->mask_entry ), filesel->masks );
412         }
413 }
414
415
416 /* General notes:
417  * Make prev and next inactive if their respective *
418  *   histories are empty.
419  * Add facilities for handling hidden files and    *
420  * directories                                     *
421  * Add an api to access the mask, and hidden files *
422  * check box?  (prob not in 1.2.x series)          *
423  */
424
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] *
436  *                                                *
437  */
438 static gint gtk_file_selection_match_char( gchar text, gchar *mask ){
439         gchar *maskc;
440         gint x;
441         gint s;
442
443         if ( mask[0] == '[' ) {
444                 if ( !strchr( mask,']' ) ) {
445                         return 0;
446                 }
447                 maskc = g_strdup( mask + 1 ); /* get the portion of mask inside []*/
448
449                 ( *( strchr( maskc,']' ) ) ) = 0;
450                 s = strlen( (char *)maskc );
451
452                 for ( x = 0; x < s; x++ ) {
453                         if ( text == maskc[x] ) {
454                                 g_free( maskc );
455                                 return s + 2;
456                         }
457                 }
458                 g_free( maskc );
459                 return 0;
460         }
461
462         if ( mask[0] == '?' ) {
463                 return 1;
464         }
465         if ( mask[0] == text ) {
466                 return 1;
467         }
468
469         return 0;
470 }
471
472
473 static gint gtk_file_selection_match_mask( gchar *text, gchar *mask ){
474
475         int mc;
476         int tc;
477
478         tc = 0; mc = 0;
479
480         if ( mask[0] == 0 && text[0] == 0 ) {
481                 return 1;
482         }
483
484         if ( mask[0] == '*' ) {
485                 for ( tc = 0; tc <= strlen( text ); tc++ )
486                 {
487                         if ( gtk_file_selection_match_mask( text + tc, mask + 1 ) ) {
488                                 return 1;
489                         }
490                 }
491                 return 0;
492         }
493         mc = gtk_file_selection_match_char( text[0], mask );
494
495         if ( mc ) {
496                 return gtk_file_selection_match_mask( text + 1, mask + mc );
497         }
498         else{
499                 return 0;
500         }
501 }
502
503 GtkType
504 gtk_file_selection_get_type( void ){
505         static GtkType file_selection_type = 0;
506
507         if ( !file_selection_type ) {
508                 static const GtkTypeInfo filesel_info =
509                 {
510                         "GtkFileSelection",
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,
518                 };
519
520                 file_selection_type = gtk_type_unique( GTK_TYPE_WINDOW, &filesel_info );
521         }
522
523         return file_selection_type;
524 }
525
526 static void
527 gtk_file_selection_class_init( GtkFileSelectionClass *klass ){   //tigital
528         GtkObjectClass *object_class;
529
530         object_class = (GtkObjectClass*) klass;
531
532         parent_class = gtk_type_class( GTK_TYPE_WINDOW );
533
534         object_class->destroy = gtk_file_selection_destroy;
535 }
536
537 static void
538 gtk_file_selection_init( GtkFileSelection *filesel ){
539         GtkWidget *entry_vbox;
540         GtkWidget *label;
541         GtkWidget *list_hbox;
542         GtkWidget *confirm_area;
543         GtkWidget *vbox;
544         GtkWidget *hbox;
545         GtkWidget *pulldown_hbox;
546         GtkWidget *scrolled_win;
547         GtkWidget *mask_label;
548         GtkWidget *bigframe;
549         GtkWidget *label_lookingin;
550         GtkWidget *up_button;
551         GtkWidget *home_button;
552         GtkWidget *prev_button;
553         GtkWidget *next_button;
554         GtkWidget *refresh_button;
555
556         char *dir_title [2];
557         char *file_title [2];
558
559         filesel->cmpl_state = cmpl_init_state();
560
561         filesel->mask = NULL;
562         filesel->prev_history = NULL;
563         filesel->next_history = NULL;
564         filesel->saved_entry = NULL;
565
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 );
571
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,
577                                                 FALSE, FALSE, 0 );
578         gtk_widget_show( filesel->button_area );
579
580         gtk_file_selection_show_fileop_buttons( filesel );
581
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 );
586
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 );
591
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,
596                                                 TRUE,TRUE, 0 );
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 );
600
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 );
604
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 );
608
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 );
613
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 );
619
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 );
624
625         hbox = gtk_hbox_new( FALSE, 0 );
626         gtk_widget_show( hbox );
627         gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
628
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 );
635
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 );
642
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 );
649
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 );
656
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 );
663
664         /* The directories clist */
665         dir_title[0] = _( "Directories" );
666         dir_title[1] = NULL;
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 ) );
676
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 );
684
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 );
689
690         hbox = gtk_hbox_new( FALSE, 5 );
691         gtk_widget_show( hbox );
692         gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
693
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 );
697
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 );
704
705
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 ) );
715
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 );
723
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,
727                                                 FALSE, FALSE, 0 );
728         gtk_widget_show( filesel->action_area );
729
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 );
736
737         /*  The OK button  */
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 );
743
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 );
749
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 );
754
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 );
759
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 );
771
772         if ( !cmpl_state_okay( filesel->cmpl_state ) ) {
773                 gchar err_buf[256];
774
775                 sprintf( err_buf, _( "Directory unreadable: %s" ), cmpl_strerror( cmpl_errno ) );
776
777                 gtk_label_set_text( GTK_LABEL( filesel->selection_text ), err_buf );
778         }
779         else
780         {
781                 gtk_file_selection_populate( filesel, "", FALSE );
782         }
783
784         gtk_widget_grab_focus( filesel->selection_entry );
785 }
786
787 GtkWidget*
788 gtk_file_selection_new( const gchar *title ){
789         GtkFileSelection *filesel;
790
791         filesel = gtk_type_new( GTK_TYPE_FILE_SELECTION );
792         gtk_window_set_title( GTK_WINDOW( filesel ), title );
793
794         return GTK_WIDGET( filesel );
795 }
796
797 void
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 ) );
801
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 );
811         }
812
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 );
821         }
822
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 );
831         }
832
833         gtk_widget_queue_resize( GTK_WIDGET( filesel ) );
834 }
835
836 void
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 ) );
840
841         if ( filesel->fileop_ren_file ) {
842                 gtk_widget_destroy( filesel->fileop_ren_file );
843                 filesel->fileop_ren_file = NULL;
844         }
845
846         if ( filesel->fileop_del_file ) {
847                 gtk_widget_destroy( filesel->fileop_del_file );
848                 filesel->fileop_del_file = NULL;
849         }
850
851         if ( filesel->fileop_c_dir ) {
852                 gtk_widget_destroy( filesel->fileop_c_dir );
853                 filesel->fileop_c_dir = NULL;
854         }
855 }
856
857
858
859 void
860 gtk_file_selection_set_filename( GtkFileSelection *filesel,
861                                                                  const gchar      *filename ){
862         char buf[MAXPATHLEN];
863         const char *name, *last_slash;
864
865         g_return_if_fail( filesel != NULL );
866         g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
867         g_return_if_fail( filename != NULL );
868
869         last_slash = strrchr( filename, '/' );
870
871         if ( !last_slash ) {
872                 buf[0] = 0;
873                 name = filename;
874         }
875         else
876         {
877                 gint len = MIN( MAXPATHLEN - 1, last_slash - filename + 1 );
878
879                 strncpy( buf, filename, len );
880                 buf[len] = 0;
881
882                 name = last_slash + 1;
883         }
884
885         gtk_file_selection_populate( filesel, buf, FALSE );
886
887         if ( filesel->selection_entry ) {
888                 gtk_entry_set_text( GTK_ENTRY( filesel->selection_entry ), name );
889         }
890 }
891
892 gchar*
893 gtk_file_selection_get_filename( GtkFileSelection *filesel ){
894         static char nothing[2] = "";
895         char *text;
896         char *filename;
897
898         g_return_val_if_fail( filesel != NULL, nothing );
899         g_return_val_if_fail( GTK_IS_FILE_SELECTION( filesel ), nothing );
900
901         text = gtk_entry_get_text( GTK_ENTRY( filesel->selection_entry ) );
902         if ( text ) {
903                 filename = cmpl_completion_fullname( text, filesel->cmpl_state );
904                 return filename;
905         }
906
907         return nothing;
908 }
909
910 void
911 gtk_file_selection_complete( GtkFileSelection *filesel,
912                                                          const gchar      *pattern ){
913         gchar *new_pattern;
914         gint x;
915
916         g_return_if_fail( filesel != NULL );
917         g_return_if_fail( GTK_IS_FILE_SELECTION( filesel ) );
918         g_return_if_fail( pattern != NULL );
919
920         if ( filesel->selection_entry ) {
921                 gtk_entry_set_text( GTK_ENTRY( filesel->selection_entry ), pattern );
922         }
923
924         if ( strchr( pattern,'*' ) || strchr( pattern,'?' ) ) {
925                 for ( x = strlen( pattern ); x >= 0; x-- )
926                 {
927                         if ( pattern[x] == '/' ) {
928                                 break;
929                         }
930                 }
931                 gtk_entry_set_text( GTK_ENTRY( filesel->mask_entry ),g_strdup( pattern + x + 1 ) );
932
933                 if ( filesel->mask ) {
934                         g_free( filesel->mask );
935                 }
936
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 );
942         }
943         else
944         {
945                 gtk_file_selection_populate( filesel, (gchar*) pattern, TRUE );
946         }
947 }
948
949 static void
950 gtk_file_selection_destroy( GtkObject *object ){
951         GtkFileSelection *filesel;
952         GList *list;
953
954         g_return_if_fail( object != NULL );
955         g_return_if_fail( GTK_IS_FILE_SELECTION( object ) );
956
957         filesel = GTK_FILE_SELECTION( object );
958
959         if ( filesel->fileop_dialog ) {
960                 gtk_widget_destroy( filesel->fileop_dialog );
961         }
962
963         if ( filesel->next_history ) {
964                 list = filesel->next_history;
965                 while ( list )
966                 {
967                         g_free( list->data );
968                         list = list->next;
969                 }
970         }
971         g_list_free( filesel->next_history );
972         filesel->next_history = NULL;
973
974         if ( filesel->prev_history ) {
975                 list = filesel->prev_history;
976                 while ( list )
977                 {
978                         g_free( list->data );
979                         list = list->next;
980                 }
981         }
982         g_list_free( filesel->prev_history );
983         filesel->prev_history = NULL;
984
985         if ( filesel->mask ) {
986                 g_free( filesel->mask );
987                 filesel->mask = NULL;
988         }
989
990         cmpl_free_state( filesel->cmpl_state );
991         filesel->cmpl_state = NULL;
992
993         if ( GTK_OBJECT_CLASS( parent_class )->destroy ) {
994                 ( *GTK_OBJECT_CLASS( parent_class )->destroy )( object );
995         }
996 }
997
998 /* Begin file operations callbacks */
999
1000 static void
1001 gtk_file_selection_fileop_error( GtkFileSelection *fs, gchar *error_message ){
1002         GtkWidget *label;
1003         GtkWidget *vbox;
1004         GtkWidget *button;
1005         GtkWidget *dialog;
1006
1007         g_return_if_fail( error_message != NULL );
1008
1009         /* main dialog */
1010         dialog = gtk_dialog_new();
1011         /*
1012            gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1013                     (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1014                     (gpointer) fs);
1015          */
1016         gtk_window_set_title( GTK_WINDOW( dialog ), _( "Error" ) );
1017         gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1018
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 );
1023         }
1024
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,
1028                                                 FALSE, FALSE, 0 );
1029         gtk_widget_show( vbox );
1030
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 );
1035
1036         /* yes, we free it */
1037         g_free( error_message );
1038
1039         /* close button */
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 );
1049
1050         gtk_widget_show( dialog );
1051 }
1052
1053 static void
1054 gtk_file_selection_fileop_destroy( GtkWidget *widget, gpointer data ){
1055         GtkFileSelection *fs = data;
1056
1057         g_return_if_fail( fs != NULL );
1058         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1059
1060         fs->fileop_dialog = NULL;
1061 }
1062
1063
1064 static void
1065 gtk_file_selection_create_dir_confirmed( GtkWidget *widget, gpointer data ){
1066         GtkFileSelection *fs = data;
1067         gchar *dirname;
1068         gchar *path;
1069         gchar *full_path;
1070         gchar *buf;
1071         CompletionState *cmpl_state;
1072
1073         g_return_if_fail( fs != NULL );
1074         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1075
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 );
1079
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 );
1085         }
1086         g_free( full_path );
1087
1088         gtk_widget_destroy( fs->fileop_dialog );
1089         gtk_file_selection_populate( fs, "", FALSE );
1090 }
1091
1092 static void
1093 gtk_file_selection_create_dir( GtkWidget *widget, gpointer data ){
1094         GtkFileSelection *fs = data;
1095         GtkWidget *label;
1096         GtkWidget *dialog;
1097         GtkWidget *vbox;
1098         GtkWidget *button;
1099
1100         g_return_if_fail( fs != NULL );
1101         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1102
1103         if ( fs->fileop_dialog ) {
1104                 return;
1105         }
1106
1107         /* main dialog */
1108         fs->fileop_dialog = dialog = gtk_dialog_new();
1109         gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1110                                                 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1111                                                 (gpointer) fs );
1112         gtk_window_set_title( GTK_WINDOW( dialog ), _( "Create Directory" ) );
1113         gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1114
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 );
1119         }
1120
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,
1124                                                 FALSE, FALSE, 0 );
1125         gtk_widget_show( vbox );
1126
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 );
1131
1132         /*  The directory entry widget  */
1133         fs->fileop_entry = gtk_entry_new();
1134         gtk_box_pack_start( GTK_BOX( vbox ), fs->fileop_entry,
1135                                                 TRUE, TRUE, 5 );
1136         GTK_WIDGET_SET_FLAGS( fs->fileop_entry, GTK_CAN_DEFAULT );
1137         gtk_widget_show( fs->fileop_entry );
1138
1139         /* buttons */
1140         button = gtk_button_new_with_label( _( "Create" ) );
1141         gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1142                                                 (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
1143                                                 (gpointer) fs );
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 );
1148
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 );
1158
1159         gtk_widget_show( dialog );
1160 }
1161
1162 static void
1163 gtk_file_selection_delete_file_confirmed( GtkWidget *widget, gpointer data ){
1164         GtkFileSelection *fs = data;
1165         CompletionState *cmpl_state;
1166         gchar *path;
1167         gchar *full_path;
1168         gchar *buf;
1169
1170         g_return_if_fail( fs != NULL );
1171         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1172
1173         cmpl_state = (CompletionState*) fs->cmpl_state;
1174         path = cmpl_reference_position( cmpl_state );
1175
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 );
1181         }
1182         g_free( full_path );
1183
1184         gtk_widget_destroy( fs->fileop_dialog );
1185         gtk_file_selection_populate( fs, "", FALSE );
1186 }
1187
1188 static void
1189 gtk_file_selection_delete_file( GtkWidget *widget, gpointer data ){
1190         GtkFileSelection *fs = data;
1191         GtkWidget *label;
1192         GtkWidget *vbox;
1193         GtkWidget *button;
1194         GtkWidget *dialog;
1195         gchar *filename;
1196         gchar *buf;
1197
1198         g_return_if_fail( fs != NULL );
1199         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1200
1201         if ( fs->fileop_dialog ) {
1202                 return;
1203         }
1204
1205         filename = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1206         if ( strlen( filename ) < 1 ) {
1207                 return;
1208         }
1209
1210         fs->fileop_file = filename;
1211
1212         /* main dialog */
1213         fs->fileop_dialog = dialog = gtk_dialog_new();
1214         gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1215                                                 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1216                                                 (gpointer) fs );
1217         gtk_window_set_title( GTK_WINDOW( dialog ), _( "Delete File" ) );
1218         gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1219
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 );
1224         }
1225
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,
1229                                                 FALSE, FALSE, 0 );
1230         gtk_widget_show( vbox );
1231
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 );
1237         g_free( buf );
1238
1239         /* buttons */
1240         button = gtk_button_new_with_label( _( "Delete" ) );
1241         gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1242                                                 (GtkSignalFunc) gtk_file_selection_delete_file_confirmed,
1243                                                 (gpointer) fs );
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 );
1248
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 );
1258
1259         gtk_widget_show( dialog );
1260
1261 }
1262
1263 static void
1264 gtk_file_selection_rename_file_confirmed( GtkWidget *widget, gpointer data ){
1265         GtkFileSelection *fs = data;
1266         gchar *buf;
1267         gchar *file;
1268         gchar *path;
1269         gchar *new_filename;
1270         gchar *old_filename;
1271         CompletionState *cmpl_state;
1272
1273         g_return_if_fail( fs != NULL );
1274         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1275
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 );
1279
1280         new_filename = g_strconcat( path, "/", file, NULL );
1281         old_filename = g_strconcat( path, "/", fs->fileop_file, NULL );
1282
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 );
1287         }
1288         g_free( new_filename );
1289         g_free( old_filename );
1290
1291         gtk_widget_destroy( fs->fileop_dialog );
1292         gtk_file_selection_populate( fs, "", FALSE );
1293 }
1294
1295 static void
1296 gtk_file_selection_rename_file( GtkWidget *widget, gpointer data ){
1297         GtkFileSelection *fs = data;
1298         GtkWidget *label;
1299         GtkWidget *dialog;
1300         GtkWidget *vbox;
1301         GtkWidget *button;
1302         gchar *buf;
1303
1304         g_return_if_fail( fs != NULL );
1305         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1306
1307         if ( fs->fileop_dialog ) {
1308                 return;
1309         }
1310
1311         fs->fileop_file = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1312         if ( strlen( fs->fileop_file ) < 1 ) {
1313                 return;
1314         }
1315
1316         /* main dialog */
1317         fs->fileop_dialog = dialog = gtk_dialog_new();
1318         gtk_signal_connect( GTK_OBJECT( dialog ), "destroy",
1319                                                 (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1320                                                 (gpointer) fs );
1321         gtk_window_set_title( GTK_WINDOW( dialog ), _( "Rename File" ) );
1322         gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
1323
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 );
1328         }
1329
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,
1333                                                 FALSE, FALSE, 0 );
1334         gtk_widget_show( vbox );
1335
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 );
1341         g_free( buf );
1342
1343         /* New filename entry */
1344         fs->fileop_entry = gtk_entry_new();
1345         gtk_box_pack_start( GTK_BOX( vbox ), fs->fileop_entry,
1346                                                 TRUE, TRUE, 5 );
1347         GTK_WIDGET_SET_FLAGS( fs->fileop_entry, GTK_CAN_DEFAULT );
1348         gtk_widget_show( fs->fileop_entry );
1349
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 ) );
1353
1354         /* buttons */
1355         button = gtk_button_new_with_label( _( "Rename" ) );
1356         gtk_signal_connect( GTK_OBJECT( button ), "clicked",
1357                                                 (GtkSignalFunc) gtk_file_selection_rename_file_confirmed,
1358                                                 (gpointer) fs );
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 );
1363
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 );
1373
1374         gtk_widget_show( dialog );
1375 }
1376
1377
1378 static gint
1379 gtk_file_selection_key_press( GtkWidget   *widget,
1380                                                           GdkEventKey *event,
1381                                                           gpointer user_data ){
1382         GtkFileSelection *fs;
1383         char *text;
1384
1385         g_return_val_if_fail( widget != NULL, FALSE );
1386         g_return_val_if_fail( event != NULL, FALSE );
1387
1388         fs = GTK_FILE_SELECTION( user_data );
1389
1390         if ( event->keyval == GDK_Tab ) {
1391                 text = gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) );
1392
1393                 text = g_strdup( text );
1394
1395                 gtk_file_selection_populate( fs, text, TRUE );
1396
1397                 g_free( text );
1398
1399                 gtk_signal_emit_stop_by_name( GTK_OBJECT( widget ), "key_press_event" );
1400
1401                 return TRUE;
1402         }
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;
1408         }
1409
1410
1411         return FALSE;
1412 }
1413
1414 static void
1415 gtk_file_selection_home_button( GtkWidget *widget, gpointer data ){
1416         GList *list;
1417
1418         GtkFileSelection *fs = data;
1419
1420         list = fs->next_history;
1421         if ( list ) {
1422                 g_free( list->data );
1423                 list = list->next;
1424         }
1425         g_list_free( fs->next_history );
1426         fs->next_history = NULL;
1427
1428         gtk_file_selection_populate( fs,"~/",FALSE );
1429 }
1430
1431 static void
1432 gtk_file_selection_up_button( GtkWidget *widget, gpointer data ){
1433         GtkFileSelection *fs = data;
1434         GList *list;
1435
1436         g_return_if_fail( fs != NULL );
1437         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1438
1439         list = fs->next_history;
1440         if ( list ) {
1441                 g_free( list->data );
1442                 list = list->next;
1443         }
1444         g_list_free( fs->next_history );
1445         fs->next_history = NULL;
1446
1447         gtk_file_selection_populate( fs, "../", FALSE ); /*change directories. */
1448
1449 }
1450
1451 static void
1452 gtk_file_selection_prev_button( GtkWidget *widget, gpointer data ){
1453         GtkFileSelection *fs = data;
1454         GList *list;
1455         GList *first;
1456         gchar *path;
1457
1458         g_return_if_fail( fs != NULL );
1459         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1460
1461         list = fs->prev_history;
1462
1463         if ( list && g_list_length( list ) > 1 ) {
1464                 first = list;          /* get first element */
1465                 list = list->next;     /* pop off current directory */
1466
1467                 list->prev = NULL;     /* make this the new head. */
1468
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 */
1471
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) */
1474
1475
1476
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. */
1481                 g_free( path );
1482         }
1483 }
1484
1485 static void
1486 gtk_file_selection_next_button( GtkWidget *widget, gpointer data ){
1487         GtkFileSelection *fs = data;
1488         GList *list;
1489         GList *first;
1490         gchar *path;
1491
1492         g_return_if_fail( fs != NULL );
1493         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1494
1495         list = fs->next_history;
1496
1497         if ( list && g_list_length( list ) > 0 ) {
1498                 first = list;          /*get first element*/
1499                 list = list->next;     /*pop off current directory*/
1500
1501                 if ( list ) {
1502                         list->prev = NULL;
1503                 }
1504
1505                 fs->next_history = list;                     /*update prev_history list*/
1506
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.*/
1511                 g_free( path );
1512
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) */
1515
1516         }
1517 }
1518
1519 void static
1520 gtk_file_selection_refresh_button( GtkWidget *widget, gpointer data ){
1521         GtkFileSelection *fs = data;
1522
1523         g_return_if_fail( fs != NULL );
1524         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1525
1526         gtk_file_selection_populate( fs,"",FALSE );
1527 }
1528
1529 static void
1530 gtk_file_selection_mask_entry_callback( GtkWidget *widget, gpointer data ){
1531         GtkFileSelection *fs = data;
1532
1533         if ( fs->mask ) {
1534                 g_free( fs->mask );
1535         }
1536
1537         fs->mask = g_strdup( gtk_entry_get_text( GTK_ENTRY( fs->mask_entry ) ) );
1538
1539         if ( strlen( fs->mask ) == 0 ) {
1540                 g_free( fs->mask );
1541                 fs->mask = NULL;
1542         }
1543
1544         gtk_file_selection_refresh_button( widget,data );
1545 }
1546
1547 static gboolean gtk_file_selection_history_combo_list_key_handler( GtkWidget *widget,
1548                                                                                                                                    GdkEventKey *event,
1549                                                                                                                                    gpointer user_data ){
1550         /*
1551            g_print("Key pressed! \n");
1552          */
1553
1554         return TRUE;
1555 }
1556
1557 static gboolean gtk_file_selection_history_combo_list_callback( GtkWidget *thelist,
1558                                                                                                                                 GdkEventButton *event,
1559                                                                                                                                 gpointer user_data ){
1560
1561         GtkFileSelection *fs = user_data;
1562         GList *list;
1563         gchar *path;
1564
1565         list = fs->next_history;
1566         if ( list ) {
1567                 g_free( list->data );
1568                 list = list->next;
1569         }
1570         g_list_free( fs->next_history );
1571         fs->next_history = NULL;
1572
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 ) ) );
1575         strcat( path,"/" );
1576
1577         gtk_file_selection_populate( fs,path,TRUE );
1578
1579         g_free( path );
1580
1581         return TRUE;
1582 }
1583
1584 static gboolean
1585 gtk_file_selection_history_combo_callback( GtkWidget *widget, GdkEventKey *event, gpointer data ){
1586         GtkEntry *entry = (GtkEntry *)widget;
1587         GtkFileSelection *fs = data;
1588         GList *list;
1589         gchar *path;
1590
1591         g_return_val_if_fail( fs != NULL,FALSE );
1592         g_return_val_if_fail( GTK_IS_FILE_SELECTION( fs ),FALSE );
1593
1594
1595         if ( event->keyval == GDK_Return ) {
1596                 list = fs->next_history;
1597                 if ( list ) {
1598                         g_free( list->data );
1599                         list = list->next;
1600                 }
1601                 g_list_free( fs->next_history );
1602                 fs->next_history = NULL;
1603
1604                 path = g_malloc( strlen( gtk_entry_get_text( entry ) ) + 4 );
1605                 strcpy( path,gtk_entry_get_text( entry ) );
1606                 strcat( path,"/" );
1607                 gtk_file_selection_populate( fs,path,TRUE );
1608                 g_free( path );
1609                 gtk_signal_emit_stop_by_name( GTK_OBJECT( widget ), "key_press_event" );
1610                 return TRUE;
1611         }
1612         else
1613         {
1614                 return FALSE;
1615         }
1616
1617 }
1618
1619 static void
1620 gtk_file_selection_update_history_menu( GtkFileSelection *fs,
1621                                                                                 gchar *current_directory ){
1622         gchar *current_dir;
1623
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 );
1627
1628         current_dir = g_strdup( current_directory );
1629
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 ) );
1633                 }
1634         }
1635         else {
1636                 fs->prev_history = g_list_prepend( fs->prev_history,g_strdup( current_dir ) );
1637         }
1638
1639         gtk_combo_set_popdown_strings( GTK_COMBO( fs->history_combo ),fs->prev_history );
1640
1641         g_free( current_dir );
1642 }
1643
1644 static void
1645 gtk_file_selection_file_button( GtkWidget *widget,
1646                                                                 gint row,
1647                                                                 gint column,
1648                                                                 GdkEventButton *bevent,
1649                                                                 gpointer user_data ){
1650         GtkFileSelection *fs = NULL;
1651         gchar *filename, *temp = NULL;
1652
1653         g_return_if_fail( GTK_IS_CLIST( widget ) );
1654
1655         fs = user_data;
1656         g_return_if_fail( fs != NULL );
1657         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1658
1659         gtk_clist_get_text( GTK_CLIST( fs->file_list ), row, 0, &temp );
1660         filename = g_strdup( temp );
1661
1662         if ( filename ) {
1663                 if ( bevent ) {
1664                         switch ( bevent->type )
1665                         {
1666                         case GDK_2BUTTON_PRESS:
1667                                 gtk_button_clicked( GTK_BUTTON( fs->ok_button ) );
1668                                 break;
1669
1670                         default:
1671                                 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1672                                 break;
1673                         }
1674                 }
1675                 else{
1676                         gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1677                 }
1678
1679                 g_free( filename );
1680         }
1681 }
1682
1683 static void
1684 gtk_file_selection_dir_button( GtkWidget *widget,
1685                                                            gint row,
1686                                                            gint column,
1687                                                            GdkEventButton *bevent,
1688                                                            gpointer user_data ){
1689         GList *list;
1690         GtkFileSelection *fs = NULL;
1691         gchar *filename, *temp = NULL;
1692
1693         g_return_if_fail( GTK_IS_CLIST( widget ) );
1694
1695         fs = GTK_FILE_SELECTION( user_data );
1696         g_return_if_fail( fs != NULL );
1697         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1698
1699         gtk_clist_get_text( GTK_CLIST( fs->dir_list ), row, 0, &temp );
1700         filename = g_strdup( temp );
1701
1702         if ( filename ) {
1703                 if ( bevent ) {
1704                         switch ( bevent->type )
1705                         {
1706                         case GDK_2BUTTON_PRESS:
1707                                 list = fs->next_history;
1708                                 if ( list ) {
1709                                         g_free( list->data );
1710                                         list = list->next;
1711                                 }
1712                                 g_list_free( fs->next_history );
1713                                 fs->next_history = NULL;
1714
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;
1719                                 break;
1720
1721                         default:
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
1724                                  */
1725                                 if ( fs->saved_entry ) {
1726                                         g_free( fs->saved_entry );
1727                                 }
1728                                 fs->saved_entry = g_strdup( gtk_entry_get_text( GTK_ENTRY( fs->selection_entry ) ) );
1729
1730                                 temp = g_strconcat( filename,fs->saved_entry,NULL );
1731                                 gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), temp );
1732                                 g_free( temp );
1733
1734                                 break;
1735                         }
1736                 }
1737                 else{
1738                         gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename );
1739                 }
1740
1741                 g_free( filename );
1742         }
1743 }
1744
1745 static void
1746 gtk_file_selection_undir_button( GtkWidget *widget,
1747                                                                  gint row,
1748                                                                  gint column,
1749                                                                  GdkEventButton *bevent,
1750                                                                  gpointer user_data ){
1751         GtkFileSelection *fs = NULL;
1752         gchar *filename, *temp = NULL;
1753
1754         g_return_if_fail( GTK_IS_CLIST( widget ) );
1755
1756         fs = GTK_FILE_SELECTION( user_data );
1757         g_return_if_fail( fs != NULL );
1758         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1759
1760         gtk_clist_get_text( GTK_CLIST( fs->dir_list ), row, 0, &temp );
1761         filename = g_strdup( temp );
1762
1763         if ( filename ) {
1764                 if ( bevent ) {
1765                         switch ( bevent->type )
1766                         {
1767                         default:
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
1770                                  */
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;
1775                                 }
1776                                 break;
1777                         }
1778                 }
1779                 else{
1780                         gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ), filename ); //?????
1781
1782                 }
1783                 g_free( filename );
1784         }
1785 }
1786
1787 static void
1788 gtk_file_selection_populate( GtkFileSelection *fs,
1789                                                          gchar            *rel_path,
1790                                                          gint try_complete ){
1791         CompletionState *cmpl_state;
1792         PossibleCompletion* poss;
1793         gchar* filename;
1794         gint row;
1795         gchar* rem_path = rel_path;
1796         gchar* sel_text;
1797         gchar* text[2];
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;
1803
1804         g_return_if_fail( fs != NULL );
1805         g_return_if_fail( GTK_IS_FILE_SELECTION( fs ) );
1806
1807         cmpl_state = (CompletionState*) fs->cmpl_state;
1808         poss = cmpl_completion_matches( rel_path, &rem_path, cmpl_state );
1809
1810         if ( !cmpl_state_okay( cmpl_state ) ) {
1811                 /* Something went wrong. */
1812                 gtk_file_selection_abort( fs );
1813                 return;
1814         }
1815
1816         g_assert( cmpl_state->reference_dir );
1817
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 ) );
1822
1823         /* Set the dir_list to include ./ and ../ */
1824         text[1] = NULL;
1825         text[0] = "./";
1826         row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1827
1828         text[0] = "../";
1829         row = gtk_clist_append( GTK_CLIST( fs->dir_list ), text );
1830
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 );
1836
1837         while ( poss )
1838         {
1839                 if ( cmpl_is_a_completion( poss ) ) {
1840                         possible_count += 1;
1841
1842                         filename = cmpl_this_completion( poss );
1843
1844                         text[0] = filename;
1845
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,
1850                                                                                                   filename );
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,
1855                                                                                                         width );
1856                                         }
1857                                 }
1858                         }
1859                         else
1860                         {
1861                                 if ( fs->mask ) {
1862                                         if ( gtk_file_selection_match_mask( filename,fs->mask ) ) {
1863                                                 int width = gdk_string_width( fs->file_list->style->font,
1864                                                                                                           filename );
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,
1869                                                                                                                 width );
1870                                                 }
1871                                         }
1872                                 }
1873                                 else
1874                                 {
1875                                         int width = gdk_string_width( fs->file_list->style->font,
1876                                                                                                   filename );
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,
1881                                                                                                         width );
1882                                         }
1883                                 }
1884                         }
1885                 }
1886
1887                 poss = cmpl_next_completion( cmpl_state );
1888         }
1889
1890         gtk_clist_thaw( GTK_CLIST( fs->dir_list ) );
1891         gtk_clist_thaw( GTK_CLIST( fs->file_list ) );
1892
1893         /* File lists are set. */
1894
1895         g_assert( cmpl_state->reference_dir );
1896
1897         if ( try_complete ) {
1898
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. */
1903
1904                 if ( cmpl_updated_text( cmpl_state )[0] ) {
1905
1906                         if ( cmpl_updated_dir( cmpl_state ) ) {
1907                                 gchar* dir_name = g_strdup( cmpl_updated_text( cmpl_state ) );
1908
1909                                 did_recurse = TRUE;
1910
1911                                 gtk_file_selection_populate( fs, dir_name, TRUE );
1912
1913                                 g_free( dir_name );
1914                         }
1915                         else
1916                         {
1917                                 if ( fs->selection_entry ) {
1918                                         gtk_entry_set_text( GTK_ENTRY( fs->selection_entry ),
1919                                                                                 cmpl_updated_text( cmpl_state ) );
1920                                 }
1921                         }
1922                 }
1923                 else
1924                 {
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 );
1929                         }
1930                 }
1931         }
1932         else
1933         {
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), "");*/
1937                         ;
1938                 }
1939         }
1940
1941         if ( !did_recurse ) {
1942                 if ( fs->selection_entry ) {
1943                         gtk_entry_set_position( GTK_ENTRY( fs->selection_entry ), selection_index );
1944                 }
1945
1946                 if ( fs->selection_entry ) {
1947                         sel_text = g_strconcat( _( "Selection: " ),
1948                                                                         cmpl_reference_position( cmpl_state ),
1949                                                                         NULL );
1950
1951                         gtk_label_set_text( GTK_LABEL( fs->selection_text ), sel_text );
1952                         g_free( sel_text );
1953                 }
1954
1955                 gtk_file_selection_update_history_menu( fs, cmpl_reference_position( cmpl_state ) );
1956
1957         }
1958 }
1959
1960 static void
1961 gtk_file_selection_abort( GtkFileSelection *fs ){
1962         gchar err_buf[256];
1963
1964         sprintf( err_buf, _( "Directory unreadable: %s" ), cmpl_strerror( cmpl_errno ) );
1965
1966         /*  BEEP gdk_beep();  */
1967
1968         if ( fs->selection_entry ) {
1969                 gtk_label_set_text( GTK_LABEL( fs->selection_text ), err_buf );
1970         }
1971 }
1972
1973 /**********************************************************************/
1974 /*                        External Interface                          */
1975 /**********************************************************************/
1976
1977 /* The four completion state selectors
1978  */
1979 static gchar*
1980 cmpl_updated_text( CompletionState* cmpl_state ){
1981         return cmpl_state->updated_text;
1982 }
1983
1984 static gint
1985 cmpl_updated_dir( CompletionState* cmpl_state ){
1986         return cmpl_state->re_complete;
1987 }
1988
1989 static gchar*
1990 cmpl_reference_position( CompletionState* cmpl_state ){
1991         return cmpl_state->reference_dir->fullname;
1992 }
1993
1994 static gint
1995 cmpl_last_valid_char( CompletionState* cmpl_state ){
1996         return cmpl_state->last_valid_char;
1997 }
1998
1999 static gchar*
2000 cmpl_completion_fullname( gchar* text, CompletionState* cmpl_state ){
2001         static char nothing[2] = "";
2002
2003         if ( !cmpl_state_okay( cmpl_state ) ) {
2004                 return nothing;
2005         }
2006         else if ( text[0] == '/' ) {
2007                 strcpy( cmpl_state->updated_text, text );
2008         }
2009         else if ( text[0] == '~' ) {
2010                 CompletionDir* dir;
2011                 char* slash;
2012
2013                 dir = open_user_dir( text, cmpl_state );
2014
2015                 if ( !dir ) {
2016                         /* spencer says just return ~something, so
2017                          * for now just do it. */
2018                         strcpy( cmpl_state->updated_text, text );
2019                 }
2020                 else
2021                 {
2022
2023                         strcpy( cmpl_state->updated_text, dir->fullname );
2024
2025                         slash = strchr( text, '/' );
2026
2027                         if ( slash ) {
2028                                 strcat( cmpl_state->updated_text, slash );
2029                         }
2030                 }
2031         }
2032         else
2033         {
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, "/" );
2037                 }
2038                 strcat( cmpl_state->updated_text, text );
2039         }
2040
2041         return cmpl_state->updated_text;
2042 }
2043
2044 /* The three completion selectors
2045  */
2046 static gchar*
2047 cmpl_this_completion( PossibleCompletion* pc ){
2048         return pc->text;
2049 }
2050
2051 static gint
2052 cmpl_is_directory( PossibleCompletion* pc ){
2053         return pc->is_directory;
2054 }
2055
2056 static gint
2057 cmpl_is_a_completion( PossibleCompletion* pc ){
2058         return pc->is_a_completion;
2059 }
2060
2061 /**********************************************************************/
2062 /*                       Construction, deletion                       */
2063 /**********************************************************************/
2064
2065 static CompletionState*
2066 cmpl_init_state( void ){
2067         gchar getcwd_buf[2 * MAXPATHLEN];
2068         CompletionState *new_state;
2069
2070         new_state = g_new( CompletionState, 1 );
2071
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.
2074          */
2075 #if defined( sun ) && !defined( __SVR4 )
2076         if ( !getwd( getcwd_buf ) )
2077 #else
2078         if ( !getcwd( getcwd_buf, MAXPATHLEN ) )
2079 #endif
2080         {
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)
2083                  */
2084                 strcpy( getcwd_buf, "/" );
2085         }
2086
2087 tryagain:
2088
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;
2101
2102         new_state->reference_dir =  open_dir( getcwd_buf, new_state );
2103
2104         if ( !new_state->reference_dir ) {
2105                 /* Directories changing from underneath us, grumble */
2106                 strcpy( getcwd_buf, "/" );
2107                 goto tryagain;
2108         }
2109
2110         return new_state;
2111 }
2112
2113 static void
2114 cmpl_free_dir_list( GList* dp0 ){
2115         GList *dp = dp0;
2116
2117         while ( dp ) {
2118                 free_dir( dp->data );
2119                 dp = dp->next;
2120         }
2121
2122         g_list_free( dp0 );
2123 }
2124
2125 static void
2126 cmpl_free_dir_sent_list( GList* dp0 ){
2127         GList *dp = dp0;
2128
2129         while ( dp ) {
2130                 free_dir_sent( dp->data );
2131                 dp = dp->next;
2132         }
2133
2134         g_list_free( dp0 );
2135 }
2136
2137 static void
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 );
2141
2142         if ( cmpl_state->user_dir_name_buffer ) {
2143                 g_free( cmpl_state->user_dir_name_buffer );
2144         }
2145         if ( cmpl_state->user_directories ) {
2146                 g_free( cmpl_state->user_directories );
2147         }
2148         if ( cmpl_state->the_completion.text ) {
2149                 g_free( cmpl_state->the_completion.text );
2150         }
2151         if ( cmpl_state->updated_text ) {
2152                 g_free( cmpl_state->updated_text );
2153         }
2154
2155         g_free( cmpl_state );
2156 }
2157
2158 static void
2159 free_dir( CompletionDir* dir ){
2160         g_free( dir->fullname );
2161         g_free( dir );
2162 }
2163
2164 static void
2165 free_dir_sent( CompletionDirSent* sent ){
2166         g_free( sent->name_buffer );
2167         g_free( sent->entries );
2168         g_free( sent );
2169 }
2170
2171 static void
2172 prune_memory_usage( CompletionState *cmpl_state ){
2173         GList* cdsl = cmpl_state->directory_sent_storage;
2174         GList* cdl = cmpl_state->directory_storage;
2175         GList* cdl0 = cdl;
2176         gint len = 0;
2177
2178         for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1 )
2179                 cdsl = cdsl->next;
2180
2181         if ( cdsl ) {
2182                 cmpl_free_dir_sent_list( cdsl->next );
2183                 cdsl->next = NULL;
2184         }
2185
2186         cmpl_state->directory_storage = NULL;
2187         while ( cdl ) {
2188                 if ( cdl->data == cmpl_state->reference_dir ) {
2189                         cmpl_state->directory_storage = g_list_prepend( NULL, cdl->data );
2190                 }
2191                 else{
2192                         free_dir( cdl->data );
2193                 }
2194                 cdl = cdl->next;
2195         }
2196
2197         g_list_free( cdl0 );
2198 }
2199
2200 /**********************************************************************/
2201 /*                        The main entrances.                         */
2202 /**********************************************************************/
2203
2204 static PossibleCompletion*
2205 cmpl_completion_matches( gchar* text_to_complete,
2206                                                  gchar** remaining_text,
2207                                                  CompletionState* cmpl_state ){
2208         gchar* first_slash;
2209         PossibleCompletion *poss;
2210
2211         prune_memory_usage( cmpl_state );
2212
2213         g_assert( text_to_complete != NULL );
2214
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;
2222
2223         first_slash = strchr( text_to_complete, '/' );
2224
2225         if ( text_to_complete[0] == '~' && !first_slash ) {
2226                 /* Text starts with ~ and there is no slash, show all the
2227                  * home directory completions.
2228                  */
2229                 poss = attempt_homedir_completion( text_to_complete, cmpl_state );
2230
2231                 update_cmpl( poss, cmpl_state );
2232
2233                 return poss;
2234         }
2235
2236         cmpl_state->reference_dir =
2237                 open_ref_dir( text_to_complete, remaining_text, cmpl_state );
2238
2239         if ( !cmpl_state->reference_dir ) {
2240                 return NULL;
2241         }
2242
2243         cmpl_state->completion_dir =
2244                 find_completion_dir( *remaining_text, remaining_text, cmpl_state );
2245
2246         cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2247
2248         if ( !cmpl_state->completion_dir ) {
2249                 return NULL;
2250         }
2251
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;
2255
2256         cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2257
2258         cmpl_state->reference_dir = cmpl_state->completion_dir;
2259
2260         poss = attempt_file_completion( cmpl_state );
2261
2262         update_cmpl( poss, cmpl_state );
2263
2264         return poss;
2265 }
2266
2267 static PossibleCompletion*
2268 cmpl_next_completion( CompletionState* cmpl_state ){
2269         PossibleCompletion* poss = NULL;
2270
2271         cmpl_state->the_completion.text[0] = 0;
2272
2273         if ( cmpl_state->user_completion_index >= 0 ) {
2274                 poss = attempt_homedir_completion( cmpl_state->last_completion_text, cmpl_state );
2275         }
2276         else{
2277                 poss = attempt_file_completion( cmpl_state );
2278         }
2279
2280         update_cmpl( poss, cmpl_state );
2281
2282         return poss;
2283 }
2284
2285 /**********************************************************************/
2286 /*                       Directory Operations                         */
2287 /**********************************************************************/
2288
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 ){
2294         gchar* first_slash;
2295         CompletionDir *new_dir;
2296
2297         first_slash = strchr( text_to_complete, '/' );
2298
2299         if ( text_to_complete[0] == '~' ) {
2300                 new_dir = open_user_dir( text_to_complete, cmpl_state );
2301
2302                 if ( new_dir ) {
2303                         if ( first_slash ) {
2304                                 *remaining_text = first_slash + 1;
2305                         }
2306                         else{
2307                                 *remaining_text = text_to_complete + strlen( text_to_complete );
2308                         }
2309                 }
2310                 else
2311                 {
2312                         return NULL;
2313                 }
2314         }
2315         else if ( text_to_complete[0] == '/' || !cmpl_state->reference_dir ) {
2316                 gchar *tmp = g_strdup( text_to_complete );
2317                 gchar *p;
2318
2319                 p = tmp;
2320                 while ( *p && *p != '*' && *p != '?' )
2321                         p++;
2322
2323                 *p = '\0';
2324                 p = strrchr( tmp, '/' );
2325                 if ( p ) {
2326                         if ( p == tmp ) {
2327                                 p++;
2328                         }
2329
2330                         *p = '\0';
2331
2332                         new_dir = open_dir( tmp, cmpl_state );
2333
2334                         if ( new_dir ) {
2335                                 *remaining_text = text_to_complete +
2336                                                                   ( ( p == tmp + 1 ) ? ( p - tmp ) : ( p + 1 - tmp ) );
2337                         }
2338                 }
2339                 else
2340                 {
2341                         /* If no possible candidates, use the cwd */
2342                         gchar *curdir = g_get_current_dir();
2343
2344                         new_dir = open_dir( curdir, cmpl_state );
2345
2346                         if ( new_dir ) {
2347                                 *remaining_text = text_to_complete;
2348                         }
2349
2350                         g_free( curdir );
2351                 }
2352
2353                 g_free( tmp );
2354         }
2355         else
2356         {
2357                 *remaining_text = text_to_complete;
2358
2359                 new_dir = open_dir( cmpl_state->reference_dir->fullname, cmpl_state );
2360         }
2361
2362         if ( new_dir ) {
2363                 new_dir->cmpl_index = -1;
2364                 new_dir->cmpl_parent = NULL;
2365         }
2366
2367         return new_dir;
2368 }
2369
2370 /* open a directory by user name */
2371 static CompletionDir*
2372 open_user_dir( gchar* text_to_complete,
2373                            CompletionState *cmpl_state ){
2374         gchar *first_slash;
2375         gint cmp_len;
2376
2377         g_assert( text_to_complete && text_to_complete[0] == '~' );
2378
2379         first_slash = strchr( text_to_complete, '/' );
2380
2381         if ( first_slash ) {
2382                 cmp_len = first_slash - text_to_complete - 1;
2383         }
2384         else{
2385                 cmp_len = strlen( text_to_complete + 1 );
2386         }
2387
2388         if ( !cmp_len ) {
2389                 /* ~/ */
2390                 gchar *homedir = g_get_home_dir();
2391
2392                 if ( homedir ) {
2393                         return open_dir( homedir, cmpl_state );
2394                 }
2395                 else{
2396                         return NULL;
2397                 }
2398         }
2399         else
2400         {
2401                 /* ~user/ */
2402                 char* copy = g_new( char, cmp_len + 1 );
2403                 struct passwd *pwd;
2404                 strncpy( copy, text_to_complete + 1, cmp_len );
2405                 copy[cmp_len] = 0;
2406                 pwd = getpwnam( copy );
2407                 g_free( copy );
2408                 if ( !pwd ) {
2409                         cmpl_errno = errno;
2410                         return NULL;
2411                 }
2412
2413                 return open_dir( pwd->pw_dir, cmpl_state );
2414         }
2415 }
2416
2417 /* open a directory relative the the current relative directory */
2418 static CompletionDir*
2419 open_relative_dir( gchar* dir_name,
2420                                    CompletionDir* dir,
2421                                    CompletionState *cmpl_state ){
2422         gchar path_buf[2 * MAXPATHLEN];
2423
2424         if ( dir->fullname_len + strlen( dir_name ) + 2 >= MAXPATHLEN ) {
2425                 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2426                 return NULL;
2427         }
2428
2429         strcpy( path_buf, dir->fullname );
2430
2431         if ( dir->fullname_len > 1 ) {
2432                 path_buf[dir->fullname_len] = '/';
2433                 strcpy( path_buf + dir->fullname_len + 1, dir_name );
2434         }
2435         else
2436         {
2437                 strcpy( path_buf + dir->fullname_len, dir_name );
2438         }
2439
2440         return open_dir( path_buf, cmpl_state );
2441 }
2442
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;
2447         DIR* directory;
2448         gchar *buffer_ptr;
2449         struct dirent *dirent_ptr;
2450         gint buffer_size = 0;
2451         gint entry_count = 0;
2452         gint i;
2453         struct stat ent_sbuf;
2454         char path_buf[MAXPATHLEN * 2];
2455         gint path_buf_len;
2456
2457         sent = g_new( CompletionDirSent, 1 );
2458         sent->mtime = sbuf->st_mtime;
2459         sent->inode = sbuf->st_ino;
2460         sent->device = sbuf->st_dev;
2461
2462         path_buf_len = strlen( dir_name );
2463
2464         if ( path_buf_len > MAXPATHLEN ) {
2465                 cmpl_errno = CMPL_ERRNO_TOO_LONG;
2466                 return NULL;
2467         }
2468
2469         strcpy( path_buf, dir_name );
2470
2471         directory = opendir( dir_name );
2472
2473         if ( !directory ) {
2474                 cmpl_errno = errno;
2475                 return NULL;
2476         }
2477
2478         while ( ( dirent_ptr = readdir( directory ) ) != NULL )
2479         {
2480                 int entry_len = strlen( dirent_ptr->d_name );
2481                 buffer_size += entry_len + 1;
2482                 entry_count += 1;
2483
2484                 if ( path_buf_len + entry_len + 2 >= MAXPATHLEN ) {
2485                         cmpl_errno = CMPL_ERRNO_TOO_LONG;
2486                         closedir( directory );
2487                         return NULL;
2488                 }
2489         }
2490
2491         sent->name_buffer = g_new( gchar, buffer_size );
2492         sent->entries = g_new( CompletionDirEntry, entry_count );
2493         sent->entry_count = entry_count;
2494
2495         buffer_ptr = sent->name_buffer;
2496
2497         rewinddir( directory );
2498
2499         for ( i = 0; i < entry_count; i += 1 )
2500         {
2501                 dirent_ptr = readdir( directory );
2502
2503                 if ( !dirent_ptr ) {
2504                         cmpl_errno = errno;
2505                         closedir( directory );
2506                         return NULL;
2507                 }
2508
2509                 strcpy( buffer_ptr, dirent_ptr->d_name );
2510                 sent->entries[i].entry_name = buffer_ptr;
2511                 buffer_ptr += strlen( dirent_ptr->d_name );
2512                 *buffer_ptr = 0;
2513                 buffer_ptr += 1;
2514
2515                 path_buf[path_buf_len] = '/';
2516                 strcpy( path_buf + path_buf_len + 1, dirent_ptr->d_name );
2517
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;
2521                         }
2522                         else{
2523                                 /* stat may fail, and we don't mind, since it could be a
2524                                  * dangling symlink. */
2525                                 sent->entries[i].is_dir = 0;
2526                         }
2527                 }
2528                 else{
2529                         sent->entries[i].is_dir = 1;
2530                 }
2531         }
2532
2533         qsort( sent->entries, sent->entry_count, sizeof( CompletionDirEntry ), compare_cmpl_dir );
2534
2535         closedir( directory );
2536
2537         return sent;
2538 }
2539
2540 static gboolean
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
2544          * expensive.
2545          */
2546
2547         static struct {
2548                 gchar *name;
2549                 gboolean present;
2550                 struct stat statbuf;
2551         } no_stat_dirs[] = {
2552                 { "/afs", FALSE, { 0 } },
2553                 { "/net", FALSE, { 0 } }
2554         };
2555
2556         static const gint n_no_stat_dirs = sizeof( no_stat_dirs ) / sizeof( no_stat_dirs[0] );
2557         static gboolean initialized = FALSE;
2558
2559         gint i;
2560
2561         if ( !initialized ) {
2562                 initialized = TRUE;
2563                 for ( i = 0; i < n_no_stat_dirs; i++ )
2564                 {
2565                         if ( stat( no_stat_dirs[i].name, &no_stat_dirs[i].statbuf ) == 0 ) {
2566                                 no_stat_dirs[i].present = TRUE;
2567                         }
2568                 }
2569         }
2570
2571         if ( stat( dir_name, result ) < 0 ) {
2572                 cmpl_errno = errno;
2573                 return FALSE;
2574         }
2575
2576         *stat_subdirs = TRUE;
2577         for ( i = 0; i < n_no_stat_dirs; i++ )
2578         {
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;
2583                         break;
2584                 }
2585         }
2586
2587         return TRUE;
2588 }
2589
2590 /* open a directory by absolute pathname */
2591 static CompletionDir*
2592 open_dir( gchar* dir_name, CompletionState* cmpl_state ){
2593         struct stat sbuf;
2594         gboolean stat_subdirs;
2595         CompletionDirSent *sent;
2596         GList* cdsl;
2597
2598         if ( !check_dir( dir_name, &sbuf, &stat_subdirs ) ) {
2599                 return NULL;
2600         }
2601
2602         cdsl = cmpl_state->directory_sent_storage;
2603
2604         while ( cdsl )
2605         {
2606                 sent = cdsl->data;
2607
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 );
2612                 }
2613
2614                 cdsl = cdsl->next;
2615         }
2616
2617         sent = open_new_dir( dir_name, &sbuf, stat_subdirs );
2618
2619         if ( sent ) {
2620                 cmpl_state->directory_sent_storage =
2621                         g_list_prepend( cmpl_state->directory_sent_storage, sent );
2622
2623                 return attach_dir( sent, dir_name, cmpl_state );
2624         }
2625
2626         return NULL;
2627 }
2628
2629 static CompletionDir*
2630 attach_dir( CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state ){
2631         CompletionDir* new_dir;
2632
2633         new_dir = g_new( CompletionDir, 1 );
2634
2635         cmpl_state->directory_storage =
2636                 g_list_prepend( cmpl_state->directory_storage, new_dir );
2637
2638         new_dir->sent = sent;
2639         new_dir->fullname = g_strdup( dir_name );
2640         new_dir->fullname_len = strlen( dir_name );
2641
2642         return new_dir;
2643 }
2644
2645 static gint
2646 correct_dir_fullname( CompletionDir* cmpl_dir ){
2647         gint length = strlen( cmpl_dir->fullname );
2648         struct stat sbuf;
2649
2650         if ( strcmp( cmpl_dir->fullname + length - 2, "/." ) == 0 ) {
2651                 if ( length == 2 ) {
2652                         strcpy( cmpl_dir->fullname, "/" );
2653                         cmpl_dir->fullname_len = 1;
2654                         return TRUE;
2655                 }
2656                 else {
2657                         cmpl_dir->fullname[length - 2] = 0;
2658                 }
2659         }
2660         else if ( strcmp( cmpl_dir->fullname + length - 3, "/./" ) == 0 ) {
2661                 cmpl_dir->fullname[length - 2] = 0;
2662         }
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;
2667                         return TRUE;
2668                 }
2669
2670                 if ( stat( cmpl_dir->fullname, &sbuf ) < 0 ) {
2671                         cmpl_errno = errno;
2672                         return FALSE;
2673                 }
2674
2675                 cmpl_dir->fullname[length - 2] = 0;
2676
2677                 if ( !correct_parent( cmpl_dir, &sbuf ) ) {
2678                         return FALSE;
2679                 }
2680         }
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;
2685                         return TRUE;
2686                 }
2687
2688                 if ( stat( cmpl_dir->fullname, &sbuf ) < 0 ) {
2689                         cmpl_errno = errno;
2690                         return FALSE;
2691                 }
2692
2693                 cmpl_dir->fullname[length - 3] = 0;
2694
2695                 if ( !correct_parent( cmpl_dir, &sbuf ) ) {
2696                         return FALSE;
2697                 }
2698         }
2699
2700         cmpl_dir->fullname_len = strlen( cmpl_dir->fullname );
2701
2702         return TRUE;
2703 }
2704
2705 static gint
2706 correct_parent( CompletionDir* cmpl_dir, struct stat *sbuf ){
2707         struct stat parbuf;
2708         gchar *last_slash;
2709         gchar *new_name;
2710         gchar c = 0;
2711
2712         last_slash = strrchr( cmpl_dir->fullname, '/' );
2713
2714         g_assert( last_slash );
2715
2716         if ( last_slash != cmpl_dir->fullname ) { /* last_slash[0] = 0; */
2717         }
2718         else
2719         {
2720                 c = last_slash[1];
2721                 last_slash[1] = 0;
2722         }
2723
2724         if ( stat( cmpl_dir->fullname, &parbuf ) < 0 ) {
2725                 cmpl_errno = errno;
2726                 return FALSE;
2727         }
2728
2729         if ( parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev ) {
2730                 /* it wasn't a link */
2731                 return TRUE;
2732         }
2733
2734         if ( c ) {
2735                 last_slash[1] = c;
2736         }
2737         /* else
2738            last_slash[0] = '/'; */
2739
2740         /* it was a link, have to figure it out the hard way */
2741
2742         new_name = find_parent_dir_fullname( cmpl_dir->fullname );
2743
2744         if ( !new_name ) {
2745                 return FALSE;
2746         }
2747
2748         g_free( cmpl_dir->fullname );
2749
2750         cmpl_dir->fullname = new_name;
2751
2752         return TRUE;
2753 }
2754
2755 static gchar*
2756 find_parent_dir_fullname( gchar* dirname ){
2757         gchar buffer[MAXPATHLEN];
2758         gchar buffer2[MAXPATHLEN];
2759
2760 #if defined( sun ) && !defined( __SVR4 )
2761         if ( !getwd( buffer ) )
2762 #else
2763         if ( !getcwd( buffer, MAXPATHLEN ) )
2764 #endif
2765         {
2766                 cmpl_errno = errno;
2767                 return NULL;
2768         }
2769
2770         if ( chdir( dirname ) != 0 || chdir( ".." ) != 0 ) {
2771                 cmpl_errno = errno;
2772                 return NULL;
2773         }
2774
2775 #if defined( sun ) && !defined( __SVR4 )
2776         if ( !getwd( buffer2 ) )
2777 #else
2778         if ( !getcwd( buffer2, MAXPATHLEN ) )
2779 #endif
2780         {
2781                 chdir( buffer );
2782                 cmpl_errno = errno;
2783
2784                 return NULL;
2785         }
2786
2787         if ( chdir( buffer ) != 0 ) {
2788                 cmpl_errno = errno;
2789                 return NULL;
2790         }
2791
2792         return g_strdup( buffer2 );
2793 }
2794
2795 /**********************************************************************/
2796 /*                        Completion Operations                       */
2797 /**********************************************************************/
2798
2799 static PossibleCompletion*
2800 attempt_homedir_completion( gchar* text_to_complete,
2801                                                         CompletionState *cmpl_state ){
2802         gint index, length;
2803
2804         if ( !cmpl_state->user_dir_name_buffer &&
2805                  !get_pwdb( cmpl_state ) ) {
2806                 return NULL;
2807         }
2808         length = strlen( text_to_complete ) - 1;
2809
2810         cmpl_state->user_completion_index += 1;
2811
2812         while ( cmpl_state->user_completion_index < cmpl_state->user_directories_len )
2813         {
2814                 index = first_diff_index( text_to_complete + 1,
2815                                                                   cmpl_state->user_directories
2816                                                                   [cmpl_state->user_completion_index].login );
2817
2818                 switch ( index )
2819                 {
2820                 case PATTERN_MATCH:
2821                         break;
2822                 default:
2823                         if ( cmpl_state->last_valid_char < ( index + 1 ) ) {
2824                                 cmpl_state->last_valid_char = index + 1;
2825                         }
2826                         cmpl_state->user_completion_index += 1;
2827                         continue;
2828                 }
2829
2830                 cmpl_state->the_completion.is_a_completion = 1;
2831                 cmpl_state->the_completion.is_directory = 1;
2832
2833                 append_completion_text( "~", cmpl_state );
2834
2835                 append_completion_text( cmpl_state->
2836                                                                 user_directories[cmpl_state->user_completion_index].login,
2837                                                                 cmpl_state );
2838
2839                 return append_completion_text( "/", cmpl_state );
2840         }
2841
2842         if ( text_to_complete[1] ||
2843                  cmpl_state->user_completion_index > cmpl_state->user_directories_len ) {
2844                 cmpl_state->user_completion_index = -1;
2845                 return NULL;
2846         }
2847         else
2848         {
2849                 cmpl_state->user_completion_index += 1;
2850                 cmpl_state->the_completion.is_a_completion = 1;
2851                 cmpl_state->the_completion.is_directory = 1;
2852
2853                 return append_completion_text( "~/", cmpl_state );
2854         }
2855 }
2856
2857 /* returns the index (>= 0) of the first differing character,
2858  * PATTERN_MATCH if the completion matches */
2859 static gint
2860 first_diff_index( gchar* pat, gchar* text ){
2861         gint diff = 0;
2862
2863         while ( *pat && *text && *text == *pat )
2864         {
2865                 pat += 1;
2866                 text += 1;
2867                 diff += 1;
2868         }
2869
2870         if ( *pat ) {
2871                 return diff;
2872         }
2873
2874         return PATTERN_MATCH;
2875 }
2876
2877 static PossibleCompletion*
2878 append_completion_text( gchar* text, CompletionState* cmpl_state ){
2879         gint len, i = 1;
2880
2881         if ( !cmpl_state->the_completion.text ) {
2882                 return NULL;
2883         }
2884
2885         len = strlen( text ) + strlen( cmpl_state->the_completion.text ) + 1;
2886
2887         if ( cmpl_state->the_completion.text_alloc > len ) {
2888                 strcat( cmpl_state->the_completion.text, text );
2889                 return &cmpl_state->the_completion;
2890         }
2891
2892         while ( i < len ) { i <<= 1; }
2893
2894         cmpl_state->the_completion.text_alloc = i;
2895
2896         cmpl_state->the_completion.text = (gchar*)g_realloc( cmpl_state->the_completion.text, i );
2897
2898         if ( !cmpl_state->the_completion.text ) {
2899                 return NULL;
2900         }
2901         else
2902         {
2903                 strcat( cmpl_state->the_completion.text, text );
2904                 return &cmpl_state->the_completion;
2905         }
2906 }
2907
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;
2916
2917         while ( first_slash )
2918         {
2919                 gint len = first_slash - *remaining_text;
2920                 gint found = 0;
2921                 gchar *found_name = NULL;       /* Quiet gcc */
2922                 gint i;
2923                 gchar* pat_buf = g_new( gchar, len + 1 );
2924
2925                 strncpy( pat_buf, *remaining_text, len );
2926                 pat_buf[len] = 0;
2927
2928                 for ( i = 0; i < dir->sent->entry_count; i += 1 )
2929                 {
2930                         if ( dir->sent->entries[i].is_dir &&
2931                                  fnmatch( pat_buf, dir->sent->entries[i].entry_name,
2932                                                   FNMATCH_FLAGS ) != FNM_NOMATCH ) {
2933                                 if ( found ) {
2934                                         g_free( pat_buf );
2935                                         return dir;
2936                                 }
2937                                 else
2938                                 {
2939                                         found = 1;
2940                                         found_name = dir->sent->entries[i].entry_name;
2941                                 }
2942                         }
2943                 }
2944
2945                 if ( !found ) {
2946                         /* Perhaps we are trying to open an automount directory */
2947                         found_name = pat_buf;
2948                 }
2949
2950                 next = open_relative_dir( found_name, dir, cmpl_state );
2951
2952                 if ( !next ) {
2953                         g_free( pat_buf );
2954                         return NULL;
2955                 }
2956
2957                 next->cmpl_parent = dir;
2958
2959                 dir = next;
2960
2961                 if ( !correct_dir_fullname( dir ) ) {
2962                         g_free( pat_buf );
2963                         return NULL;
2964                 }
2965
2966                 *remaining_text = first_slash + 1;
2967                 first_slash = strchr( *remaining_text, '/' );
2968
2969                 g_free( pat_buf );
2970         }
2971
2972         return dir;
2973 }
2974
2975 static void
2976 update_cmpl( PossibleCompletion* poss, CompletionState* cmpl_state ){
2977         gint cmpl_len;
2978
2979         if ( !poss || !cmpl_is_a_completion( poss ) ) {
2980                 return;
2981         }
2982
2983         cmpl_len = strlen( cmpl_this_completion( poss ) );
2984
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;
2990         }
2991
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 );
2996         }
2997         else if ( cmpl_state->updated_text_len == 0 ) {
2998                 cmpl_state->re_complete = FALSE;
2999         }
3000         else
3001         {
3002                 gint first_diff =
3003                         first_diff_index( cmpl_state->updated_text,
3004                                                           cmpl_this_completion( poss ) );
3005
3006                 cmpl_state->re_complete = FALSE;
3007
3008                 if ( first_diff == PATTERN_MATCH ) {
3009                         return;
3010                 }
3011
3012                 if ( first_diff > cmpl_state->updated_text_len ) {
3013                         strcpy( cmpl_state->updated_text, cmpl_this_completion( poss ) );
3014                 }
3015
3016                 cmpl_state->updated_text_len = first_diff;
3017                 cmpl_state->updated_text[first_diff] = 0;
3018         }
3019 }
3020
3021 static PossibleCompletion*
3022 attempt_file_completion( CompletionState *cmpl_state ){
3023         gchar *pat_buf, *first_slash;
3024         CompletionDir *dir = cmpl_state->active_completion_dir;
3025
3026         dir->cmpl_index += 1;
3027
3028         if ( dir->cmpl_index == dir->sent->entry_count ) {
3029                 if ( dir->cmpl_parent == NULL ) {
3030                         cmpl_state->active_completion_dir = NULL;
3031
3032                         return NULL;
3033                 }
3034                 else
3035                 {
3036                         cmpl_state->active_completion_dir = dir->cmpl_parent;
3037
3038                         return attempt_file_completion( cmpl_state );
3039                 }
3040         }
3041
3042         g_assert( dir->cmpl_text );
3043
3044         first_slash = strchr( dir->cmpl_text, '/' );
3045
3046         if ( first_slash ) {
3047                 gint len = first_slash - dir->cmpl_text;
3048
3049                 pat_buf = g_new( gchar, len + 1 );
3050                 strncpy( pat_buf, dir->cmpl_text, len );
3051                 pat_buf[len] = 0;
3052         }
3053         else
3054         {
3055                 gint len = strlen( dir->cmpl_text );
3056
3057                 pat_buf = g_new( gchar, len + 2 );
3058                 strcpy( pat_buf, dir->cmpl_text );
3059                 strcpy( pat_buf + len, "*" );
3060         }
3061
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;
3067
3068                                 new_dir = open_relative_dir( dir->sent->entries[dir->cmpl_index].entry_name,
3069                                                                                          dir, cmpl_state );
3070
3071                                 if ( !new_dir ) {
3072                                         g_free( pat_buf );
3073                                         return NULL;
3074                                 }
3075
3076                                 new_dir->cmpl_parent = dir;
3077
3078                                 new_dir->cmpl_index = -1;
3079                                 new_dir->cmpl_text = first_slash + 1;
3080
3081                                 cmpl_state->active_completion_dir = new_dir;
3082
3083                                 g_free( pat_buf );
3084                                 return attempt_file_completion( cmpl_state );
3085                         }
3086                         else
3087                         {
3088                                 g_free( pat_buf );
3089                                 return attempt_file_completion( cmpl_state );
3090                         }
3091                 }
3092                 else
3093                 {
3094                         g_free( pat_buf );
3095                         return attempt_file_completion( cmpl_state );
3096                 }
3097         }
3098         else
3099         {
3100                 if ( dir->cmpl_parent != NULL ) {
3101                         append_completion_text( dir->fullname +
3102                                                                         strlen( cmpl_state->completion_dir->fullname ) + 1,
3103                                                                         cmpl_state );
3104                         append_completion_text( "/", cmpl_state );
3105                 }
3106
3107                 append_completion_text( dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state );
3108
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 );
3112
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 );
3116                 }
3117
3118                 g_free( pat_buf );
3119                 return &cmpl_state->the_completion;
3120         }
3121 }
3122
3123
3124 static gint
3125 get_pwdb( CompletionState* cmpl_state ){
3126         struct passwd *pwd_ptr;
3127         gchar* buf_ptr;
3128         gint len = 0, i, count = 0;
3129
3130         if ( cmpl_state->user_dir_name_buffer ) {
3131                 return TRUE;
3132         }
3133         setpwent();
3134
3135         while ( ( pwd_ptr = getpwent() ) != NULL )
3136         {
3137                 len += strlen( pwd_ptr->pw_name );
3138                 len += strlen( pwd_ptr->pw_dir );
3139                 len += 2;
3140                 count += 1;
3141         }
3142
3143         setpwent();
3144
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;
3148
3149         buf_ptr = cmpl_state->user_dir_name_buffer;
3150
3151         for ( i = 0; i < count; i += 1 )
3152         {
3153                 pwd_ptr = getpwent();
3154                 if ( !pwd_ptr ) {
3155                         cmpl_errno = errno;
3156                         goto error;
3157                 }
3158
3159                 strcpy( buf_ptr, pwd_ptr->pw_name );
3160                 cmpl_state->user_directories[i].login = buf_ptr;
3161                 buf_ptr += strlen( buf_ptr );
3162                 buf_ptr += 1;
3163                 strcpy( buf_ptr, pwd_ptr->pw_dir );
3164                 cmpl_state->user_directories[i].homedir = buf_ptr;
3165                 buf_ptr += strlen( buf_ptr );
3166                 buf_ptr += 1;
3167         }
3168
3169         qsort( cmpl_state->user_directories,
3170                    cmpl_state->user_directories_len,
3171                    sizeof( CompletionUserDir ),
3172                    compare_user_dir );
3173
3174         endpwent();
3175
3176         return TRUE;
3177
3178 error:
3179
3180         if ( cmpl_state->user_dir_name_buffer ) {
3181                 g_free( cmpl_state->user_dir_name_buffer );
3182         }
3183         if ( cmpl_state->user_directories ) {
3184                 g_free( cmpl_state->user_directories );
3185         }
3186
3187         cmpl_state->user_dir_name_buffer = NULL;
3188         cmpl_state->user_directories = NULL;
3189
3190         return FALSE;
3191 }
3192
3193 static gint
3194 compare_user_dir( const void* a, const void* b ){
3195         return strcmp( ( ( (CompletionUserDir*)a ) )->login,
3196                                    ( ( (CompletionUserDir*)b ) )->login );
3197 }
3198
3199 static gint
3200 compare_cmpl_dir( const void* a, const void* b ){
3201         return strcmp( ( ( (CompletionDirEntry*)a ) )->entry_name,
3202                                    ( ( (CompletionDirEntry*)b ) )->entry_name );
3203 }
3204
3205 static gint
3206 cmpl_state_okay( CompletionState* cmpl_state ){
3207         return cmpl_state && cmpl_state->reference_dir;
3208 }
3209
3210 static gchar*
3211 cmpl_strerror( gint err ){
3212         if ( err == CMPL_ERRNO_TOO_LONG ) {
3213                 return "Name too long";
3214         }
3215         else{
3216                 return g_strerror( err );
3217         }
3218 }
3219
3220
3221 /* Testing area */
3222 #ifdef TORRIE_DEBUG
3223
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 ) ) );
3228 }
3229
3230 void destroy( GtkWidget *widget,
3231                           gpointer data ){
3232         gtk_main_quit();
3233 }
3234
3235 int main( int argc,
3236                   char *argv[] ){
3237         GtkWidget *filew;
3238
3239         gtk_init( &argc, &argv );
3240
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");
3244
3245
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 );
3251
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 ) );
3257
3258
3259         gtk_widget_show( filew );
3260
3261 /*
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"));
3271  */
3272         gtk_main();
3273
3274         return 0;
3275 }
3276
3277 /* example-end */
3278 #endif