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 #include <stdio.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/param.h>
31 #include <dirent.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <pwd.h>
37 #include "fnmatch.h"
38 
39 #include "config.h"
40 
41 //#ifdef DISABLE_NLS
42 //#define _(Text) Text
43 //#else
44 //#include <libintl.h>
45 //#define _(Text) gettext(Text)
46 //#endif
47 
48 #include <gdk/gdkkeysyms.h>
49 #include <gtk/gtkbutton.h>
50 #include <gtk/gtkentry.h>
51 #include "gtkfilesel.h"
52 #include <gtk/gtkhbox.h>
53 #include <gtk/gtkhbbox.h>
54 #include <gtk/gtklabel.h>
55 #include <gtk/gtklist.h>
56 #include <gtk/gtklistitem.h>
57 #include <gtk/gtkmain.h>
58 #include <gtk/gtkscrolledwindow.h>
59 #include <gtk/gtksignal.h>
60 #include <gtk/gtkvbox.h>
61 #include <gtk/gtkmenu.h>
62 #include <gtk/gtkmenuitem.h>
63 #include <gtk/gtkoptionmenu.h>
64 #include <gtk/gtkclist.h>
65 #include <gtk/gtkdialog.h>
66 #include <gtk/gtkcombo.h>
67 #include <gtk/gtkframe.h>
68 #include <gtk/gtkpixmap.h>
69 #include "gtkintl.h"
70 
71 /* The Hurd doesn't define either PATH_MAX or MAXPATHLEN, so we put this *
72  * in here, since the rest of the code in the file does require some     *
73  * fixed maximum                                                         *
74  */
75 
76 #ifndef MAXPATHLEN
77 #  ifdef PATH_MAX
78 #    define MAXPATHLEN PATH_MAX
79 #  else
80 #    define MAXPATHLEN 2048
81 #  endif
82 #endif
83 
84 
85 #define DIR_LIST_WIDTH   180
86 #define DIR_LIST_HEIGHT  180
87 #define FILE_LIST_WIDTH  180
88 #define FILE_LIST_HEIGHT 180
89 
90 /* I've put this here so it doesn't get confused with the
91  * file completion interface */
92 typedef struct _HistoryCallbackArg HistoryCallbackArg;
93 
94 struct _HistoryCallbackArg
95 {
96   gchar *directory;
97   GtkWidget *menu_item;
98 };
99 
100 
101 typedef struct _CompletionState CompletionState;
102 typedef struct _CompletionDir CompletionDir;
103 typedef struct _CompletionDirSent CompletionDirSent;
104 typedef struct _CompletionDirEntry CompletionDirEntry;
105 typedef struct _CompletionUserDir CompletionUserDir;
106 typedef struct _PossibleCompletion PossibleCompletion;
107 
108 /* Non-external file completion decls and structures */
109 
110 /* A contant telling PRCS how many directories to cache.  Its actually
111  * kept in a list, so the geometry isn't important. */
112 #define CMPL_DIRECTORY_CACHE_SIZE 10
113 
114 /* A constant used to determine whether a substring was an exact
115  * match by first_diff_index()
116  */
117 #define PATTERN_MATCH -1
118 /* The arguments used by all fnmatch() calls below
119  */
120 #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)
121 
122 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
123 
124 /* This structure contains all the useful information about a directory
125  * for the purposes of filename completion.  These structures are cached
126  * in the CompletionState struct.  CompletionDir's are reference counted.
127  */
128 struct _CompletionDirSent
129 {
130   ino_t inode;
131   time_t mtime;
132   dev_t device;
133 
134   gint entry_count;
135   gchar *name_buffer;		/* memory segment containing names of all entries */
136 
137   struct _CompletionDirEntry *entries;
138 };
139 
140 struct _CompletionDir
141 {
142   CompletionDirSent *sent;
143 
144   gchar *fullname;
145   gint fullname_len;
146 
147   struct _CompletionDir *cmpl_parent;
148   gint cmpl_index;
149   gchar *cmpl_text;
150 };
151 
152 /* This structure contains pairs of directory entry names with a flag saying
153  * whether or not they are a valid directory.  NOTE: This information is used
154  * to provide the caller with information about whether to update its completions
155  * or try to open a file.  Since directories are cached by the directory mtime,
156  * a symlink which points to an invalid file (which will not be a directory),
157  * will not be reevaluated if that file is created, unless the containing
158  * directory is touched.  I consider this case to be worth ignoring (josh).
159  */
160 struct _CompletionDirEntry
161 {
162   gint is_dir;
163   gchar *entry_name;
164 };
165 
166 struct _CompletionUserDir
167 {
168   gchar *login;
169   gchar *homedir;
170 };
171 
172 struct _PossibleCompletion
173 {
174   /* accessible fields, all are accessed externally by functions
175    * declared above
176    */
177   gchar *text;
178   gint is_a_completion;
179   gint is_directory;
180 
181   gint file_size;
182   gint file_time;
183   gint uid;
184   gint gid;
185   /* Private fields
186    */
187   gint text_alloc;
188 };
189 
190 struct _CompletionState
191 {
192   gint last_valid_char;
193   gchar *updated_text;
194   gint updated_text_len;
195   gint updated_text_alloc;
196   gint re_complete;
197 
198   gchar *user_dir_name_buffer;
199   gint user_directories_len;
200 
201   gchar *last_completion_text;
202 
203   gint user_completion_index;	/* if >= 0, currently completing ~user */
204 
205   struct _CompletionDir *completion_dir;	/* directory completing from */
206   struct _CompletionDir *active_completion_dir;
207 
208   struct _PossibleCompletion the_completion;
209 
210   struct _CompletionDir *reference_dir;	/* initial directory */
211 
212   GList *directory_storage;
213   GList *directory_sent_storage;
214 
215   struct _CompletionUserDir *user_directories;
216 };
217 
218 
219 /* File completion functions which would be external, were they used
220  * outside of this file.
221  */
222 
223 static CompletionState *cmpl_init_state (void);
224 static void cmpl_free_state (CompletionState * cmpl_state);
225 static gint cmpl_state_okay (CompletionState * cmpl_state);
226 static gchar *cmpl_strerror (gint);
227 
228 static PossibleCompletion *cmpl_completion_matches (gchar * text_to_complete,
229 						    gchar ** remaining_text,
230 						    CompletionState *
231 						    cmpl_state);
232 
233 /* Returns a name for consideration, possibly a completion, this name
234  * will be invalid after the next call to cmpl_next_completion.
235  */
236 static char *cmpl_this_completion (PossibleCompletion *);
237 
238 /* True if this completion matches the given text.  Otherwise, this
239  * output can be used to have a list of non-completions.
240  */
241 static gint cmpl_is_a_completion (PossibleCompletion *);
242 
243 /* True if the completion is a directory
244  */
245 static gint cmpl_is_directory (PossibleCompletion *);
246 
247 /* Obtains the next completion, or NULL
248  */
249 static PossibleCompletion *cmpl_next_completion (CompletionState *);
250 
251 /* Updating completions: the return value of cmpl_updated_text() will
252  * be text_to_complete completed as much as possible after the most
253  * recent call to cmpl_completion_matches.  For the present
254  * application, this is the suggested replacement for the user's input
255  * string.  You must CALL THIS AFTER ALL cmpl_text_completions have
256  * been received.
257  */
258 static gchar *cmpl_updated_text (CompletionState * cmpl_state);
259 
260 /* After updating, to see if the completion was a directory, call
261  * this.  If it was, you should consider re-calling completion_matches.
262  */
263 static gint cmpl_updated_dir (CompletionState * cmpl_state);
264 
265 /* Current location: if using file completion, return the current
266  * directory, from which file completion begins.  More specifically,
267  * the cwd concatenated with all exact completions up to the last
268  * directory delimiter('/').
269  */
270 static gchar *cmpl_reference_position (CompletionState * cmpl_state);
271 
272 /* backing up: if cmpl_completion_matches returns NULL, you may query
273  * the index of the last completable character into cmpl_updated_text.
274  */
275 static gint cmpl_last_valid_char (CompletionState * cmpl_state);
276 
277 /* When the user selects a non-directory, call cmpl_completion_fullname
278  * to get the full name of the selected file.
279  */
280 static gchar *cmpl_completion_fullname (gchar *,
281 					CompletionState * cmpl_state);
282 
283 
284 /* Directory operations. */
285 static CompletionDir *open_ref_dir (gchar * text_to_complete,
286 				    gchar ** remaining_text,
287 				    CompletionState * cmpl_state);
288 static gboolean check_dir (gchar * dir_name,
289 			   struct stat *result, gboolean * stat_subdirs);
290 static CompletionDir *open_dir (gchar * dir_name,
291 				CompletionState * cmpl_state);
292 static CompletionDir *open_user_dir (gchar * text_to_complete,
293 				     CompletionState * cmpl_state);
294 static CompletionDir *open_relative_dir (gchar * dir_name,
295 					 CompletionDir * dir,
296 					 CompletionState * cmpl_state);
297 static CompletionDirSent *open_new_dir (gchar * dir_name, struct stat *sbuf,
298 					gboolean stat_subdirs);
299 static gint correct_dir_fullname (CompletionDir * cmpl_dir);
300 static gint correct_parent (CompletionDir * cmpl_dir, struct stat *sbuf);
301 static gchar *find_parent_dir_fullname (gchar * dirname);
302 static CompletionDir *attach_dir (CompletionDirSent * sent,
303 				  gchar * dir_name,
304 				  CompletionState * cmpl_state);
305 static void free_dir_sent (CompletionDirSent * sent);
306 static void free_dir (CompletionDir * dir);
307 static void prune_memory_usage (CompletionState * cmpl_state);
308 
309 /* Completion operations */
310 static PossibleCompletion *attempt_homedir_completion (gchar *
311 						       text_to_complete,
312 						       CompletionState *
313 						       cmpl_state);
314 static PossibleCompletion *attempt_file_completion (CompletionState *
315 						    cmpl_state);
316 static CompletionDir *find_completion_dir (gchar * text_to_complete,
317 					   gchar ** remaining_text,
318 					   CompletionState * cmpl_state);
319 static PossibleCompletion *append_completion_text (gchar * text,
320 						   CompletionState *
321 						   cmpl_state);
322 static gint get_pwdb (CompletionState * cmpl_state);
323 static gint first_diff_index (gchar * pat, gchar * text);
324 static gint compare_user_dir (const void *a, const void *b);
325 static gint compare_cmpl_dir (const void *a, const void *b);
326 static void update_cmpl (PossibleCompletion * poss,
327 			 CompletionState * cmpl_state);
328 
329 static void gtk_file_selection_class_init (GtkFileSelectionClass * klass);
330 static void gtk_file_selection_init (GtkFileSelection * filesel);
331 static void gtk_file_selection_destroy (GtkObject * object);
332 static gint gtk_file_selection_key_press (GtkWidget * widget,
333 					  GdkEventKey * event,
334 					  gpointer user_data);
335 
336 static void gtk_file_selection_file_button (GtkWidget * widget,
337 					    gint row,
338 					    gint column,
339 					    GdkEventButton * bevent,
340 					    gpointer user_data);
341 
342 static void gtk_file_selection_dir_button (GtkWidget * widget,
343 					   gint row,
344 					   gint column,
345 					   GdkEventButton * bevent,
346 					   gpointer data);
347 
348 static void gtk_file_selection_undir_button (GtkWidget * widget,
349 					     gint row,
350 					     gint column,
351 					     GdkEventButton * bevent,
352 					     gpointer data);
353 
354 static void gtk_file_selection_populate (GtkFileSelection * fs,
355 					 gchar * rel_path, gint try_complete);
356 static void gtk_file_selection_abort (GtkFileSelection * fs);
357 
358 static void gtk_file_selection_update_history_menu (GtkFileSelection * fs,
359 						    gchar * current_dir);
360 
361 static void gtk_file_selection_create_dir (GtkWidget * widget, gpointer data);
362 static void gtk_file_selection_delete_file (GtkWidget * widget,
363 					    gpointer data);
364 static void gtk_file_selection_rename_file (GtkWidget * widget,
365 					    gpointer data);
366 
367 static gboolean gtk_file_selection_history_combo_callback (GtkWidget * widget,
368 							   GdkEventKey *
369 							   event,
370 							   gpointer data);
371 static gboolean gtk_file_selection_history_combo_list_key_handler (GtkWidget *
372 								   widget,
373 								   GdkEventKey
374 								   * event,
375 								   gpointer
376 								   user_data);
377 static gboolean gtk_file_selection_history_combo_list_callback (GtkWidget *
378 								thelist,
379 								GdkEventButton
380 								* event,
381 								gpointer
382 								user_data);
383 static void gtk_file_selection_mask_entry_callback (GtkWidget * widget,
384 						    gpointer data);
385 static void gtk_file_selection_create_dir (GtkWidget * widget, gpointer data);
386 static void gtk_file_selection_delete_file (GtkWidget * widget,
387 					    gpointer data);
388 static void gtk_file_selection_rename_file (GtkWidget * widget,
389 					    gpointer data);
390 static void gtk_file_selection_home_button (GtkWidget * widget,
391 					    gpointer data);
392 static void gtk_file_selection_up_button (GtkWidget * widget, gpointer data);
393 static void gtk_file_selection_prev_button (GtkWidget * widget,
394 					    gpointer data);
395 static void gtk_file_selection_next_button (GtkWidget * widget,
396 					    gpointer data);
397 static void gtk_file_selection_refresh_button (GtkWidget * widget,
398 					       gpointer data);
399 
400 static gint gtk_file_selection_match_char (gchar, gchar * mask);
401 static gint gtk_file_selection_match_mask (gchar *, gchar *);
402 
403 
404 static GtkWindowClass *parent_class = NULL;
405 
406 /* Saves errno when something cmpl does fails. */
407 static gint cmpl_errno;
408 
409 /* General notes:
410  * Make prev and next inactive if their respective *
411  *   histories are empty.
412  * Add facilities for handling hidden files and    *
413  * directories                                     *
414  * Add an api to access the mask, and hidden files *
415  * check box?  (prob not in 1.2.x series)          *
416  */
417 
418 /* Routine for applying mask to filenames         *
419  *   Need to be optimized to minimize recursion   *
420  *     help the for loop by looking for the next  *
421  *     instance of the mask character following   *
422  *     the '*'.  ei *.c -- look for '.'           *
423  *     Also, swap all *? pairs (-> ?*), as that   *
424  *     will make it possible to look ahead (?     *
425  *     makes it very nondeterministic as in *?.c  *
426  *     which really is ?*.c                       *
427  *   Allow multiply masks, separted by commas     *
428  *   Allow more flexible [] handling (ie [a-zA-Z] *
429  *                                                *
430  */
431 static gint
gtk_file_selection_match_char(gchar text,gchar * mask)432 gtk_file_selection_match_char (gchar text, gchar * mask)
433 {
434   gchar *maskc;
435   gint x;
436   gint s;
437 
438   if (mask[0] == '[')
439     {
440       if (!strchr (mask, ']'))
441 	return 0;
442       maskc = g_strdup (mask + 1);	/* get the portion of mask inside [] */
443 
444       (*(strchr (maskc, ']'))) = 0;
445       s =(gint) strlen ((char *) maskc);
446 
447       for (x = 0; x < s; x++)
448 	{
449 	  if (text == maskc[x])
450 	    {
451 	      g_free (maskc);
452 	      return s + 2;
453 	    }
454 	}
455       g_free (maskc);
456       return 0;
457     }
458 
459   if (mask[0] == '?')
460     return 1;
461   if (mask[0] == text)
462     return 1;
463 
464   return 0;
465 }
466 
467 
468 static gint
gtk_file_selection_match_mask(gchar * text,gchar * mask)469 gtk_file_selection_match_mask (gchar * text, gchar * mask)
470 {
471 
472   int mc;
473   int tc;
474   gchar *mymask;
475   gchar *mask2, *mask3;
476   int tc1 = 0;
477 
478 
479   tc = 0;
480   mc = 0;
481   if (mask[0] == 0 && text[0] == 0)
482     return 1;
483   if (mask[0] == '*')
484     {
485        /*MK*/ mymask = g_strdup (mask);
486       mask2 = g_strdup (strchr (mymask, ','));
487       if (mask2 != (char *) NULL)
488 	{
489 	  tc1 = 1;
490 	  (*(strchr (mymask, ','))) = 0;
491 	}
492       for (tc = 0; tc <= (int)strlen (text); tc++)
493 	{
494 	  if (gtk_file_selection_match_mask (text + tc, mymask + 1))
495 	    {
496 /*	fprintf(stderr,"%s matches %s\n",text,mymask);*/
497 	      g_free (mymask);
498 	      g_free (mask2);
499 	      return 1;
500 	    }
501 	}
502        /*MK*/ if (tc1 == 0)
503 	{
504 /*	fprintf(stderr,"no mask2\n");*/
505 	  g_free (mymask);
506 	  return 0;
507 	}
508       mask2++;
509 /*	fprintf(stderr,"mask2: %s, text=%s\n",mask2,text);*/
510       if (mask2[0] != '*')
511 	{
512 	  g_free (mymask);
513 	  free (--mask2);
514 	  return 0;
515 	}
516       tc = 0;
517       mask3 = strchr (mask2, ',');
518       if (mask3 != (char *) NULL)
519 	{
520 	  tc = 1;
521 	  (*(strchr (mask2, ','))) = 0;
522 	}
523       for (tc1 = 0; tc1 <= (int)strlen (text); tc1++)
524 	{
525 	  if (gtk_file_selection_match_mask (text + tc1, mask2 + 1))
526 	    {
527 /*	fprintf(stderr,"%s matches %s\n",text,mask2);*/
528 	      free (--mask2);
529 	      g_free (mymask);
530 	      return 1;
531 	    }
532 	}
533       if (tc == 0)
534 	{
535 /*	fprintf(stderr,"no mask3 -- no match for %s\n",text);*/
536 	  g_free (--mask2);
537 	  g_free (mymask);
538 	  return 0;
539 	}
540       mask3++;
541 /*	fprintf(stderr,"mask3: %s, text=%s\n",mask3,text);*/
542       if (mask3[0] != '*')
543 	{
544 	  g_free (--mask2);
545 	  g_free (mymask);
546 	  return 0;
547 	}
548       for (tc = 0; tc <= (int)strlen (text); tc++)
549 	{
550 	  if (gtk_file_selection_match_mask (text + tc, mask3 + 1))
551 	    {
552 /*	fprintf(stderr,"%s matches %s\n",text,mask3);*/
553 	      g_free (--mask2);
554 	      g_free (mymask);
555 	      return 1;
556 	    }
557 	}
558        /*MK*/ g_free (--mask2);
559       g_free (mymask);
560 /*	fprintf(stderr,"nothing matches %s\n",text);*/
561       return 0;
562     }
563   mc = gtk_file_selection_match_char (text[0], mask);
564 
565   if (mc)
566     return gtk_file_selection_match_mask (text + 1, mask + mc);
567   else
568     return 0;
569 }
570 
gtk_file_selection_get_type(void)571 GtkType gtk_file_selection_get_type (void)
572 {
573   static GtkType file_selection_type = 0;
574 
575   if (!file_selection_type)
576     {
577       static const GtkTypeInfo filesel_info = {
578 	"GtkFileSelection",
579 	(guint)sizeof (GtkFileSelection),
580 	(guint)sizeof (GtkFileSelectionClass),
581 	(GtkClassInitFunc) gtk_file_selection_class_init,
582 	(GtkObjectInitFunc) gtk_file_selection_init,
583 	/* reserved_1 */ NULL,
584 	/* reserved_2 */ NULL,
585 	(GtkClassInitFunc) NULL,
586       };
587 
588       file_selection_type = gtk_type_unique (GTK_TYPE_WINDOW, &filesel_info);
589     }
590 
591   return file_selection_type;
592 }
593 
594 static void
gtk_file_selection_class_init(GtkFileSelectionClass * class)595 gtk_file_selection_class_init (GtkFileSelectionClass * class)
596 {
597   GtkObjectClass *object_class;
598 
599   object_class = (GtkObjectClass *) class;
600 
601   parent_class = gtk_type_class (GTK_TYPE_WINDOW);
602 
603   object_class->destroy = gtk_file_selection_destroy;
604 }
605 
606 static void
gtk_file_selection_init(GtkFileSelection * filesel)607 gtk_file_selection_init (GtkFileSelection * filesel)
608 {
609   GtkWidget *entry_vbox;
610   GtkWidget *label;
611   GtkWidget *list_hbox;
612   GtkWidget *confirm_area;
613   GtkWidget *vbox;
614   GtkWidget *hbox;
615   GtkWidget *pulldown_hbox;
616   GtkWidget *scrolled_win;
617   GtkWidget *mask_label;
618   GtkWidget *bigframe;
619   GtkWidget *label_lookingin;
620   GtkWidget *up_button;
621   GtkWidget *home_button;
622   GtkWidget *prev_button;
623   GtkWidget *next_button;
624   GtkWidget *refresh_button;
625   GdkPixmap *pixmap;
626   GtkWidget *pixmapwidget;
627   GtkStyle *style;
628   GdkBitmap *mask;
629 
630   char *dir_title[2];
631   char *file_title[2];
632 
633   filesel->cmpl_state = cmpl_init_state ();
634 
635   filesel->mask = NULL;
636   filesel->prev_history = NULL;
637   filesel->next_history = NULL;
638   filesel->saved_entry = NULL;
639 
640   /* The dialog-sized vertical box  */
641   filesel->main_vbox = gtk_vbox_new (FALSE, 10);
642   gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
643   gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox);
644   gtk_widget_show (filesel->main_vbox);
645 
646   /* The horizontal box containing create, rename etc. buttons */
647   filesel->button_area = gtk_hbutton_box_new ();
648   gtk_button_box_set_layout (GTK_BUTTON_BOX (filesel->button_area),
649 			     GTK_BUTTONBOX_START);
650   gtk_button_box_set_spacing (GTK_BUTTON_BOX (filesel->button_area), 0);
651   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area,
652 		      FALSE, FALSE, 0);
653   gtk_widget_show (filesel->button_area);
654 
655   gtk_file_selection_show_fileop_buttons (filesel);
656 
657   /* hbox for pulldown menu */
658   pulldown_hbox = gtk_hbox_new (FALSE, 5);
659   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE,
660 		      FALSE, 0);
661   gtk_widget_show (pulldown_hbox);
662 
663   /* The combo box that replaces the pulldown menu */
664   label_lookingin = gtk_label_new (_("Looking in:"));
665   gtk_widget_show (label_lookingin);
666   gtk_box_pack_start (GTK_BOX (pulldown_hbox), label_lookingin, FALSE, FALSE,
667 		      0);
668 
669   filesel->history_combo = gtk_combo_new ();
670   gtk_widget_show (filesel->history_combo);
671   gtk_combo_set_value_in_list (GTK_COMBO (filesel->history_combo), FALSE,
672 			       FALSE);
673   gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_combo, TRUE,
674 		      TRUE, 0);
675   (void)gtk_signal_connect (GTK_OBJECT
676 		      (((GtkCombo *) filesel->history_combo)->entry),
677 		      "key-press-event",
678 		      (GtkSignalFunc)
679 		      gtk_file_selection_history_combo_callback,
680 		      (gpointer) filesel);
681 
682   (void)gtk_signal_connect (GTK_OBJECT
683 		      (((GtkCombo *) filesel->history_combo)->list),
684 		      "button-press-event",
685 		      (GtkSignalFunc)
686 		      gtk_file_selection_history_combo_list_callback,
687 		      (gpointer) filesel);
688 
689   (void)gtk_signal_connect (GTK_OBJECT
690 		      (((GtkCombo *) filesel->history_combo)->list),
691 		      "key-press-event",
692 		      (GtkSignalFunc)
693 		      gtk_file_selection_history_combo_list_key_handler,
694 		      (gpointer) filesel);
695 
696   /*  frame to put the following hbox in  */
697   bigframe = gtk_frame_new (NULL);
698   gtk_widget_show (bigframe);
699   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), bigframe, TRUE, TRUE, 0);
700 
701   /*  The horizontal box containing the directory and file listboxes  */
702   list_hbox = gtk_hbox_new (FALSE, 5);
703   gtk_container_add (GTK_CONTAINER (bigframe), list_hbox);
704   gtk_container_set_border_width (GTK_CONTAINER (list_hbox), 5);
705   gtk_widget_show (list_hbox);
706 
707   /* vbox to put the buttons and directory listing in  */
708   vbox = gtk_vbox_new (FALSE, 0);
709   gtk_widget_show (vbox);
710   gtk_box_pack_start (GTK_BOX (list_hbox), vbox, FALSE, FALSE, 0);
711 
712   hbox = gtk_hbox_new (FALSE, 0);
713   gtk_widget_show (hbox);
714   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
715 
716 /*  home_button = gtk_button_new_with_label (_("Home"));*/
717 /*  style= gtk_widget_get_style(GTK_WIDGET(filesel));*/
718   gtk_widget_realize (GTK_WIDGET (filesel));
719   style = filesel->button_area->style;
720   pixmap = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (filesel)->window, &mask,
721 					 &style->bg[GTK_STATE_NORMAL],
722 					 (gchar **) filesel_home_pixmap);
723   pixmapwidget = gtk_pixmap_new (pixmap, mask);
724   gtk_widget_show (pixmapwidget);
725   gdk_pixmap_unref (pixmap);
726   home_button = gtk_button_new ();
727   gtk_container_add (GTK_CONTAINER (home_button), pixmapwidget);
728   gtk_widget_show (home_button);
729   (void)gtk_signal_connect (GTK_OBJECT (home_button), "clicked",
730 		      (GtkSignalFunc) gtk_file_selection_home_button,
731 		      (gpointer) filesel);
732   gtk_box_pack_start (GTK_BOX (hbox), home_button, TRUE, TRUE, 0);
733 
734 /*  prev_button = gtk_button_new_with_label (_("Prev"));*/
735   pixmap = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (filesel)->window, &mask,
736 					 &style->bg[GTK_STATE_NORMAL],
737 					 (gchar **) filesel_back_pixmap);
738   pixmapwidget = gtk_pixmap_new (pixmap, mask);
739   gtk_widget_show (pixmapwidget);
740   gdk_pixmap_unref (pixmap);
741   prev_button = gtk_button_new ();
742   gtk_container_add (GTK_CONTAINER (prev_button), pixmapwidget);
743 
744   (void)gtk_signal_connect (GTK_OBJECT (prev_button), "clicked",
745 		      (GtkSignalFunc) gtk_file_selection_prev_button,
746 		      (gpointer) filesel);
747   gtk_widget_show (prev_button);
748   gtk_box_pack_start (GTK_BOX (hbox), prev_button, TRUE, TRUE, 0);
749 
750 /*  up_button = gtk_button_new_with_label (_("Up"));*/
751   pixmap = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (filesel)->window, &mask,
752 					 &style->bg[GTK_STATE_NORMAL],
753 					 (gchar **) filesel_up_pixmap);
754   pixmapwidget = gtk_pixmap_new (pixmap, mask);
755   gtk_widget_show (pixmapwidget);
756   gdk_pixmap_unref (pixmap);
757   up_button = gtk_button_new ();
758   gtk_container_add (GTK_CONTAINER (up_button), pixmapwidget);
759   (void)gtk_signal_connect (GTK_OBJECT (up_button), "clicked",
760 		      (GtkSignalFunc) gtk_file_selection_up_button,
761 		      (gpointer) filesel);
762   gtk_widget_show (up_button);
763   gtk_box_pack_start (GTK_BOX (hbox), up_button, TRUE, TRUE, 0);
764 
765 /*  next_button = gtk_button_new_with_label (_("Next"));*/
766   pixmap = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (filesel)->window, &mask,
767 					 &style->bg[GTK_STATE_NORMAL],
768 					 (gchar **) filesel_forward_pixmap);
769   pixmapwidget = gtk_pixmap_new (pixmap, mask);
770   gtk_widget_show (pixmapwidget);
771   gdk_pixmap_unref (pixmap);
772   next_button = gtk_button_new ();
773   gtk_container_add (GTK_CONTAINER (next_button), pixmapwidget);
774   gtk_widget_show (next_button);
775   (void)gtk_signal_connect (GTK_OBJECT (next_button), "clicked",
776 		      (GtkSignalFunc) gtk_file_selection_next_button,
777 		      (gpointer) filesel);
778   gtk_box_pack_start (GTK_BOX (hbox), next_button, TRUE, TRUE, 0);
779 
780 /*  refresh_button = gtk_button_new_with_label (_("Refresh"));*/
781   pixmap = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (filesel)->window, &mask,
782 					 &style->bg[GTK_STATE_NORMAL],
783 					 (gchar **) filesel_reload_pixmap);
784   pixmapwidget = gtk_pixmap_new (pixmap, mask);
785   gtk_widget_show (pixmapwidget);
786   gdk_pixmap_unref (pixmap);
787   refresh_button = gtk_button_new ();
788   gtk_container_add (GTK_CONTAINER (refresh_button), pixmapwidget);
789   gtk_widget_show (refresh_button);
790   (void)gtk_signal_connect (GTK_OBJECT (refresh_button), "clicked",
791 		      (GtkSignalFunc) gtk_file_selection_refresh_button,
792 		      (gpointer) filesel);
793   gtk_box_pack_start (GTK_BOX (hbox), refresh_button, TRUE, TRUE, 0);
794 
795   /* The directories clist */
796   dir_title[0] = _("Directories");
797   dir_title[1] = NULL;
798   filesel->dir_list = gtk_clist_new_with_titles (1, (gchar **) dir_title);
799   gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
800   (void)gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row",
801 		      (GtkSignalFunc) gtk_file_selection_dir_button,
802 		      (gpointer) filesel);
803   (void)gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "unselect_row",
804 		      (GtkSignalFunc) gtk_file_selection_undir_button,
805 		      (gpointer) filesel);
806   gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list));
807 
808   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
809   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list);
810   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
811 				  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
812   gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 5);
813   gtk_widget_show (filesel->dir_list);
814   gtk_widget_show (scrolled_win);
815 
816   /* vbox area for mask entry and files clist  */
817   vbox = gtk_vbox_new (FALSE, 0);
818   gtk_widget_show (vbox);
819   gtk_box_pack_start (GTK_BOX (list_hbox), vbox, TRUE, TRUE, 0);
820 
821   hbox = gtk_hbox_new (FALSE, 5);
822   gtk_widget_show (hbox);
823   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
824 
825   mask_label = gtk_label_new (_("Mask:"));
826   gtk_widget_show (mask_label);
827   gtk_box_pack_start (GTK_BOX (hbox), mask_label, FALSE, FALSE, 0);
828 
829   filesel->mask_entry = gtk_entry_new ();
830   gtk_widget_show (filesel->mask_entry);
831   (void)gtk_signal_connect (GTK_OBJECT (filesel->mask_entry), "activate",
832 		      (GtkSignalFunc) gtk_file_selection_mask_entry_callback,
833 		      (gpointer) filesel);
834   gtk_box_pack_start (GTK_BOX (hbox), filesel->mask_entry, TRUE, TRUE, 0);
835 
836 
837   /* The files clist */
838   file_title[0] = _("Files");
839   file_title[1] = NULL;
840   filesel->file_list = gtk_clist_new_with_titles (1, (gchar **) file_title);
841   gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH,
842 			FILE_LIST_HEIGHT);
843   (void)gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row",
844 		      (GtkSignalFunc) gtk_file_selection_file_button,
845 		      (gpointer) filesel);
846   gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list));
847 
848   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
849   gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list);
850   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
851 				  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
852   gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 5);
853   gtk_widget_show (filesel->file_list);
854   gtk_widget_show (scrolled_win);
855 
856   /* action area for packing buttons into. */
857   filesel->action_area = gtk_hbox_new (TRUE, 0);
858   gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area,
859 		      FALSE, FALSE, 0);
860   gtk_widget_show (filesel->action_area);
861 
862   /*  The OK/Cancel button area */
863   confirm_area = gtk_hbutton_box_new ();
864   gtk_button_box_set_layout (GTK_BUTTON_BOX (confirm_area),
865 			     GTK_BUTTONBOX_END);
866   gtk_button_box_set_spacing (GTK_BUTTON_BOX (confirm_area), 5);
867   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE,
868 		    0);
869   gtk_widget_show (confirm_area);
870 
871   /*  The OK button  */
872   filesel->ok_button = gtk_button_new_with_label (_("OK"));
873   GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT);
874   gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE,
875 		      0);
876   gtk_widget_grab_default (filesel->ok_button);
877   gtk_widget_show (filesel->ok_button);
878 
879   /*  The Cancel button  */
880   filesel->cancel_button = gtk_button_new_with_label (_("Cancel"));
881   GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT);
882   gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE,
883 		      TRUE, 0);
884   gtk_widget_show (filesel->cancel_button);
885 
886   /*  The selection entry widget  */
887   entry_vbox = gtk_vbox_new (FALSE, 2);
888   gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE,
889 		    0);
890   gtk_widget_show (entry_vbox);
891 
892   filesel->selection_text = label = gtk_label_new ("");
893   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
894   gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0);
895   gtk_widget_show (label);
896 
897   filesel->selection_entry = gtk_entry_new ();
898   (void)gtk_signal_connect (GTK_OBJECT (filesel->selection_entry),
899 		      "key_press_event",
900 		      (GtkSignalFunc) gtk_file_selection_key_press, filesel);
901   (void)gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry),
902 			     "focus_in_event",
903 			     (GtkSignalFunc) gtk_widget_grab_default,
904 			     GTK_OBJECT (filesel->ok_button));
905   (void)gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry),
906 			     "activate", (GtkSignalFunc) gtk_button_clicked,
907 			     GTK_OBJECT (filesel->ok_button));
908   gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE,
909 		      TRUE, 0);
910   gtk_widget_show (filesel->selection_entry);
911 
912   if (!cmpl_state_okay (filesel->cmpl_state))
913     {
914       gchar err_buf[256];
915 
916       snprintf (err_buf,256, _("Directory unreadable: %s"),
917 	       cmpl_strerror (cmpl_errno));
918 
919       gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
920     }
921   else
922     {
923       gtk_file_selection_populate (filesel, "", FALSE);
924     }
925 
926   gtk_widget_grab_focus (filesel->selection_entry);
927 }
928 
929 GtkWidget *
gtk_file_selection_new(const gchar * title)930 gtk_file_selection_new (const gchar * title)
931 {
932   GtkFileSelection *filesel;
933 
934   filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION);
935   gtk_window_set_title (GTK_WINDOW (filesel), title);
936 
937   return GTK_WIDGET (filesel);
938 }
939 
940 void
gtk_file_selection_show_fileop_buttons(GtkFileSelection * filesel)941 gtk_file_selection_show_fileop_buttons (GtkFileSelection * filesel)
942 {
943   g_return_if_fail (filesel != NULL);
944   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
945 
946   /* delete, create directory, and rename */
947   if (!filesel->fileop_c_dir)
948     {
949       filesel->fileop_c_dir = gtk_button_new_with_label (_("Create Dir"));
950       (void)gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked",
951 			  (GtkSignalFunc) gtk_file_selection_create_dir,
952 			  (gpointer) filesel);
953       gtk_box_pack_start (GTK_BOX (filesel->button_area),
954 			  filesel->fileop_c_dir, TRUE, TRUE, 0);
955       gtk_widget_show (filesel->fileop_c_dir);
956     }
957 
958   if (!filesel->fileop_del_file)
959     {
960       filesel->fileop_del_file = gtk_button_new_with_label (_("Delete File"));
961       (void)gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked",
962 			  (GtkSignalFunc) gtk_file_selection_delete_file,
963 			  (gpointer) filesel);
964       gtk_box_pack_start (GTK_BOX (filesel->button_area),
965 			  filesel->fileop_del_file, TRUE, TRUE, 0);
966       gtk_widget_show (filesel->fileop_del_file);
967     }
968 
969   if (!filesel->fileop_ren_file)
970     {
971       filesel->fileop_ren_file = gtk_button_new_with_label (_("Rename File"));
972       (void)gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked",
973 			  (GtkSignalFunc) gtk_file_selection_rename_file,
974 			  (gpointer) filesel);
975       gtk_box_pack_start (GTK_BOX (filesel->button_area),
976 			  filesel->fileop_ren_file, TRUE, TRUE, 0);
977       gtk_widget_show (filesel->fileop_ren_file);
978     }
979 
980   gtk_widget_queue_resize (GTK_WIDGET (filesel));
981 }
982 
983 void
gtk_file_selection_hide_fileop_buttons(GtkFileSelection * filesel)984 gtk_file_selection_hide_fileop_buttons (GtkFileSelection * filesel)
985 {
986   g_return_if_fail (filesel != NULL);
987   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
988 
989   if (filesel->fileop_ren_file)
990     {
991       gtk_widget_destroy (filesel->fileop_ren_file);
992       filesel->fileop_ren_file = NULL;
993     }
994 
995   if (filesel->fileop_del_file)
996     {
997       gtk_widget_destroy (filesel->fileop_del_file);
998       filesel->fileop_del_file = NULL;
999     }
1000 
1001   if (filesel->fileop_c_dir)
1002     {
1003       gtk_widget_destroy (filesel->fileop_c_dir);
1004       filesel->fileop_c_dir = NULL;
1005     }
1006 }
1007 
1008 
1009 
1010 void
gtk_file_selection_set_filename(GtkFileSelection * filesel,const gchar * filename)1011 gtk_file_selection_set_filename (GtkFileSelection * filesel,
1012 				 const gchar * filename)
1013 {
1014   char buf[MAXPATHLEN];
1015   const char *name, *last_slash;
1016 
1017   g_return_if_fail (filesel != NULL);
1018   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
1019   g_return_if_fail (filename != NULL);
1020 
1021   last_slash = strrchr (filename, '/');
1022 
1023   if (!last_slash)
1024     {
1025       buf[0] = 0;
1026       name = filename;
1027     }
1028   else
1029     {
1030       gint len = MIN ((int)MAXPATHLEN - 1, (int)(last_slash - filename + 1));
1031 
1032       strncpy (buf, filename, (size_t)len);
1033       buf[len] = 0;
1034 
1035       name = last_slash + 1;
1036     }
1037 
1038   gtk_file_selection_populate (filesel, buf, FALSE);
1039 
1040   if (filesel->selection_entry)
1041     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
1042 }
1043 
1044 gchar *
gtk_file_selection_get_filename(GtkFileSelection * filesel)1045 gtk_file_selection_get_filename (GtkFileSelection * filesel)
1046 {
1047   static char nothing[2] = "";
1048   char *text;
1049   char *filename;
1050 
1051   g_return_val_if_fail (filesel != NULL, nothing);
1052   g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);
1053 
1054   text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
1055   if (text)
1056     {
1057       filename = cmpl_completion_fullname (text, filesel->cmpl_state);
1058       return filename;
1059     }
1060 
1061   return nothing;
1062 }
1063 
1064 void
gtk_file_selection_complete(GtkFileSelection * filesel,const gchar * pattern)1065 gtk_file_selection_complete (GtkFileSelection * filesel,
1066 			     const gchar * pattern)
1067 {
1068   gchar *new_pattern;
1069   gint x;
1070 
1071   g_return_if_fail (filesel != NULL);
1072   g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
1073   g_return_if_fail (pattern != NULL);
1074 
1075   if (filesel->selection_entry)
1076     gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern);
1077 
1078   if (strchr (pattern, '*') || strchr (pattern, '?'))
1079     {
1080       for (x = (gint)strlen (pattern); x >= 0; x--)
1081 	{
1082 	  if (pattern[x] == '/')
1083 	    break;
1084 	}
1085       gtk_entry_set_text (GTK_ENTRY (filesel->mask_entry),
1086 			  g_strdup (pattern + x + 1));
1087 
1088       if (filesel->mask)
1089 	g_free (filesel->mask);
1090 
1091       filesel->mask = g_strdup (pattern + x + 1);
1092       new_pattern = g_strdup (pattern);
1093       new_pattern[x + 1] = 0;
1094       gtk_file_selection_populate (filesel, (gchar *) new_pattern, TRUE);
1095       g_free (new_pattern);
1096     }
1097   else
1098     {
1099       gtk_file_selection_populate (filesel, (gchar *) pattern, TRUE);
1100     }
1101 }
1102 
1103 static void
gtk_file_selection_destroy(GtkObject * object)1104 gtk_file_selection_destroy (GtkObject * object)
1105 {
1106   GtkFileSelection *filesel;
1107   GList *list;
1108 
1109   g_return_if_fail (object != NULL);
1110   g_return_if_fail (GTK_IS_FILE_SELECTION (object));
1111 
1112   filesel = GTK_FILE_SELECTION (object);
1113 
1114   if (filesel->fileop_dialog)
1115     gtk_widget_destroy (filesel->fileop_dialog);
1116 
1117   if (filesel->next_history)
1118     {
1119       list = filesel->next_history;
1120       while (list)
1121 	{
1122 	  g_free (list->data);
1123 	  list = list->next;
1124 	}
1125     }
1126   g_list_free (filesel->next_history);
1127   filesel->next_history = NULL;
1128 
1129   if (filesel->prev_history)
1130     {
1131       list = filesel->prev_history;
1132       while (list)
1133 	{
1134 	  g_free (list->data);
1135 	  list = list->next;
1136 	}
1137     }
1138   g_list_free (filesel->prev_history);
1139   filesel->prev_history = NULL;
1140 
1141   if (filesel->mask)
1142     {
1143       g_free (filesel->mask);
1144       filesel->mask = NULL;
1145     }
1146 
1147   cmpl_free_state (filesel->cmpl_state);
1148   filesel->cmpl_state = NULL;
1149 
1150   if (GTK_OBJECT_CLASS (parent_class)->destroy)
1151     (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
1152 }
1153 
1154 /* Begin file operations callbacks */
1155 
1156 static void
gtk_file_selection_fileop_error(GtkFileSelection * fs,gchar * error_message)1157 gtk_file_selection_fileop_error (GtkFileSelection * fs, gchar * error_message)
1158 {
1159   GtkWidget *label;
1160   GtkWidget *vbox;
1161   GtkWidget *button;
1162   GtkWidget *dialog;
1163 
1164   g_return_if_fail (error_message != NULL);
1165 
1166   /* main dialog */
1167   dialog = gtk_dialog_new ();
1168   /*
1169      gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1170      (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1171      (gpointer) fs);
1172    */
1173   gtk_window_set_title (GTK_WINDOW (dialog), _("Error"));
1174   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1175 
1176   /* If file dialog is grabbed, make this dialog modal too */
1177   /* When error dialog is closed, file dialog will be grabbed again */
1178   if (GTK_WINDOW (fs)->modal)
1179     gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1180 
1181   vbox = gtk_vbox_new (FALSE, 0);
1182   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1183   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1184 		      FALSE, FALSE, 0);
1185   gtk_widget_show (vbox);
1186 
1187   label = gtk_label_new (error_message);
1188   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1189   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1190   gtk_widget_show (label);
1191 
1192   /* yes, we free it */
1193   g_free (error_message);
1194 
1195   /* close button */
1196   button = gtk_button_new_with_label (_("Close"));
1197   (void)gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1198 			     (GtkSignalFunc) gtk_widget_destroy,
1199 			     (gpointer) dialog);
1200   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1201 		      button, TRUE, TRUE, 0);
1202   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1203   gtk_widget_grab_default (button);
1204   gtk_widget_show (button);
1205 
1206   gtk_widget_show (dialog);
1207 }
1208 
1209 static void
gtk_file_selection_fileop_destroy(GtkWidget * widget,gpointer data)1210 gtk_file_selection_fileop_destroy (GtkWidget * widget, gpointer data)
1211 {
1212   GtkFileSelection *fs = data;
1213 
1214   g_return_if_fail (fs != NULL);
1215   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1216 
1217   fs->fileop_dialog = NULL;
1218 }
1219 
1220 
1221 static void
gtk_file_selection_create_dir_confirmed(GtkWidget * widget,gpointer data)1222 gtk_file_selection_create_dir_confirmed (GtkWidget * widget, gpointer data)
1223 {
1224   GtkFileSelection *fs = data;
1225   gchar *dirname;
1226   gchar *path;
1227   gchar *full_path;
1228   gchar *buf;
1229   CompletionState *cmpl_state;
1230 
1231   g_return_if_fail (fs != NULL);
1232   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1233 
1234   dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1235   cmpl_state = (CompletionState *) fs->cmpl_state;
1236   path = cmpl_reference_position (cmpl_state);
1237 
1238   full_path = g_strconcat (path, "/", dirname, NULL);
1239   if ((mkdir (full_path, 0755) < 0))
1240     {
1241       buf = g_strconcat ("Error creating directory \"", dirname, "\":  ",
1242 			 g_strerror (errno), NULL);
1243       gtk_file_selection_fileop_error (fs, buf);
1244     }
1245   g_free (full_path);
1246 
1247   gtk_widget_destroy (fs->fileop_dialog);
1248   gtk_file_selection_populate (fs, "", FALSE);
1249 }
1250 
1251 static void
gtk_file_selection_create_dir(GtkWidget * widget,gpointer data)1252 gtk_file_selection_create_dir (GtkWidget * widget, gpointer data)
1253 {
1254   GtkFileSelection *fs = data;
1255   GtkWidget *label;
1256   GtkWidget *dialog;
1257   GtkWidget *vbox;
1258   GtkWidget *button;
1259 
1260   g_return_if_fail (fs != NULL);
1261   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1262 
1263   if (fs->fileop_dialog)
1264     return;
1265 
1266   /* main dialog */
1267   fs->fileop_dialog = dialog = gtk_dialog_new ();
1268   (void)gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1269 		      (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1270 		      (gpointer) fs);
1271   gtk_window_set_title (GTK_WINDOW (dialog), _("Create Directory"));
1272   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1273 
1274   /* If file dialog is grabbed, grab option dialog */
1275   /* When option dialog is closed, file dialog will be grabbed again */
1276   if (GTK_WINDOW (fs)->modal)
1277     gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1278 
1279   vbox = gtk_vbox_new (FALSE, 0);
1280   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1281   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1282 		      FALSE, FALSE, 0);
1283   gtk_widget_show (vbox);
1284 
1285   label = gtk_label_new (_("Directory name:"));
1286   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1287   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1288   gtk_widget_show (label);
1289 
1290   /*  The directory entry widget  */
1291   fs->fileop_entry = gtk_entry_new ();
1292   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, TRUE, TRUE, 5);
1293   GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1294   gtk_widget_show (fs->fileop_entry);
1295 
1296   /* buttons */
1297   button = gtk_button_new_with_label (_("Create"));
1298   (void)gtk_signal_connect (GTK_OBJECT (button), "clicked",
1299 		      (GtkSignalFunc) gtk_file_selection_create_dir_confirmed,
1300 		      (gpointer) fs);
1301   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1302 		      button, TRUE, TRUE, 0);
1303   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1304   gtk_widget_show (button);
1305 
1306   button = gtk_button_new_with_label (_("Cancel"));
1307   (void)gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1308 			     (GtkSignalFunc) gtk_widget_destroy,
1309 			     (gpointer) dialog);
1310   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1311 		      button, TRUE, TRUE, 0);
1312   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1313   gtk_widget_grab_default (button);
1314   gtk_widget_show (button);
1315 
1316   gtk_widget_show (dialog);
1317 }
1318 
1319 static void
gtk_file_selection_delete_file_confirmed(GtkWidget * widget,gpointer data)1320 gtk_file_selection_delete_file_confirmed (GtkWidget * widget, gpointer data)
1321 {
1322   GtkFileSelection *fs = data;
1323   CompletionState *cmpl_state;
1324   gchar *path;
1325   gchar *full_path;
1326   gchar *buf;
1327 
1328   g_return_if_fail (fs != NULL);
1329   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1330 
1331   cmpl_state = (CompletionState *) fs->cmpl_state;
1332   path = cmpl_reference_position (cmpl_state);
1333 
1334   full_path = g_strconcat (path, "/", fs->fileop_file, NULL);
1335   if ((unlink (full_path) < 0))
1336     {
1337       buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\":  ",
1338 			 g_strerror (errno), NULL);
1339       gtk_file_selection_fileop_error (fs, buf);
1340     }
1341   g_free (full_path);
1342 
1343   gtk_widget_destroy (fs->fileop_dialog);
1344   gtk_file_selection_populate (fs, "", FALSE);
1345 }
1346 
1347 static void
gtk_file_selection_delete_file(GtkWidget * widget,gpointer data)1348 gtk_file_selection_delete_file (GtkWidget * widget, gpointer data)
1349 {
1350   GtkFileSelection *fs = data;
1351   GtkWidget *label;
1352   GtkWidget *vbox;
1353   GtkWidget *button;
1354   GtkWidget *dialog;
1355   gchar *filename;
1356   gchar *buf;
1357 
1358   g_return_if_fail (fs != NULL);
1359   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1360 
1361   if (fs->fileop_dialog)
1362     return;
1363 
1364   filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1365   if (strlen (filename) < 1)
1366     return;
1367 
1368   fs->fileop_file = filename;
1369 
1370   /* main dialog */
1371   fs->fileop_dialog = dialog = gtk_dialog_new ();
1372   (void)gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1373 		      (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1374 		      (gpointer) fs);
1375   gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File"));
1376   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1377 
1378   /* If file dialog is grabbed, grab option dialog */
1379   /* When option dialog is closed, file dialog will be grabbed again */
1380   if (GTK_WINDOW (fs)->modal)
1381     gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1382 
1383   vbox = gtk_vbox_new (FALSE, 0);
1384   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1385   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1386 		      FALSE, FALSE, 0);
1387   gtk_widget_show (vbox);
1388 
1389   buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL);
1390   label = gtk_label_new (buf);
1391   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1392   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1393   gtk_widget_show (label);
1394   g_free (buf);
1395 
1396   /* buttons */
1397   button = gtk_button_new_with_label (_("Delete"));
1398   (void)gtk_signal_connect (GTK_OBJECT (button), "clicked",
1399 		      (GtkSignalFunc)
1400 		      gtk_file_selection_delete_file_confirmed,
1401 		      (gpointer) fs);
1402   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), button,
1403 		      TRUE, TRUE, 0);
1404   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1405   gtk_widget_show (button);
1406 
1407   button = gtk_button_new_with_label (_("Cancel"));
1408   (void)gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1409 			     (GtkSignalFunc) gtk_widget_destroy,
1410 			     (gpointer) dialog);
1411   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1412 		      button, TRUE, TRUE, 0);
1413   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1414   gtk_widget_grab_default (button);
1415   gtk_widget_show (button);
1416 
1417   gtk_widget_show (dialog);
1418 
1419 }
1420 
1421 static void
gtk_file_selection_rename_file_confirmed(GtkWidget * widget,gpointer data)1422 gtk_file_selection_rename_file_confirmed (GtkWidget * widget, gpointer data)
1423 {
1424   GtkFileSelection *fs = data;
1425   gchar *buf;
1426   gchar *file;
1427   gchar *path;
1428   gchar *new_filename;
1429   gchar *old_filename;
1430   CompletionState *cmpl_state;
1431 
1432   g_return_if_fail (fs != NULL);
1433   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1434 
1435   file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1436   cmpl_state = (CompletionState *) fs->cmpl_state;
1437   path = cmpl_reference_position (cmpl_state);
1438 
1439   new_filename = g_strconcat (path, "/", file, NULL);
1440   old_filename = g_strconcat (path, "/", fs->fileop_file, NULL);
1441 
1442   if ((rename (old_filename, new_filename)) < 0)
1443     {
1444       buf = g_strconcat ("Error renaming file \"", file, "\":  ",
1445 			 g_strerror (errno), NULL);
1446       gtk_file_selection_fileop_error (fs, buf);
1447     }
1448   g_free (new_filename);
1449   g_free (old_filename);
1450 
1451   gtk_widget_destroy (fs->fileop_dialog);
1452   gtk_file_selection_populate (fs, "", FALSE);
1453 }
1454 
1455 static void
gtk_file_selection_rename_file(GtkWidget * widget,gpointer data)1456 gtk_file_selection_rename_file (GtkWidget * widget, gpointer data)
1457 {
1458   GtkFileSelection *fs = data;
1459   GtkWidget *label;
1460   GtkWidget *dialog;
1461   GtkWidget *vbox;
1462   GtkWidget *button;
1463   gchar *buf;
1464 
1465   g_return_if_fail (fs != NULL);
1466   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1467 
1468   if (fs->fileop_dialog)
1469     return;
1470 
1471   fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1472   if (strlen (fs->fileop_file) < 1)
1473     return;
1474 
1475   /* main dialog */
1476   fs->fileop_dialog = dialog = gtk_dialog_new ();
1477   (void)gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
1478 		      (GtkSignalFunc) gtk_file_selection_fileop_destroy,
1479 		      (gpointer) fs);
1480   gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File"));
1481   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1482 
1483   /* If file dialog is grabbed, grab option dialog */
1484   /* When option dialog  closed, file dialog will be grabbed again */
1485   if (GTK_WINDOW (fs)->modal)
1486     gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1487 
1488   vbox = gtk_vbox_new (FALSE, 0);
1489   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1490   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1491 		      FALSE, FALSE, 0);
1492   gtk_widget_show (vbox);
1493 
1494   buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL);
1495   label = gtk_label_new (buf);
1496   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1497   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1498   gtk_widget_show (label);
1499   g_free (buf);
1500 
1501   /* New filename entry */
1502   fs->fileop_entry = gtk_entry_new ();
1503   gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, TRUE, TRUE, 5);
1504   GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1505   gtk_widget_show (fs->fileop_entry);
1506 
1507   gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
1508   gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
1509 			      0, (gint)strlen (fs->fileop_file));
1510 
1511   /* buttons */
1512   button = gtk_button_new_with_label (_("Rename"));
1513   (void)gtk_signal_connect (GTK_OBJECT (button), "clicked",
1514 		      (GtkSignalFunc)
1515 		      gtk_file_selection_rename_file_confirmed,
1516 		      (gpointer) fs);
1517   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), button,
1518 		      TRUE, TRUE, 0);
1519   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1520   gtk_widget_show (button);
1521 
1522   button = gtk_button_new_with_label (_("Cancel"));
1523   (void)gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
1524 			     (GtkSignalFunc) gtk_widget_destroy,
1525 			     (gpointer) dialog);
1526   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1527 		      button, TRUE, TRUE, 0);
1528   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1529   gtk_widget_grab_default (button);
1530   gtk_widget_show (button);
1531 
1532   gtk_widget_show (dialog);
1533 }
1534 
1535 
1536 static gint
gtk_file_selection_key_press(GtkWidget * widget,GdkEventKey * event,gpointer user_data)1537 gtk_file_selection_key_press (GtkWidget * widget,
1538 			      GdkEventKey * event, gpointer user_data)
1539 {
1540   GtkFileSelection *fs;
1541   char *text;
1542 
1543   g_return_val_if_fail (widget != NULL, FALSE);
1544   g_return_val_if_fail (event != NULL, FALSE);
1545 
1546   fs = GTK_FILE_SELECTION (user_data);
1547 
1548   if (event->keyval == GDK_Tab)
1549     {
1550       text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1551 
1552       text = g_strdup (text);
1553 
1554       gtk_file_selection_populate (fs, text, TRUE);
1555 
1556       g_free (text);
1557 
1558       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1559 
1560       return TRUE;
1561     }
1562   if (fs->saved_entry)
1563     {
1564       gtk_clist_unselect_all ((GtkCList *) (fs->dir_list));
1565       gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), fs->saved_entry);
1566       g_free (fs->saved_entry);
1567       fs->saved_entry = NULL;
1568     }
1569 
1570 
1571   return FALSE;
1572 }
1573 
1574 static void
gtk_file_selection_home_button(GtkWidget * widget,gpointer data)1575 gtk_file_selection_home_button (GtkWidget * widget, gpointer data)
1576 {
1577   GList *list;
1578 
1579   GtkFileSelection *fs = data;
1580 
1581   list = fs->next_history;
1582   if (list)
1583     {
1584       g_free (list->data);
1585       list = list->next;
1586     }
1587   g_list_free (fs->next_history);
1588   fs->next_history = NULL;
1589 
1590   gtk_file_selection_populate (fs, "~/", FALSE);
1591 }
1592 
1593 static void
gtk_file_selection_up_button(GtkWidget * widget,gpointer data)1594 gtk_file_selection_up_button (GtkWidget * widget, gpointer data)
1595 {
1596   GtkFileSelection *fs = data;
1597   GList *list;
1598 
1599   g_return_if_fail (fs != NULL);
1600   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1601 
1602   list = fs->next_history;
1603   if (list)
1604     {
1605       g_free (list->data);
1606       list = list->next;
1607     }
1608   g_list_free (fs->next_history);
1609   fs->next_history = NULL;
1610 
1611   gtk_file_selection_populate (fs, "../", FALSE);	/*change directories. */
1612 
1613 }
1614 
1615 static void
gtk_file_selection_prev_button(GtkWidget * widget,gpointer data)1616 gtk_file_selection_prev_button (GtkWidget * widget, gpointer data)
1617 {
1618   GtkFileSelection *fs = data;
1619   GList *list;
1620   GList *first;
1621   gchar *path;
1622 
1623   g_return_if_fail (fs != NULL);
1624   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1625 
1626   list = fs->prev_history;
1627 
1628   if (list && g_list_length (list) > 1)
1629     {
1630       first = list;		/* get first element */
1631       list = list->next;	/* pop off current directory */
1632 
1633       list->prev = NULL;	/* make this the new head. */
1634 
1635       fs->prev_history = list;	/* update prev_history list */
1636       fs->next_history = g_list_prepend (fs->next_history, first->data);	/* put it on next_history */
1637 
1638       first->next = NULL;	/* orphan the old first node */
1639       g_list_free (first);	/* free the node (data is now in use by next_history) */
1640 
1641 
1642 
1643       path = g_malloc (strlen (list->data) + 4);	/* plenty of space */
1644       strcpy (path, list->data);	/* get the 2nd path in the history */
1645       strcat (path, "/");	/* append a '/' */
1646       gtk_file_selection_populate (fs, path, FALSE);	/* change directories. */
1647       g_free (path);
1648     }
1649 }
1650 
1651 static void
gtk_file_selection_next_button(GtkWidget * widget,gpointer data)1652 gtk_file_selection_next_button (GtkWidget * widget, gpointer data)
1653 {
1654   GtkFileSelection *fs = data;
1655   GList *list;
1656   GList *first;
1657   gchar *path;
1658 
1659   g_return_if_fail (fs != NULL);
1660   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1661 
1662   list = fs->next_history;
1663 
1664   if (list && g_list_length (list) > 0)
1665     {
1666       first = list;		/*get first element */
1667       list = list->next;	/*pop off current directory */
1668 
1669       if (list)
1670 	list->prev = NULL;
1671 
1672       fs->next_history = list;	/*update prev_history list */
1673 
1674       path = g_malloc (strlen (first->data) + 4);	/*plenty of space */
1675       strcpy (path, first->data);
1676       strcat (path, "/");	/*append a /   */
1677       gtk_file_selection_populate (fs, path, FALSE);	/*change directories. */
1678       g_free (path);
1679 
1680       first->next = NULL;	/* orphan the old first node */
1681       g_list_free (first);	/* free the node (data is now in use by next_history) */
1682 
1683     }
1684 }
1685 
1686 void static
gtk_file_selection_refresh_button(GtkWidget * widget,gpointer data)1687 gtk_file_selection_refresh_button (GtkWidget * widget, gpointer data)
1688 {
1689   GtkFileSelection *fs = data;
1690 
1691   g_return_if_fail (fs != NULL);
1692   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1693 
1694   gtk_file_selection_populate (fs, "", FALSE);
1695 }
1696 
1697 static void
gtk_file_selection_mask_entry_callback(GtkWidget * widget,gpointer data)1698 gtk_file_selection_mask_entry_callback (GtkWidget * widget, gpointer data)
1699 {
1700   GtkFileSelection *fs = data;
1701 
1702   if (fs->mask)
1703     g_free (fs->mask);
1704 
1705   fs->mask = g_strdup (gtk_entry_get_text (GTK_ENTRY (fs->mask_entry)));
1706 
1707   if (strlen (fs->mask) == 0)
1708     {
1709       g_free (fs->mask);
1710       fs->mask = NULL;
1711     }
1712 
1713   gtk_file_selection_refresh_button (widget, data);
1714 }
1715 
1716 static gboolean
gtk_file_selection_history_combo_list_key_handler(GtkWidget * widget,GdkEventKey * event,gpointer user_data)1717 gtk_file_selection_history_combo_list_key_handler (GtkWidget * widget,
1718 						   GdkEventKey * event,
1719 						   gpointer user_data)
1720 {
1721   /*
1722      g_print("Key pressed! \n");
1723    */
1724 
1725   return TRUE;
1726 }
1727 
1728 static gboolean
gtk_file_selection_history_combo_list_callback(GtkWidget * thelist,GdkEventButton * event,gpointer user_data)1729 gtk_file_selection_history_combo_list_callback (GtkWidget * thelist,
1730 						GdkEventButton * event,
1731 						gpointer user_data)
1732 {
1733 
1734   GtkFileSelection *fs = user_data;
1735   GList *list;
1736   gchar *path;
1737 
1738   list = fs->next_history;
1739   if (list)
1740     {
1741       g_free (list->data);
1742       list = list->next;
1743     }
1744   g_list_free (fs->next_history);
1745   fs->next_history = NULL;
1746 
1747   path =
1748     g_malloc (strlen
1749 	      (gtk_entry_get_text
1750 	       (GTK_ENTRY (((GtkCombo *) fs->history_combo)->entry))) + 4);
1751   strcpy (path,
1752 	  gtk_entry_get_text (GTK_ENTRY
1753 			      (((GtkCombo *) fs->history_combo)->entry)));
1754   strcat (path, "/");
1755 
1756   gtk_file_selection_populate (fs, path, TRUE);
1757 
1758   g_free (path);
1759 
1760   return TRUE;
1761 }
1762 
1763 static gboolean
gtk_file_selection_history_combo_callback(GtkWidget * widget,GdkEventKey * event,gpointer data)1764 gtk_file_selection_history_combo_callback (GtkWidget * widget,
1765 					   GdkEventKey * event, gpointer data)
1766 {
1767   GtkEntry *entry = (GtkEntry *) widget;
1768   GtkFileSelection *fs = data;
1769   GList *list;
1770   gchar *path;
1771 
1772   g_return_val_if_fail (fs != NULL, FALSE);
1773   g_return_val_if_fail (GTK_IS_FILE_SELECTION (fs), FALSE);
1774 
1775 
1776   if (event->keyval == GDK_Return)
1777     {
1778       list = fs->next_history;
1779       if (list)
1780 	{
1781 	  g_free (list->data);
1782 	  list = list->next;
1783 	}
1784       g_list_free (fs->next_history);
1785       fs->next_history = NULL;
1786 
1787       path = g_malloc (strlen (gtk_entry_get_text (entry)) + 4);
1788       strcpy (path, gtk_entry_get_text (entry));
1789       strcat (path, "/");
1790       gtk_file_selection_populate (fs, path, TRUE);
1791       g_free (path);
1792       gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
1793       return TRUE;
1794     }
1795   else
1796     {
1797       return FALSE;
1798     }
1799 
1800 }
1801 
1802 static void
gtk_file_selection_update_history_menu(GtkFileSelection * fs,gchar * current_directory)1803 gtk_file_selection_update_history_menu (GtkFileSelection * fs,
1804 					gchar * current_directory)
1805 {
1806   gchar *current_dir;
1807 
1808   g_return_if_fail (fs != NULL);
1809   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1810   g_return_if_fail (current_directory != NULL);
1811 
1812   current_dir = g_strdup (current_directory);
1813 
1814   if (fs->prev_history)
1815     {
1816       if (strcmp ((fs->prev_history)->data, current_dir))
1817 	{			/*if this item isn't on the top of the list */
1818 	  fs->prev_history =
1819 	    g_list_prepend (fs->prev_history, g_strdup (current_dir));
1820 	}
1821     }
1822   else
1823     {
1824       fs->prev_history =
1825 	g_list_prepend (fs->prev_history, g_strdup (current_dir));
1826     }
1827 
1828   gtk_combo_set_popdown_strings (GTK_COMBO (fs->history_combo),
1829 				 fs->prev_history);
1830 
1831   g_free (current_dir);
1832 }
1833 
1834 static void
gtk_file_selection_file_button(GtkWidget * widget,gint row,gint column,GdkEventButton * bevent,gpointer user_data)1835 gtk_file_selection_file_button (GtkWidget * widget,
1836 				gint row,
1837 				gint column,
1838 				GdkEventButton * bevent, gpointer user_data)
1839 {
1840   GtkFileSelection *fs = NULL;
1841   gchar *filename, *temp = NULL;
1842 
1843   g_return_if_fail (GTK_IS_CLIST (widget));
1844 
1845   fs = user_data;
1846   g_return_if_fail (fs != NULL);
1847   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1848 
1849   (void)gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp);
1850   filename = g_strdup (temp);
1851 
1852   if (filename)
1853     {
1854       if (bevent)
1855 	switch (bevent->type)
1856 	  {
1857 	  case GDK_2BUTTON_PRESS:
1858 	    gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1859 	    break;
1860 
1861 	  default:
1862 	    gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1863 	    break;
1864 	  }
1865       else
1866 	gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1867 
1868       g_free (filename);
1869     }
1870 }
1871 
1872 static void
gtk_file_selection_dir_button(GtkWidget * widget,gint row,gint column,GdkEventButton * bevent,gpointer user_data)1873 gtk_file_selection_dir_button (GtkWidget * widget,
1874 			       gint row,
1875 			       gint column,
1876 			       GdkEventButton * bevent, gpointer user_data)
1877 {
1878   GList *list;
1879   GtkFileSelection *fs = NULL;
1880   gchar *filename, *temp = NULL;
1881 
1882   g_return_if_fail (GTK_IS_CLIST (widget));
1883 
1884   fs = GTK_FILE_SELECTION (user_data);
1885   g_return_if_fail (fs != NULL);
1886   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1887 
1888   (void)gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1889   filename = g_strdup (temp);
1890 
1891   if (filename)
1892     {
1893       if (bevent)
1894 	switch (bevent->type)
1895 	  {
1896 	  case GDK_2BUTTON_PRESS:
1897 	    list = fs->next_history;
1898 	    if (list)
1899 	      {
1900 		g_free (list->data);
1901 		list = list->next;
1902 	      }
1903 	    g_list_free (fs->next_history);
1904 	    fs->next_history = NULL;
1905 
1906 	    gtk_file_selection_populate (fs, filename, FALSE);
1907 	    if (fs->saved_entry)
1908 	      {			/* might be NULL --MK30.12.2001 */
1909 		gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1910 				    fs->saved_entry);
1911 		g_free (fs->saved_entry);
1912 		fs->saved_entry = NULL;
1913 	      }
1914 	    break;
1915 
1916 	  default:
1917 	    /* here we need to add the "filename" to the beginning of what's already
1918 	       in the entry.  Save what's in the entry, then restore it on the double click
1919 	     */
1920 	    if (fs->saved_entry)
1921 	      g_free (fs->saved_entry);
1922 	    fs->saved_entry =
1923 	      g_strdup (gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)));
1924 
1925 	    temp = g_strconcat (filename, fs->saved_entry, NULL);
1926 	    gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), temp);
1927 	    g_free (temp);
1928 
1929 	    break;
1930 	  }
1931       else
1932 	gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1933 
1934       g_free (filename);
1935     }
1936 }
1937 
1938 static void
gtk_file_selection_undir_button(GtkWidget * widget,gint row,gint column,GdkEventButton * bevent,gpointer user_data)1939 gtk_file_selection_undir_button (GtkWidget * widget,
1940 				 gint row,
1941 				 gint column,
1942 				 GdkEventButton * bevent, gpointer user_data)
1943 {
1944   GtkFileSelection *fs = NULL;
1945   gchar *filename, *temp = NULL;
1946 
1947   g_return_if_fail (GTK_IS_CLIST (widget));
1948 
1949   fs = GTK_FILE_SELECTION (user_data);
1950   g_return_if_fail (fs != NULL);
1951   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
1952 
1953   (void)gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp);
1954   filename = g_strdup (temp);
1955 
1956   if (filename)
1957     {
1958       if (bevent)
1959 	switch (bevent->type)
1960 	  {
1961 	  default:
1962 	    /* here we need to add the "filename" to the beginning of what's already
1963 	       in the entry.  Save what's in the entry, then restore it on the double click
1964 	     */
1965 	    if (fs->saved_entry)
1966 	      {
1967 		gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
1968 				    fs->saved_entry);
1969 		g_free (fs->saved_entry);
1970 		fs->saved_entry = NULL;
1971 	      }
1972 	    break;
1973 	  }
1974       else
1975 	gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);	//?????
1976 
1977       g_free (filename);
1978     }
1979 }
1980 
1981 static void
gtk_file_selection_populate(GtkFileSelection * fs,gchar * rel_path,gint try_complete)1982 gtk_file_selection_populate (GtkFileSelection * fs,
1983 			     gchar * rel_path, gint try_complete)
1984 {
1985   CompletionState *cmpl_state;
1986   PossibleCompletion *poss;
1987   gchar *filename;
1988   gint row;
1989   gchar *rem_path = rel_path;
1990   gchar *sel_text;
1991   gchar *text[2];
1992   gint did_recurse = FALSE;
1993   gint possible_count = 0;
1994   gint selection_index = -1;
1995   gint file_list_width;
1996   gint dir_list_width;
1997 
1998   g_return_if_fail (fs != NULL);
1999   g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
2000 
2001   cmpl_state = (CompletionState *) fs->cmpl_state;
2002   poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
2003 
2004   if (!cmpl_state_okay (cmpl_state))
2005     {
2006       /* Something went wrong. */
2007       gtk_file_selection_abort (fs);
2008       return;
2009     }
2010 
2011   g_assert (cmpl_state->reference_dir);
2012 
2013   gtk_clist_freeze (GTK_CLIST (fs->dir_list));
2014   gtk_clist_clear (GTK_CLIST (fs->dir_list));
2015   gtk_clist_freeze (GTK_CLIST (fs->file_list));
2016   gtk_clist_clear (GTK_CLIST (fs->file_list));
2017 
2018   /* Set the dir_list to include ./ and ../ */
2019   text[1] = NULL;
2020   text[0] = "./";
2021   row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
2022 
2023   text[0] = "../";
2024   row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
2025 
2026   /*reset the max widths of the lists */
2027   dir_list_width = gdk_string_width (fs->dir_list->style->font, "../");
2028   gtk_clist_set_column_width (GTK_CLIST (fs->dir_list), 0, dir_list_width);
2029   file_list_width = 1;
2030   gtk_clist_set_column_width (GTK_CLIST (fs->file_list), 0, file_list_width);
2031 
2032   while (poss)
2033     {
2034       if (cmpl_is_a_completion (poss))
2035 	{
2036 	  possible_count += 1;
2037 
2038 	  filename = cmpl_this_completion (poss);
2039 
2040 	  text[0] = filename;
2041 
2042 	  if (cmpl_is_directory (poss))
2043 	    {
2044 	      if (strcmp (filename, "./") != 0 &&
2045 		  strcmp (filename, "../") != 0)
2046 		{
2047 		  int width = gdk_string_width (fs->dir_list->style->font,
2048 						filename);
2049 		  row = gtk_clist_append (GTK_CLIST (fs->dir_list), text);
2050 		  if (width > dir_list_width)
2051 		    {
2052 		      dir_list_width = width;
2053 		      gtk_clist_set_column_width (GTK_CLIST (fs->dir_list), 0,
2054 						  width);
2055 		    }
2056 		}
2057 	    }
2058 	  else
2059 	    {
2060 	      if (fs->mask)
2061 		{
2062 		  if (gtk_file_selection_match_mask (filename, fs->mask))
2063 		    {
2064 		      int width =
2065 			gdk_string_width (fs->file_list->style->font,
2066 					  filename);
2067 		      row =
2068 			gtk_clist_append (GTK_CLIST (fs->file_list), text);
2069 		      if (width > file_list_width)
2070 			{
2071 			  file_list_width = width;
2072 			  gtk_clist_set_column_width (GTK_CLIST
2073 						      (fs->file_list), 0,
2074 						      width);
2075 			}
2076 		    }
2077 		}
2078 	      else
2079 		{
2080 		  int width = gdk_string_width (fs->file_list->style->font,
2081 						filename);
2082 		  row = gtk_clist_append (GTK_CLIST (fs->file_list), text);
2083 		  if (width > file_list_width)
2084 		    {
2085 		      file_list_width = width;
2086 		      gtk_clist_set_column_width (GTK_CLIST (fs->file_list),
2087 						  0, width);
2088 		    }
2089 		}
2090 	    }
2091 	}
2092 
2093       poss = cmpl_next_completion (cmpl_state);
2094     }
2095 
2096   gtk_clist_thaw (GTK_CLIST (fs->dir_list));
2097   gtk_clist_thaw (GTK_CLIST (fs->file_list));
2098 
2099   /* File lists are set. */
2100 
2101   g_assert (cmpl_state->reference_dir);
2102 
2103   if (try_complete)
2104     {
2105 
2106       /* User is trying to complete filenames, so advance the user's input
2107        * string to the updated_text, which is the common leading substring
2108        * of all possible completions, and if its a directory attempt
2109        * attempt completions in it. */
2110 
2111       if (cmpl_updated_text (cmpl_state)[0])
2112 	{
2113 
2114 	  if (cmpl_updated_dir (cmpl_state))
2115 	    {
2116 	      gchar *dir_name = g_strdup (cmpl_updated_text (cmpl_state));
2117 
2118 	      did_recurse = TRUE;
2119 
2120 	      gtk_file_selection_populate (fs, dir_name, TRUE);
2121 
2122 	      g_free (dir_name);
2123 	    }
2124 	  else
2125 	    {
2126 	      if (fs->selection_entry)
2127 		gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
2128 				    cmpl_updated_text (cmpl_state));
2129 	    }
2130 	}
2131       else
2132 	{
2133 	  selection_index = (gint) ( cmpl_last_valid_char (cmpl_state) -
2134 	    ((gint)strlen (rel_path) - (gint)strlen (rem_path)) );
2135 	  if (fs->selection_entry)
2136 	    gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
2137 	}
2138     }
2139   else
2140     {
2141       if (fs->selection_entry){}
2142 	/* Here we need to take the old filename and keep it! */
2143 	/*gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), ""); */
2144 	;
2145     }
2146 
2147   if (!did_recurse)
2148     {
2149       if (fs->selection_entry)
2150 	gtk_entry_set_position (GTK_ENTRY (fs->selection_entry),
2151 				selection_index);
2152 
2153       if (fs->selection_entry)
2154 	{
2155 	  sel_text = g_strconcat (_("Selection: "),
2156 				  cmpl_reference_position (cmpl_state), NULL);
2157 
2158 	  gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text);
2159 	  g_free (sel_text);
2160 	}
2161 
2162       gtk_file_selection_update_history_menu (fs,
2163 					      cmpl_reference_position
2164 					      (cmpl_state));
2165 
2166     }
2167 }
2168 
2169 static void
gtk_file_selection_abort(GtkFileSelection * fs)2170 gtk_file_selection_abort (GtkFileSelection * fs)
2171 {
2172   gchar err_buf[256];
2173 
2174   snprintf (err_buf,256, _("Directory unreadable: %s"),
2175 	   cmpl_strerror (cmpl_errno));
2176 
2177   /*  BEEP gdk_beep();  */
2178 
2179   if (fs->selection_entry)
2180     gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
2181 }
2182 
2183 /**********************************************************************/
2184 /*			  External Interface                          */
2185 /**********************************************************************/
2186 
2187 /* The four completion state selectors
2188  */
2189 static gchar *
cmpl_updated_text(CompletionState * cmpl_state)2190 cmpl_updated_text (CompletionState * cmpl_state)
2191 {
2192   return cmpl_state->updated_text;
2193 }
2194 
2195 static gint
cmpl_updated_dir(CompletionState * cmpl_state)2196 cmpl_updated_dir (CompletionState * cmpl_state)
2197 {
2198   return cmpl_state->re_complete;
2199 }
2200 
2201 static gchar *
cmpl_reference_position(CompletionState * cmpl_state)2202 cmpl_reference_position (CompletionState * cmpl_state)
2203 {
2204   return cmpl_state->reference_dir->fullname;
2205 }
2206 
2207 static gint
cmpl_last_valid_char(CompletionState * cmpl_state)2208 cmpl_last_valid_char (CompletionState * cmpl_state)
2209 {
2210   return cmpl_state->last_valid_char;
2211 }
2212 
2213 static gchar *
cmpl_completion_fullname(gchar * text,CompletionState * cmpl_state)2214 cmpl_completion_fullname (gchar * text, CompletionState * cmpl_state)
2215 {
2216   static char nothing[2] = "";
2217 
2218   if (!cmpl_state_okay (cmpl_state))
2219     {
2220       return nothing;
2221     }
2222   else if (text[0] == '/')
2223     {
2224       strcpy (cmpl_state->updated_text, text);
2225     }
2226   else if (text[0] == '~')
2227     {
2228       CompletionDir *dir;
2229       char *slash;
2230 
2231       dir = open_user_dir (text, cmpl_state);
2232 
2233       if (!dir)
2234 	{
2235 	  /* spencer says just return ~something, so
2236 	   * for now just do it. */
2237 	  strcpy (cmpl_state->updated_text, text);
2238 	}
2239       else
2240 	{
2241 
2242 	  strcpy (cmpl_state->updated_text, dir->fullname);
2243 
2244 	  slash = strchr (text, '/');
2245 
2246 	  if (slash)
2247 	    strcat (cmpl_state->updated_text, slash);
2248 	}
2249     }
2250   else
2251     {
2252       strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
2253       if (strcmp (cmpl_state->reference_dir->fullname, "/") != 0)
2254 	strcat (cmpl_state->updated_text, "/");
2255       strcat (cmpl_state->updated_text, text);
2256     }
2257 
2258   return cmpl_state->updated_text;
2259 }
2260 
2261 /* The three completion selectors
2262  */
2263 static gchar *
cmpl_this_completion(PossibleCompletion * pc)2264 cmpl_this_completion (PossibleCompletion * pc)
2265 {
2266   return pc->text;
2267 }
2268 
2269 static gint
cmpl_is_directory(PossibleCompletion * pc)2270 cmpl_is_directory (PossibleCompletion * pc)
2271 {
2272   return pc->is_directory;
2273 }
2274 
2275 static gint
cmpl_is_a_completion(PossibleCompletion * pc)2276 cmpl_is_a_completion (PossibleCompletion * pc)
2277 {
2278   return pc->is_a_completion;
2279 }
2280 
2281 /**********************************************************************/
2282 /*	                 Construction, deletion                       */
2283 /**********************************************************************/
2284 
2285 static CompletionState *
cmpl_init_state(void)2286 cmpl_init_state (void)
2287 {
2288   gchar getcwd_buf[2 * MAXPATHLEN];
2289   CompletionState *new_state;
2290 
2291   new_state = g_new (CompletionState, 1);
2292 
2293   /* We don't use getcwd() on SUNOS, because, it does a popen("pwd")
2294    * and, if that wasn't bad enough, hangs in doing so.
2295    */
2296 #if defined(sun) && !defined(__SVR4)
2297   if (!getwd (getcwd_buf))
2298 #else
2299   if (!getcwd (getcwd_buf, MAXPATHLEN))
2300 #endif
2301     {
2302       /* Oh joy, we can't get the current directory. Um..., we should have
2303        * a root directory, right? Right? (Probably not portable to non-Unix)
2304        */
2305       strcpy (getcwd_buf, "/");
2306     }
2307 
2308 tryagain:
2309 
2310   new_state->reference_dir = NULL;
2311   new_state->completion_dir = NULL;
2312   new_state->active_completion_dir = NULL;
2313   new_state->directory_storage = NULL;
2314   new_state->directory_sent_storage = NULL;
2315   new_state->last_valid_char = 0;
2316   new_state->updated_text = g_new (gchar, MAXPATHLEN);
2317   new_state->updated_text_alloc = (gint)MAXPATHLEN;
2318   new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
2319   new_state->the_completion.text_alloc = (gint)MAXPATHLEN;
2320   new_state->user_dir_name_buffer = NULL;
2321   new_state->user_directories = NULL;
2322 
2323   new_state->reference_dir = open_dir (getcwd_buf, new_state);
2324 
2325   if (!new_state->reference_dir)
2326     {
2327       /* Directories changing from underneath us, grumble */
2328       strcpy (getcwd_buf, "/");
2329       goto tryagain;
2330     }
2331 
2332   return new_state;
2333 }
2334 
2335 static void
cmpl_free_dir_list(GList * dp0)2336 cmpl_free_dir_list (GList * dp0)
2337 {
2338   GList *dp = dp0;
2339 
2340   while (dp)
2341     {
2342       free_dir (dp->data);
2343       dp = dp->next;
2344     }
2345 
2346   g_list_free (dp0);
2347 }
2348 
2349 static void
cmpl_free_dir_sent_list(GList * dp0)2350 cmpl_free_dir_sent_list (GList * dp0)
2351 {
2352   GList *dp = dp0;
2353 
2354   while (dp)
2355     {
2356       free_dir_sent (dp->data);
2357       dp = dp->next;
2358     }
2359 
2360   g_list_free (dp0);
2361 }
2362 
2363 static void
cmpl_free_state(CompletionState * cmpl_state)2364 cmpl_free_state (CompletionState * cmpl_state)
2365 {
2366   cmpl_free_dir_list (cmpl_state->directory_storage);
2367   cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
2368 
2369   if (cmpl_state->user_dir_name_buffer)
2370     g_free (cmpl_state->user_dir_name_buffer);
2371   if (cmpl_state->user_directories)
2372     g_free (cmpl_state->user_directories);
2373   if (cmpl_state->the_completion.text)
2374     g_free (cmpl_state->the_completion.text);
2375   if (cmpl_state->updated_text)
2376     g_free (cmpl_state->updated_text);
2377 
2378   g_free (cmpl_state);
2379 }
2380 
2381 static void
free_dir(CompletionDir * dir)2382 free_dir (CompletionDir * dir)
2383 {
2384   g_free (dir->fullname);
2385   g_free (dir);
2386 }
2387 
2388 static void
free_dir_sent(CompletionDirSent * sent)2389 free_dir_sent (CompletionDirSent * sent)
2390 {
2391   g_free (sent->name_buffer);
2392   g_free (sent->entries);
2393   g_free (sent);
2394 }
2395 
2396 static void
prune_memory_usage(CompletionState * cmpl_state)2397 prune_memory_usage (CompletionState * cmpl_state)
2398 {
2399   GList *cdsl = cmpl_state->directory_sent_storage;
2400   GList *cdl = cmpl_state->directory_storage;
2401   GList *cdl0 = cdl;
2402   gint len = 0;
2403 
2404   for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
2405     cdsl = cdsl->next;
2406 
2407   if (cdsl)
2408     {
2409       cmpl_free_dir_sent_list (cdsl->next);
2410       cdsl->next = NULL;
2411     }
2412 
2413   cmpl_state->directory_storage = NULL;
2414   while (cdl)
2415     {
2416       if (cdl->data == cmpl_state->reference_dir)
2417 	cmpl_state->directory_storage = g_list_prepend (NULL, cdl->data);
2418       else
2419 	free_dir (cdl->data);
2420       cdl = cdl->next;
2421     }
2422 
2423   g_list_free (cdl0);
2424 }
2425 
2426 /**********************************************************************/
2427 /*                        The main entrances.                         */
2428 /**********************************************************************/
2429 
2430 static PossibleCompletion *
cmpl_completion_matches(gchar * text_to_complete,gchar ** remaining_text,CompletionState * cmpl_state)2431 cmpl_completion_matches (gchar * text_to_complete,
2432 			 gchar ** remaining_text,
2433 			 CompletionState * cmpl_state)
2434 {
2435   gchar *first_slash;
2436   PossibleCompletion *poss;
2437 
2438   prune_memory_usage (cmpl_state);
2439 
2440   g_assert (text_to_complete != NULL);
2441 
2442   cmpl_state->user_completion_index = -1;
2443   cmpl_state->last_completion_text = text_to_complete;
2444   cmpl_state->the_completion.text[0] = 0;
2445   cmpl_state->last_valid_char = 0;
2446   cmpl_state->updated_text_len = -1;
2447   cmpl_state->updated_text[0] = 0;
2448   cmpl_state->re_complete = FALSE;
2449 
2450   first_slash = strchr (text_to_complete, '/');
2451 
2452   if (text_to_complete[0] == '~' && !first_slash)
2453     {
2454       /* Text starts with ~ and there is no slash, show all the
2455        * home directory completions.
2456        */
2457       poss = attempt_homedir_completion (text_to_complete, cmpl_state);
2458 
2459       update_cmpl (poss, cmpl_state);
2460 
2461       return poss;
2462     }
2463 
2464   cmpl_state->reference_dir =
2465     open_ref_dir (text_to_complete, remaining_text, cmpl_state);
2466 
2467   if (!cmpl_state->reference_dir)
2468     return NULL;
2469 
2470   cmpl_state->completion_dir =
2471     find_completion_dir (*remaining_text, remaining_text, cmpl_state);
2472 
2473   cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2474 
2475   if (!cmpl_state->completion_dir)
2476     return NULL;
2477 
2478   cmpl_state->completion_dir->cmpl_index = -1;
2479   cmpl_state->completion_dir->cmpl_parent = NULL;
2480   cmpl_state->completion_dir->cmpl_text = *remaining_text;
2481 
2482   cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2483 
2484   cmpl_state->reference_dir = cmpl_state->completion_dir;
2485 
2486   poss = attempt_file_completion (cmpl_state);
2487 
2488   update_cmpl (poss, cmpl_state);
2489 
2490   return poss;
2491 }
2492 
2493 static PossibleCompletion *
cmpl_next_completion(CompletionState * cmpl_state)2494 cmpl_next_completion (CompletionState * cmpl_state)
2495 {
2496   PossibleCompletion *poss = NULL;
2497 
2498   cmpl_state->the_completion.text[0] = 0;
2499 
2500   if (cmpl_state->user_completion_index >= 0)
2501     poss =
2502       attempt_homedir_completion (cmpl_state->last_completion_text,
2503 				  cmpl_state);
2504   else
2505     poss = attempt_file_completion (cmpl_state);
2506 
2507   update_cmpl (poss, cmpl_state);
2508 
2509   return poss;
2510 }
2511 
2512 /**********************************************************************/
2513 /*			 Directory Operations                         */
2514 /**********************************************************************/
2515 
2516 /* Open the directory where completion will begin from, if possible. */
2517 static CompletionDir *
open_ref_dir(gchar * text_to_complete,gchar ** remaining_text,CompletionState * cmpl_state)2518 open_ref_dir (gchar * text_to_complete,
2519 	      gchar ** remaining_text, CompletionState * cmpl_state)
2520 {
2521   gchar *first_slash;
2522   CompletionDir *new_dir;
2523 
2524   first_slash = strchr (text_to_complete, '/');
2525 
2526   if (text_to_complete[0] == '~')
2527     {
2528       new_dir = open_user_dir (text_to_complete, cmpl_state);
2529 
2530       if (new_dir)
2531 	{
2532 	  if (first_slash)
2533 	    *remaining_text = first_slash + 1;
2534 	  else
2535 	    *remaining_text = text_to_complete + strlen (text_to_complete);
2536 	}
2537       else
2538 	{
2539 	  return NULL;
2540 	}
2541     }
2542   else if (text_to_complete[0] == '/' || !cmpl_state->reference_dir)
2543     {
2544       gchar *tmp = g_strdup (text_to_complete);
2545       gchar *p;
2546 
2547       p = tmp;
2548       while (*p && *p != '*' && *p != '?')
2549 	p++;
2550 
2551       *p = '\0';
2552       p = strrchr (tmp, '/');
2553       if (p)
2554 	{
2555 	  if (p == tmp)
2556 	    p++;
2557 
2558 	  *p = '\0';
2559 
2560 	  new_dir = open_dir (tmp, cmpl_state);
2561 
2562 	  if (new_dir)
2563 	    *remaining_text = text_to_complete +
2564 	      ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp));
2565 	}
2566       else
2567 	{
2568 	  /* If no possible candidates, use the cwd */
2569 	  gchar *curdir = g_get_current_dir ();
2570 
2571 	  new_dir = open_dir (curdir, cmpl_state);
2572 
2573 	  if (new_dir)
2574 	    *remaining_text = text_to_complete;
2575 
2576 	  g_free (curdir);
2577 	}
2578 
2579       g_free (tmp);
2580     }
2581   else
2582     {
2583       *remaining_text = text_to_complete;
2584 
2585       new_dir = open_dir (cmpl_state->reference_dir->fullname, cmpl_state);
2586     }
2587 
2588   if (new_dir)
2589     {
2590       new_dir->cmpl_index = -1;
2591       new_dir->cmpl_parent = NULL;
2592     }
2593 
2594   return new_dir;
2595 }
2596 
2597 /* open a directory by user name */
2598 static CompletionDir *
open_user_dir(gchar * text_to_complete,CompletionState * cmpl_state)2599 open_user_dir (gchar * text_to_complete, CompletionState * cmpl_state)
2600 {
2601   gchar *first_slash;
2602   gint cmp_len;
2603 
2604   g_assert (text_to_complete && text_to_complete[0] == '~');
2605 
2606   first_slash = strchr (text_to_complete, '/');
2607 
2608   if (first_slash)
2609     cmp_len = first_slash - text_to_complete - 1;
2610   else
2611     cmp_len = (gint)strlen (text_to_complete + 1);
2612 
2613   if (!cmp_len)
2614     {
2615       /* ~/ */
2616       gchar *homedir = g_get_home_dir ();
2617 
2618       if (homedir)
2619 	return open_dir (homedir, cmpl_state);
2620       else
2621 	return NULL;
2622     }
2623   else
2624     {
2625       /* ~user/ */
2626       char *copy = g_new (char, cmp_len + 1);
2627       struct passwd *pwd;
2628       strncpy (copy, text_to_complete + 1, (size_t)cmp_len);
2629       copy[cmp_len] = 0;
2630       pwd = getpwnam (copy);
2631       g_free (copy);
2632       if (!pwd)
2633 	{
2634 	  cmpl_errno = errno;
2635 	  return NULL;
2636 	}
2637 
2638       return open_dir (pwd->pw_dir, cmpl_state);
2639     }
2640 }
2641 
2642 /* open a directory relative the the current relative directory */
2643 static CompletionDir *
open_relative_dir(gchar * dir_name,CompletionDir * dir,CompletionState * cmpl_state)2644 open_relative_dir (gchar * dir_name,
2645 		   CompletionDir * dir, CompletionState * cmpl_state)
2646 {
2647   gchar path_buf[2 * MAXPATHLEN];
2648 
2649   if (dir->fullname_len + strlen (dir_name) + 2 >= MAXPATHLEN)
2650     {
2651       cmpl_errno = CMPL_ERRNO_TOO_LONG;
2652       return NULL;
2653     }
2654 
2655   strcpy (path_buf, dir->fullname);
2656 
2657   if (dir->fullname_len > 1)
2658     {
2659       path_buf[dir->fullname_len] = '/';
2660       strcpy (path_buf + dir->fullname_len + 1, dir_name);
2661     }
2662   else
2663     {
2664       strcpy (path_buf + dir->fullname_len, dir_name);
2665     }
2666 
2667   return open_dir (path_buf, cmpl_state);
2668 }
2669 
2670 /* after the cache lookup fails, really open a new directory */
2671 static CompletionDirSent *
open_new_dir(gchar * dir_name,struct stat * sbuf,gboolean stat_subdirs)2672 open_new_dir (gchar * dir_name, struct stat *sbuf, gboolean stat_subdirs)
2673 {
2674   CompletionDirSent *sent;
2675   DIR *directory;
2676   gchar *buffer_ptr;
2677   struct dirent *dirent_ptr;
2678   gint buffer_size = 0;
2679   gint entry_count = 0;
2680   gint i;
2681   struct stat ent_sbuf;
2682   char path_buf[MAXPATHLEN * 2];
2683   gint path_buf_len;
2684 
2685   sent = g_new (CompletionDirSent, 1);
2686   sent->mtime = sbuf->st_mtime;
2687   sent->inode = sbuf->st_ino;
2688   sent->device = sbuf->st_dev;
2689 
2690   path_buf_len = (gint)strlen (dir_name);
2691 
2692   if (path_buf_len > (gint)MAXPATHLEN)
2693     {
2694       cmpl_errno = CMPL_ERRNO_TOO_LONG;
2695       return NULL;
2696     }
2697 
2698   strcpy (path_buf, dir_name);
2699 
2700   directory = opendir (dir_name);
2701 
2702   if (!directory)
2703     {
2704       cmpl_errno = errno;
2705       return NULL;
2706     }
2707 
2708   while ((dirent_ptr = readdir (directory)) != NULL)
2709     {
2710       int entry_len = (int)strlen (dirent_ptr->d_name);
2711       buffer_size += entry_len + 1;
2712       entry_count += 1;
2713 
2714       if (path_buf_len + entry_len + 2 >= (int)MAXPATHLEN)
2715 	{
2716 	  cmpl_errno = CMPL_ERRNO_TOO_LONG;
2717 	  closedir (directory);
2718 	  return NULL;
2719 	}
2720     }
2721 
2722   sent->name_buffer = g_new (gchar, buffer_size);
2723   sent->entries = g_new (CompletionDirEntry, entry_count);
2724   sent->entry_count = entry_count;
2725 
2726   buffer_ptr = sent->name_buffer;
2727 
2728   rewinddir (directory);
2729 
2730   for (i = 0; i < entry_count; i += 1)
2731     {
2732       dirent_ptr = readdir (directory);
2733 
2734       if (!dirent_ptr)
2735 	{
2736 	  cmpl_errno = errno;
2737 	  closedir (directory);
2738 	  return NULL;
2739 	}
2740 
2741       strcpy (buffer_ptr, dirent_ptr->d_name);
2742       sent->entries[i].entry_name = buffer_ptr;
2743       buffer_ptr += strlen (dirent_ptr->d_name);
2744       *buffer_ptr = 0;
2745       buffer_ptr += 1;
2746 
2747       path_buf[path_buf_len] = '/';
2748       strcpy (path_buf + path_buf_len + 1, dirent_ptr->d_name);
2749 
2750       if (stat_subdirs)
2751 	{
2752 	  if (stat (path_buf, &ent_sbuf) >= 0 && S_ISDIR (ent_sbuf.st_mode))
2753 	    sent->entries[i].is_dir = 1;
2754 	  else
2755 	    /* stat may fail, and we don't mind, since it could be a
2756 	     * dangling symlink. */
2757 	    sent->entries[i].is_dir = 0;
2758 	}
2759       else
2760 	sent->entries[i].is_dir = 1;
2761     }
2762 
2763   qsort (sent->entries, (size_t)sent->entry_count, sizeof (CompletionDirEntry),
2764 	 compare_cmpl_dir);
2765 
2766   closedir (directory);
2767 
2768   return sent;
2769 }
2770 
2771 static gboolean
check_dir(gchar * dir_name,struct stat * result,gboolean * stat_subdirs)2772 check_dir (gchar * dir_name, struct stat *result, gboolean * stat_subdirs)
2773 {
2774   /* A list of directories that we know only contain other directories.
2775    * Trying to stat every file in these directories would be very
2776    * expensive.
2777    */
2778 
2779   static struct
2780   {
2781     gchar *name;
2782     gboolean present;
2783     struct stat statbuf;
2784   }
2785 /*@ignore@ splint does not like the incomplete initializer, but it is OK */
2786   no_stat_dirs[] =
2787   {
2788     {
2789       "/afs", FALSE,
2790       {
2791       0}
2792     }
2793     ,
2794     {
2795       "/net", FALSE,
2796       {
2797       0}
2798     }
2799   };
2800 /*@end@ splint should resume checking here */
2801   static const gint n_no_stat_dirs = (int)
2802    ( sizeof (no_stat_dirs) / sizeof (no_stat_dirs[0]));
2803   static gboolean initialized = FALSE;
2804 
2805   gint i;
2806 
2807   if (!initialized)
2808     {
2809       initialized = TRUE;
2810       for (i = 0; i < n_no_stat_dirs; i++)
2811 	{
2812 	  if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0)
2813 	    no_stat_dirs[i].present = TRUE;
2814 	}
2815     }
2816 
2817   if (stat (dir_name, result) < 0)
2818     {
2819       cmpl_errno = errno;
2820       return FALSE;
2821     }
2822 
2823   *stat_subdirs = TRUE;
2824   for (i = 0; i < n_no_stat_dirs; i++)
2825     {
2826       if (no_stat_dirs[i].present &&
2827 	  (no_stat_dirs[i].statbuf.st_dev == result->st_dev) &&
2828 	  (no_stat_dirs[i].statbuf.st_ino == result->st_ino))
2829 	{
2830 	  *stat_subdirs = FALSE;
2831 	  break;
2832 	}
2833     }
2834 
2835   return TRUE;
2836 }
2837 
2838 /* open a directory by absolute pathname */
2839 static CompletionDir *
open_dir(gchar * dir_name,CompletionState * cmpl_state)2840 open_dir (gchar * dir_name, CompletionState * cmpl_state)
2841 {
2842   struct stat sbuf;
2843   gboolean stat_subdirs;
2844   CompletionDirSent *sent;
2845   GList *cdsl;
2846 
2847   if (!check_dir (dir_name, &sbuf, &stat_subdirs))
2848     return NULL;
2849 
2850   cdsl = cmpl_state->directory_sent_storage;
2851 
2852   while (cdsl)
2853     {
2854       sent = cdsl->data;
2855 
2856       if (sent->inode == sbuf.st_ino &&
2857 	  sent->mtime == sbuf.st_mtime && sent->device == sbuf.st_dev)
2858 	return attach_dir (sent, dir_name, cmpl_state);
2859 
2860       cdsl = cdsl->next;
2861     }
2862 
2863   sent = open_new_dir (dir_name, &sbuf, stat_subdirs);
2864 
2865   if (sent)
2866     {
2867       cmpl_state->directory_sent_storage =
2868 	g_list_prepend (cmpl_state->directory_sent_storage, sent);
2869 
2870       return attach_dir (sent, dir_name, cmpl_state);
2871     }
2872 
2873   return NULL;
2874 }
2875 
2876 static CompletionDir *
attach_dir(CompletionDirSent * sent,gchar * dir_name,CompletionState * cmpl_state)2877 attach_dir (CompletionDirSent * sent, gchar * dir_name,
2878 	    CompletionState * cmpl_state)
2879 {
2880   CompletionDir *new_dir;
2881 
2882   new_dir = g_new (CompletionDir, 1);
2883 
2884   cmpl_state->directory_storage =
2885     g_list_prepend (cmpl_state->directory_storage, new_dir);
2886 
2887   new_dir->sent = sent;
2888   new_dir->fullname = g_strdup (dir_name);
2889   new_dir->fullname_len = (gint)strlen (dir_name);
2890 
2891   return new_dir;
2892 }
2893 
2894 static gint
correct_dir_fullname(CompletionDir * cmpl_dir)2895 correct_dir_fullname (CompletionDir * cmpl_dir)
2896 {
2897   gint length = (gint)strlen (cmpl_dir->fullname);
2898   struct stat sbuf;
2899 
2900   if (strcmp (cmpl_dir->fullname + length - 2, "/.") == 0)
2901     {
2902       if (length == 2)
2903 	{
2904 	  strcpy (cmpl_dir->fullname, "/");
2905 	  cmpl_dir->fullname_len = 1;
2906 	  return TRUE;
2907 	}
2908       else
2909 	{
2910 	  cmpl_dir->fullname[length - 2] = 0;
2911 	}
2912     }
2913   else if (strcmp (cmpl_dir->fullname + length - 3, "/./") == 0)
2914     cmpl_dir->fullname[length - 2] = 0;
2915   else if (strcmp (cmpl_dir->fullname + length - 3, "/..") == 0)
2916     {
2917       if (length == 3)
2918 	{
2919 	  strcpy (cmpl_dir->fullname, "/");
2920 	  cmpl_dir->fullname_len = 1;
2921 	  return TRUE;
2922 	}
2923 
2924       if (stat (cmpl_dir->fullname, &sbuf) < 0)
2925 	{
2926 	  cmpl_errno = errno;
2927 	  return FALSE;
2928 	}
2929 
2930       cmpl_dir->fullname[length - 2] = 0;
2931 
2932       if (!correct_parent (cmpl_dir, &sbuf))
2933 	return FALSE;
2934     }
2935   else if (strcmp (cmpl_dir->fullname + length - 4, "/../") == 0)
2936     {
2937       if (length == 4)
2938 	{
2939 	  strcpy (cmpl_dir->fullname, "/");
2940 	  cmpl_dir->fullname_len = 1;
2941 	  return TRUE;
2942 	}
2943 
2944       if (stat (cmpl_dir->fullname, &sbuf) < 0)
2945 	{
2946 	  cmpl_errno = errno;
2947 	  return FALSE;
2948 	}
2949 
2950       cmpl_dir->fullname[length - 3] = 0;
2951 
2952       if (!correct_parent (cmpl_dir, &sbuf))
2953 	return FALSE;
2954     }
2955 
2956   cmpl_dir->fullname_len = (gint)strlen (cmpl_dir->fullname);
2957 
2958   return TRUE;
2959 }
2960 
2961 static gint
correct_parent(CompletionDir * cmpl_dir,struct stat * sbuf)2962 correct_parent (CompletionDir * cmpl_dir, struct stat *sbuf)
2963 {
2964   struct stat parbuf;
2965   gchar *last_slash;
2966   gchar *new_name;
2967   gchar c = 0;
2968 
2969   last_slash = strrchr (cmpl_dir->fullname, '/');
2970 
2971   g_assert (last_slash);
2972 
2973   if (last_slash != cmpl_dir->fullname)
2974     {				/* last_slash[0] = 0; */
2975     }
2976   else
2977     {
2978       c = last_slash[1];
2979       last_slash[1] = 0;
2980     }
2981 
2982   if (stat (cmpl_dir->fullname, &parbuf) < 0)
2983     {
2984       cmpl_errno = errno;
2985       return FALSE;
2986     }
2987 
2988   if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
2989     /* it wasn't a link */
2990     return TRUE;
2991 
2992   if (c)
2993     last_slash[1] = c;
2994   /* else
2995      last_slash[0] = '/'; */
2996 
2997   /* it was a link, have to figure it out the hard way */
2998 
2999   new_name = find_parent_dir_fullname (cmpl_dir->fullname);
3000 
3001   if (!new_name)
3002     return FALSE;
3003 
3004   g_free (cmpl_dir->fullname);
3005 
3006   cmpl_dir->fullname = new_name;
3007 
3008   return TRUE;
3009 }
3010 
3011 static gchar *
find_parent_dir_fullname(gchar * dirname)3012 find_parent_dir_fullname (gchar * dirname)
3013 {
3014   gchar buffer[MAXPATHLEN];
3015   gchar buffer2[MAXPATHLEN];
3016 
3017 #if defined(sun) && !defined(__SVR4)
3018   if (!getwd (buffer))
3019 #else
3020   if (!getcwd (buffer, MAXPATHLEN))
3021 #endif
3022     {
3023       cmpl_errno = errno;
3024       return NULL;
3025     }
3026 
3027   if (chdir (dirname) != 0 || chdir ("..") != 0)
3028     {
3029       cmpl_errno = errno;
3030       return NULL;
3031     }
3032 
3033 #if defined(sun) && !defined(__SVR4)
3034   if (!getwd (buffer2))
3035 #else
3036   if (!getcwd (buffer2, MAXPATHLEN))
3037 #endif
3038     {
3039       chdir (buffer);
3040       cmpl_errno = errno;
3041 
3042       return NULL;
3043     }
3044 
3045   if (chdir (buffer) != 0)
3046     {
3047       cmpl_errno = errno;
3048       return NULL;
3049     }
3050 
3051   return g_strdup (buffer2);
3052 }
3053 
3054 /**********************************************************************/
3055 /*                        Completion Operations                       */
3056 /**********************************************************************/
3057 
3058 static PossibleCompletion *
attempt_homedir_completion(gchar * text_to_complete,CompletionState * cmpl_state)3059 attempt_homedir_completion (gchar * text_to_complete,
3060 			    CompletionState * cmpl_state)
3061 {
3062   gint index, length;
3063 
3064   if (!cmpl_state->user_dir_name_buffer && !get_pwdb (cmpl_state))
3065     return NULL;
3066   length = (gint) (strlen (text_to_complete) - 1);
3067 
3068   cmpl_state->user_completion_index += 1;
3069 
3070   while (cmpl_state->user_completion_index < cmpl_state->user_directories_len)
3071     {
3072       index = first_diff_index (text_to_complete + 1,
3073 				cmpl_state->user_directories
3074 				[cmpl_state->user_completion_index].login);
3075 
3076       switch (index)
3077 	{
3078 	case PATTERN_MATCH:
3079 	  break;
3080 	default:
3081 	  if (cmpl_state->last_valid_char < (index + 1))
3082 	    cmpl_state->last_valid_char = index + 1;
3083 	  cmpl_state->user_completion_index += 1;
3084 	  continue;
3085 	}
3086 
3087       cmpl_state->the_completion.is_a_completion = 1;
3088       cmpl_state->the_completion.is_directory = 1;
3089 
3090       (void)append_completion_text ("~", cmpl_state);
3091 
3092       (void)append_completion_text (cmpl_state->user_directories
3093 			      [cmpl_state->user_completion_index].login,
3094 			      cmpl_state);
3095 
3096       return append_completion_text ("/", cmpl_state);
3097     }
3098 
3099   if (text_to_complete[1] ||
3100       cmpl_state->user_completion_index > cmpl_state->user_directories_len)
3101     {
3102       cmpl_state->user_completion_index = -1;
3103       return NULL;
3104     }
3105   else
3106     {
3107       cmpl_state->user_completion_index += 1;
3108       cmpl_state->the_completion.is_a_completion = 1;
3109       cmpl_state->the_completion.is_directory = 1;
3110 
3111       return append_completion_text ("~/", cmpl_state);
3112     }
3113 }
3114 
3115 /* returns the index (>= 0) of the first differing character,
3116  * PATTERN_MATCH if the completion matches */
3117 static gint
first_diff_index(gchar * pat,gchar * text)3118 first_diff_index (gchar * pat, gchar * text)
3119 {
3120   gint diff = 0;
3121 
3122   while (*pat && *text && *text == *pat)
3123     {
3124       pat += 1;
3125       text += 1;
3126       diff += 1;
3127     }
3128 
3129   if (*pat)
3130     return diff;
3131 
3132   return PATTERN_MATCH;
3133 }
3134 
3135 static PossibleCompletion *
append_completion_text(gchar * text,CompletionState * cmpl_state)3136 append_completion_text (gchar * text, CompletionState * cmpl_state)
3137 {
3138   gint len, i = 1;
3139 
3140   if (!cmpl_state->the_completion.text)
3141     return NULL;
3142 
3143   len = (int) (strlen (text) + strlen (cmpl_state->the_completion.text) + 1);
3144 
3145   if (cmpl_state->the_completion.text_alloc > len)
3146     {
3147       strcat (cmpl_state->the_completion.text, text);
3148       return &cmpl_state->the_completion;
3149     }
3150 
3151   while (i < len)
3152     {
3153       i <<= 1;
3154     }
3155 
3156   cmpl_state->the_completion.text_alloc = i;
3157 
3158   cmpl_state->the_completion.text =
3159     (gchar *) g_realloc (cmpl_state->the_completion.text, (gulong)i);
3160 
3161   if (!cmpl_state->the_completion.text)
3162     return NULL;
3163   else
3164     {
3165       strcat (cmpl_state->the_completion.text, text);
3166       return &cmpl_state->the_completion;
3167     }
3168 }
3169 
3170 static CompletionDir *
find_completion_dir(gchar * text_to_complete,gchar ** remaining_text,CompletionState * cmpl_state)3171 find_completion_dir (gchar * text_to_complete,
3172 		     gchar ** remaining_text, CompletionState * cmpl_state)
3173 {
3174   gchar *first_slash = strchr (text_to_complete, '/');
3175   CompletionDir *dir = cmpl_state->reference_dir;
3176   CompletionDir *next;
3177   *remaining_text = text_to_complete;
3178 
3179   while (first_slash)
3180     {
3181       gint len = first_slash - *remaining_text;
3182       gint found = 0;
3183       gchar *found_name = NULL;	/* Quiet gcc */
3184       gint i;
3185       gchar *pat_buf = g_new (gchar, len + 1);
3186 
3187       strncpy (pat_buf, *remaining_text, (size_t)len);
3188       pat_buf[len] = 0;
3189 
3190       for (i = 0; i < dir->sent->entry_count; i += 1)
3191 	{
3192 	  if (dir->sent->entries[i].is_dir &&
3193 	      fnmatch (pat_buf, dir->sent->entries[i].entry_name,
3194 		       FNMATCH_FLAGS) != FNM_NOMATCH)
3195 	    {
3196 	      if (found)
3197 		{
3198 		  g_free (pat_buf);
3199 		  return dir;
3200 		}
3201 	      else
3202 		{
3203 		  found = 1;
3204 		  found_name = dir->sent->entries[i].entry_name;
3205 		}
3206 	    }
3207 	}
3208 
3209       if (!found)
3210 	{
3211 	  /* Perhaps we are trying to open an automount directory */
3212 	  found_name = pat_buf;
3213 	}
3214 
3215       next = open_relative_dir (found_name, dir, cmpl_state);
3216 
3217       if (!next)
3218 	{
3219 	  g_free (pat_buf);
3220 	  return NULL;
3221 	}
3222 
3223       next->cmpl_parent = dir;
3224 
3225       dir = next;
3226 
3227       if (!correct_dir_fullname (dir))
3228 	{
3229 	  g_free (pat_buf);
3230 	  return NULL;
3231 	}
3232 
3233       *remaining_text = first_slash + 1;
3234       first_slash = strchr (*remaining_text, '/');
3235 
3236       g_free (pat_buf);
3237     }
3238 
3239   return dir;
3240 }
3241 
3242 static void
update_cmpl(PossibleCompletion * poss,CompletionState * cmpl_state)3243 update_cmpl (PossibleCompletion * poss, CompletionState * cmpl_state)
3244 {
3245   gint cmpl_len;
3246 
3247   if (!poss || !cmpl_is_a_completion (poss))
3248     return;
3249 
3250   cmpl_len = (gint)strlen (cmpl_this_completion (poss));
3251 
3252   if (cmpl_state->updated_text_alloc < cmpl_len + 1)
3253     {
3254       cmpl_state->updated_text =
3255 	(gchar *) g_realloc (cmpl_state->updated_text,
3256 			     (gulong)cmpl_state->updated_text_alloc);
3257       cmpl_state->updated_text_alloc = 2 * cmpl_len;
3258     }
3259 
3260   if (cmpl_state->updated_text_len < 0)
3261     {
3262       strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3263       cmpl_state->updated_text_len = cmpl_len;
3264       cmpl_state->re_complete = cmpl_is_directory (poss);
3265     }
3266   else if (cmpl_state->updated_text_len == 0)
3267     {
3268       cmpl_state->re_complete = FALSE;
3269     }
3270   else
3271     {
3272       gint first_diff = first_diff_index (cmpl_state->updated_text,
3273 					  cmpl_this_completion (poss));
3274 
3275       cmpl_state->re_complete = FALSE;
3276 
3277       if (first_diff == PATTERN_MATCH)
3278 	return;
3279 
3280       if (first_diff > cmpl_state->updated_text_len)
3281 	strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3282 
3283       cmpl_state->updated_text_len = first_diff;
3284       cmpl_state->updated_text[first_diff] = 0;
3285     }
3286 }
3287 
3288 static PossibleCompletion *
attempt_file_completion(CompletionState * cmpl_state)3289 attempt_file_completion (CompletionState * cmpl_state)
3290 {
3291   gchar *pat_buf, *first_slash;
3292   CompletionDir *dir = cmpl_state->active_completion_dir;
3293 
3294   dir->cmpl_index += 1;
3295 
3296   if (dir->cmpl_index == dir->sent->entry_count)
3297     {
3298       if (dir->cmpl_parent == NULL)
3299 	{
3300 	  cmpl_state->active_completion_dir = NULL;
3301 
3302 	  return NULL;
3303 	}
3304       else
3305 	{
3306 	  cmpl_state->active_completion_dir = dir->cmpl_parent;
3307 
3308 	  return attempt_file_completion (cmpl_state);
3309 	}
3310     }
3311 
3312   g_assert (dir->cmpl_text);
3313 
3314   first_slash = strchr (dir->cmpl_text, '/');
3315 
3316   if (first_slash)
3317     {
3318       gint len = first_slash - dir->cmpl_text;
3319 
3320       pat_buf = g_new (gchar, len + 1);
3321       strncpy (pat_buf, dir->cmpl_text, (size_t)len);
3322       pat_buf[len] = 0;
3323     }
3324   else
3325     {
3326       gint len = (gint)strlen (dir->cmpl_text);
3327 
3328       pat_buf = g_new (gchar, len + 2);
3329       strcpy (pat_buf, dir->cmpl_text);
3330       strcpy (pat_buf + len, "*");
3331     }
3332 
3333   if (first_slash)
3334     {
3335       if (dir->sent->entries[dir->cmpl_index].is_dir)
3336 	{
3337 	  if (fnmatch
3338 	      (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3339 	       FNMATCH_FLAGS) != FNM_NOMATCH)
3340 	    {
3341 	      CompletionDir *new_dir;
3342 
3343 	      new_dir =
3344 		open_relative_dir (dir->sent->entries[dir->cmpl_index].
3345 				   entry_name, dir, cmpl_state);
3346 
3347 	      if (!new_dir)
3348 		{
3349 		  g_free (pat_buf);
3350 		  return NULL;
3351 		}
3352 
3353 	      new_dir->cmpl_parent = dir;
3354 
3355 	      new_dir->cmpl_index = -1;
3356 	      new_dir->cmpl_text = first_slash + 1;
3357 
3358 	      cmpl_state->active_completion_dir = new_dir;
3359 
3360 	      g_free (pat_buf);
3361 	      return attempt_file_completion (cmpl_state);
3362 	    }
3363 	  else
3364 	    {
3365 	      g_free (pat_buf);
3366 	      return attempt_file_completion (cmpl_state);
3367 	    }
3368 	}
3369       else
3370 	{
3371 	  g_free (pat_buf);
3372 	  return attempt_file_completion (cmpl_state);
3373 	}
3374     }
3375   else
3376     {
3377       if (dir->cmpl_parent != NULL)
3378 	{
3379 	  (void)append_completion_text (dir->fullname +
3380 				  strlen (cmpl_state->completion_dir->
3381 					  fullname) + 1, cmpl_state);
3382 	  (void)append_completion_text ("/", cmpl_state);
3383 	}
3384 
3385       (void)append_completion_text (dir->sent->entries[dir->cmpl_index].entry_name,
3386 			      cmpl_state);
3387 
3388       cmpl_state->the_completion.is_a_completion =
3389 	(fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name,
3390 		  FNMATCH_FLAGS) != FNM_NOMATCH);
3391 
3392       cmpl_state->the_completion.is_directory =
3393 	dir->sent->entries[dir->cmpl_index].is_dir;
3394       if (dir->sent->entries[dir->cmpl_index].is_dir)
3395 	(void)append_completion_text ("/", cmpl_state);
3396 
3397       g_free (pat_buf);
3398       return &cmpl_state->the_completion;
3399     }
3400 }
3401 
3402 
3403 static gint
get_pwdb(CompletionState * cmpl_state)3404 get_pwdb (CompletionState * cmpl_state)
3405 {
3406   struct passwd *pwd_ptr;
3407   gchar *buf_ptr;
3408   gint len = 0, i, count = 0;
3409 
3410   if (cmpl_state->user_dir_name_buffer)
3411     return TRUE;
3412 /*@i@*/  setpwent ();
3413 
3414 /*@i@*/  while ((pwd_ptr = getpwent ()) != NULL)
3415     {
3416       len += strlen (pwd_ptr->pw_name);
3417       len += strlen (pwd_ptr->pw_dir);
3418       len += 2;
3419       count += 1;
3420     }
3421 
3422 /*@i@*/  setpwent ();
3423 
3424   cmpl_state->user_dir_name_buffer = g_new (gchar, len);
3425   cmpl_state->user_directories = g_new (CompletionUserDir, count);
3426   cmpl_state->user_directories_len = count;
3427 
3428   buf_ptr = cmpl_state->user_dir_name_buffer;
3429 
3430   for (i = 0; i < count; i += 1)
3431     {
3432       pwd_ptr = getpwent ();
3433       if (!pwd_ptr)
3434 	{
3435 	  cmpl_errno = errno;
3436 	  goto error;
3437 	}
3438 
3439       strcpy (buf_ptr, pwd_ptr->pw_name);
3440       cmpl_state->user_directories[i].login = buf_ptr;
3441       buf_ptr += strlen (buf_ptr);
3442       buf_ptr += 1;
3443       strcpy (buf_ptr, pwd_ptr->pw_dir);
3444       cmpl_state->user_directories[i].homedir = buf_ptr;
3445       buf_ptr += strlen (buf_ptr);
3446       buf_ptr += 1;
3447     }
3448 
3449   qsort (cmpl_state->user_directories,
3450 	 (size_t)cmpl_state->user_directories_len,
3451 	 sizeof (CompletionUserDir), compare_user_dir);
3452 
3453 /*@i@*/  endpwent ();
3454 
3455   return TRUE;
3456 
3457 error:
3458 
3459   if (cmpl_state->user_dir_name_buffer)
3460     g_free (cmpl_state->user_dir_name_buffer);
3461   if (cmpl_state->user_directories)
3462     g_free (cmpl_state->user_directories);
3463 
3464   cmpl_state->user_dir_name_buffer = NULL;
3465   cmpl_state->user_directories = NULL;
3466 
3467   return FALSE;
3468 }
3469 
3470 static gint
compare_user_dir(const void * a,const void * b)3471 compare_user_dir (const void *a, const void *b)
3472 {
3473   return strcmp ((((CompletionUserDir *) a))->login,
3474 		 (((CompletionUserDir *) b))->login);
3475 }
3476 
3477 static gint
compare_cmpl_dir(const void * a,const void * b)3478 compare_cmpl_dir (const void *a, const void *b)
3479 {
3480   return strcmp ((((CompletionDirEntry *) a))->entry_name,
3481 		 (((CompletionDirEntry *) b))->entry_name);
3482 }
3483 
3484 static gint
cmpl_state_okay(CompletionState * cmpl_state)3485 cmpl_state_okay (CompletionState * cmpl_state)
3486 {
3487   return cmpl_state && cmpl_state->reference_dir;
3488 }
3489 
3490 static gchar *
cmpl_strerror(gint err)3491 cmpl_strerror (gint err)
3492 {
3493   if (err == CMPL_ERRNO_TOO_LONG)
3494     return "Name too long";
3495   else
3496     return g_strerror (err);
3497 }
3498 
3499 
3500 /* Testing area */
3501 #ifdef TORRIE_DEBUG
3502 
3503 /* Get the selected filename and print it to the console */
3504 void
file_ok_sel(GtkWidget * w,GtkFileSelection * fs)3505 file_ok_sel (GtkWidget * w, GtkFileSelection * fs)
3506 {
3507   g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
3508 }
3509 
3510 void
destroy(GtkWidget * widget,gpointer data)3511 destroy (GtkWidget * widget, gpointer data)
3512 {
3513   gtk_main_quit ();
3514 }
3515 
3516 int
main(int argc,char * argv[])3517 main (int argc, char *argv[])
3518 {
3519   GtkWidget *filew;
3520 
3521   gtk_init (&argc, &argv);
3522 
3523   /* Create a new file selection widget */
3524   filew = gtk_file_selection_new ("Michael's Glorious File Selector");
3525 //    gtk_file_selection_complete(GTK_FILE_SELECTION(filew),"bob");
3526 
3527 
3528   (void)gtk_signal_connect (GTK_OBJECT (filew), "destroy",
3529 		      (GtkSignalFunc) destroy, &filew);
3530   /* Connect the ok_button to file_ok_sel function */
3531   (void)gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
3532 		      "clicked", (GtkSignalFunc) file_ok_sel, filew);
3533 
3534   /* Connect the cancel_button to destroy the widget */
3535   (void)gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION
3536 					 (filew)->cancel_button),
3537 			     "clicked", (GtkSignalFunc) gtk_widget_destroy,
3538 			     GTK_OBJECT (filew));
3539 
3540 
3541   gtk_widget_show (filew);
3542 
3543 /*
3544     g_print("%d",gtk_file_selection_match_mask("mask.c","m*.c"));
3545     g_print("%d",gtk_file_selection_match_mask("mask.c","m???.c"));
3546 		g_print("%d",gtk_file_selection_match_mask("mask.c","m??*.c"));
3547 		g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c"));
3548 		g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c???"));
3549 		g_print("%d",gtk_file_selection_match_mask("mask.cout","m*.c*"));
3550 		g_print("%d",gtk_file_selection_match_mask("mask.cout","n*.c???"));
3551 		g_print("%d",gtk_file_selection_match_mask("mask.c","[mn]*"));
3552 		g_print("%d",gtk_file_selection_match_mask("COPYING","*.xpm"));
3553 */
3554   gtk_main ();
3555 
3556   return 0;
3557 }
3558 
3559 /* example-end */
3560 #endif
3561