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