]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserentry.c
Populate the completion store when the folder is done loading
[~andy/gtk] / gtk / gtkfilechooserentry.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilechooserentry.c: Entry with filename completion
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include <config.h>
22 #include <string.h>
23
24 #include "gtkcelllayout.h"
25 #include "gtkcellrenderertext.h"
26 #include "gtkentry.h"
27 #include "gtkfilechooserentry.h"
28 #include "gtkmain.h"
29 #include "gtkintl.h"
30 #include "gtkalias.h"
31
32 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
33
34 #define GTK_FILE_CHOOSER_ENTRY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
35 #define GTK_IS_FILE_CHOOSER_ENTRY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY))
36 #define GTK_FILE_CHOOSER_ENTRY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
37
38 struct _GtkFileChooserEntryClass
39 {
40   GtkEntryClass parent_class;
41 };
42
43 /* Action to take when the current folder finishes loading (for explicit or automatic completion) */
44 typedef enum {
45   LOAD_COMPLETE_NOTHING,
46   LOAD_COMPLETE_AUTOCOMPLETE,
47   LOAD_COMPLETE_INSERT_PREFIX
48 } LoadCompleteAction;
49
50 struct _GtkFileChooserEntry
51 {
52   GtkEntry parent_instance;
53
54   GtkFileChooserAction action;
55
56   GtkFileSystem *file_system;
57   GtkFilePath *base_folder;
58   gchar *file_part;
59   gint file_part_pos;
60   guint check_completion_idle;
61
62   /* Folder being loaded or already loaded */
63   GtkFilePath *current_folder_path;
64   GtkFileFolder *current_folder;
65   GtkFileSystemHandle *load_folder_handle;
66
67   LoadCompleteAction load_complete_action;
68
69   GtkListStore *completion_store;
70
71   guint start_autocompletion_idle_id;
72
73   guint has_completion : 1;
74   guint in_change      : 1;
75   guint eat_tabs       : 1;
76 };
77
78 enum
79 {
80   DISPLAY_NAME_COLUMN,
81   PATH_COLUMN,
82   N_COLUMNS
83 };
84
85 static void     gtk_file_chooser_entry_iface_init     (GtkEditableClass *iface);
86
87 static void     gtk_file_chooser_entry_finalize       (GObject          *object);
88 static void     gtk_file_chooser_entry_dispose        (GObject          *object);
89 static void     gtk_file_chooser_entry_grab_focus     (GtkWidget        *widget);
90 static gboolean gtk_file_chooser_entry_focus          (GtkWidget        *widget,
91                                                        GtkDirectionType  direction);
92 static void     gtk_file_chooser_entry_activate       (GtkEntry         *entry);
93 static void     gtk_file_chooser_entry_changed        (GtkEditable      *editable);
94 static void     gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
95                                                        const gchar *new_text,
96                                                        gint         new_text_length,
97                                                        gint        *position);
98
99 static void     clear_completion_callback (GtkFileChooserEntry *chooser_entry,
100                                            GParamSpec          *pspec);
101
102 #ifdef G_OS_WIN32
103 static gint     insert_text_callback      (GtkFileChooserEntry *widget,
104                                            const gchar         *new_text,
105                                            gint                 new_text_length,
106                                            gint                *position,
107                                            gpointer             user_data);
108 static void     delete_text_callback      (GtkFileChooserEntry *widget,
109                                            gint                 start_pos,
110                                            gint                 end_pos,
111                                            gpointer             user_data);
112 #endif
113
114 static gboolean match_selected_callback   (GtkEntryCompletion  *completion,
115                                            GtkTreeModel        *model,
116                                            GtkTreeIter         *iter,
117                                            GtkFileChooserEntry *chooser_entry);
118 static gboolean completion_match_func     (GtkEntryCompletion  *comp,
119                                            const char          *key,
120                                            GtkTreeIter         *iter,
121                                            gpointer             data);
122 static char    *maybe_append_separator_to_path (GtkFileChooserEntry *chooser_entry,
123                                                 GtkFilePath         *path,
124                                                 gchar               *display_name);
125
126 static void finished_loading_cb (GtkFileFolder *folder,
127                                  gpointer       data);
128 static void autocomplete (GtkFileChooserEntry *chooser_entry);
129
130 static GtkEditableClass *parent_editable_iface;
131
132 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserEntry, _gtk_file_chooser_entry, GTK_TYPE_ENTRY,
133                          G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
134                                                 gtk_file_chooser_entry_iface_init))
135
136 static void
137 _gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class)
138 {
139   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
140   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
141   GtkEntryClass *entry_class = GTK_ENTRY_CLASS (class);
142
143   gobject_class->finalize = gtk_file_chooser_entry_finalize;
144   gobject_class->dispose = gtk_file_chooser_entry_dispose;
145
146   widget_class->grab_focus = gtk_file_chooser_entry_grab_focus;
147   widget_class->focus = gtk_file_chooser_entry_focus;
148
149   entry_class->activate = gtk_file_chooser_entry_activate;
150 }
151
152 static void
153 gtk_file_chooser_entry_iface_init (GtkEditableClass *iface)
154 {
155   parent_editable_iface = g_type_interface_peek_parent (iface);
156
157   iface->do_insert_text = gtk_file_chooser_entry_do_insert_text;
158   iface->changed = gtk_file_chooser_entry_changed;
159 }
160
161 static void
162 _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
163 {
164   GtkEntryCompletion *comp;
165   GtkCellRenderer *cell;
166
167   g_object_set (chooser_entry, "truncate-multiline", TRUE, NULL);
168
169   comp = gtk_entry_completion_new ();
170   gtk_entry_completion_set_popup_single_match (comp, FALSE);
171
172   gtk_entry_completion_set_match_func (comp,
173                                        completion_match_func,
174                                        chooser_entry,
175                                        NULL);
176
177   cell = gtk_cell_renderer_text_new ();
178   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (comp),
179                               cell, TRUE);
180   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (comp),
181                                  cell,
182                                  "text", 0);
183
184   g_signal_connect (comp, "match_selected",
185                     G_CALLBACK (match_selected_callback), chooser_entry);
186
187   gtk_entry_set_completion (GTK_ENTRY (chooser_entry), comp);
188   g_object_unref (comp);
189
190   g_signal_connect (chooser_entry, "notify::cursor-position",
191                     G_CALLBACK (clear_completion_callback), NULL);
192   g_signal_connect (chooser_entry, "notify::selection-bound",
193                     G_CALLBACK (clear_completion_callback), NULL);
194
195 #ifdef G_OS_WIN32
196   g_signal_connect (chooser_entry, "insert_text",
197                     G_CALLBACK (insert_text_callback), NULL);
198   g_signal_connect (chooser_entry, "delete_text",
199                     G_CALLBACK (delete_text_callback), NULL);
200 #endif
201 }
202
203 static void
204 gtk_file_chooser_entry_finalize (GObject *object)
205 {
206   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
207
208   gtk_file_path_free (chooser_entry->base_folder);
209   gtk_file_path_free (chooser_entry->current_folder_path);
210   g_free (chooser_entry->file_part);
211
212   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->finalize (object);
213 }
214
215 static void
216 gtk_file_chooser_entry_dispose (GObject *object)
217 {
218   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
219
220   if (chooser_entry->start_autocompletion_idle_id != 0)
221     {
222       g_source_remove (chooser_entry->start_autocompletion_idle_id);
223       chooser_entry->start_autocompletion_idle_id = 0;
224     }
225
226   if (chooser_entry->completion_store)
227     {
228       g_object_unref (chooser_entry->completion_store);
229       chooser_entry->completion_store = NULL;
230     }
231
232   if (chooser_entry->load_folder_handle)
233     {
234       gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
235       chooser_entry->load_folder_handle = NULL;
236     }
237
238   if (chooser_entry->current_folder)
239     {
240       g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
241                                             G_CALLBACK (finished_loading_cb), chooser_entry);
242       g_object_unref (chooser_entry->current_folder);
243       chooser_entry->current_folder = NULL;
244     }
245
246   if (chooser_entry->file_system)
247     {
248       g_object_unref (chooser_entry->file_system);
249       chooser_entry->file_system = NULL;
250     }
251
252   if (chooser_entry->check_completion_idle)
253     {
254       g_source_remove (chooser_entry->check_completion_idle);
255       chooser_entry->check_completion_idle = 0;
256     }
257
258   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object);
259 }
260
261 /* Match functions for the GtkEntryCompletion */
262 static gboolean
263 match_selected_callback (GtkEntryCompletion  *completion,
264                          GtkTreeModel        *model,
265                          GtkTreeIter         *iter,
266                          GtkFileChooserEntry *chooser_entry)
267 {
268   char *display_name;
269   GtkFilePath *path;
270   gint pos;
271   
272   gtk_tree_model_get (model, iter,
273                       DISPLAY_NAME_COLUMN, &display_name,
274                       PATH_COLUMN, &path,
275                       -1);
276
277   if (!display_name || !path)
278     {
279       /* these shouldn't complain if passed NULL */
280       gtk_file_path_free (path);
281       g_free (display_name);
282       return FALSE;
283     }
284
285   display_name = maybe_append_separator_to_path (chooser_entry, path, display_name);
286
287   pos = chooser_entry->file_part_pos;
288
289   /* We don't set in_change here as we want to update the current_folder
290    * variable */
291   gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
292                             pos, -1);
293   gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
294                             display_name, -1, 
295                             &pos);
296   gtk_editable_set_position (GTK_EDITABLE (chooser_entry), -1);
297
298   gtk_file_path_free (path);
299   g_free (display_name);
300
301   return TRUE;
302 }
303
304 /* Match function for the GtkEntryCompletion */
305 static gboolean
306 completion_match_func (GtkEntryCompletion *comp,
307                        const char         *key_unused,
308                        GtkTreeIter        *iter,
309                        gpointer            data)
310 {
311   GtkFileChooserEntry *chooser_entry;
312   char *name;
313   gboolean result;
314   char *norm_file_part;
315   char *norm_name;
316
317   chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
318
319   /* We ignore the key because it is the contents of the entry.  Instead, we
320    * just use our precomputed file_part.
321    */
322   if (!chooser_entry->file_part)
323     {
324       return FALSE;
325     }
326
327   gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store), iter, DISPLAY_NAME_COLUMN, &name, -1);
328   if (!name)
329     {
330       return FALSE; /* Uninitialized row, ugh */
331     }
332
333   /* If we have an empty file_part, then we're at the root of a directory.  In
334    * that case, we want to match all non-dot files.  We might want to match
335    * dot_files too if show_hidden is TRUE on the fileselector in the future.
336    */
337   /* Additionally, support for gnome .hidden files would be sweet, too */
338   if (chooser_entry->file_part[0] == '\000')
339     {
340       if (name[0] == '.')
341         result = FALSE;
342       else
343         result = TRUE;
344       g_free (name);
345
346       return result;
347     }
348
349
350   norm_file_part = g_utf8_normalize (chooser_entry->file_part, -1, G_NORMALIZE_ALL);
351   norm_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
352
353 #ifdef G_PLATFORM_WIN32
354   {
355     gchar *temp;
356
357     temp = norm_file_part;
358     norm_file_part = g_utf8_casefold (norm_file_part, -1);
359     g_free (temp);
360
361     temp = norm_name;
362     norm_name = g_utf8_casefold (norm_name, -1);
363     g_free (temp);
364   }
365 #endif
366
367   result = (strncmp (norm_file_part, norm_name, strlen (norm_file_part)) == 0);
368
369   g_free (norm_file_part);
370   g_free (norm_name);
371   g_free (name);
372   
373   return result;
374 }
375
376 /* This function will append a directory separator to paths to
377  * display_name iff the path associated with it is a directory.
378  * maybe_append_separator_to_path will g_free the display_name and
379  * return a new one if needed.  Otherwise, it will return the old one.
380  * You should be safe calling
381  *
382  * display_name = maybe_append_separator_to_path (entry, path, display_name);
383  * ...
384  * g_free (display_name);
385  */
386 static char *
387 maybe_append_separator_to_path (GtkFileChooserEntry *chooser_entry,
388                                 GtkFilePath         *path,
389                                 gchar               *display_name)
390 {
391   if (!g_str_has_suffix (display_name, G_DIR_SEPARATOR_S) && path)
392     {
393       GtkFileInfo *info;
394             
395       info = gtk_file_folder_get_info (chooser_entry->current_folder,
396                                        path, NULL); /* NULL-GError */
397
398       if (info)
399         {
400           if (gtk_file_info_get_is_folder (info))
401             {
402               gchar *tmp = display_name;
403               display_name = g_strconcat (tmp, G_DIR_SEPARATOR_S, NULL);
404               g_free (tmp);
405             }
406           
407           gtk_file_info_free (info);
408         }
409     }
410
411   return display_name;
412 }
413
414 /* Determines if the completion model has entries with a common prefix relative
415  * to the current contents of the entry.  Also, if there's one and only one such
416  * path, stores it in unique_path_ret.
417  */
418 static void
419 find_common_prefix (GtkFileChooserEntry *chooser_entry,
420                     gchar               **common_prefix_ret,
421                     GtkFilePath         **unique_path_ret)
422 {
423   GtkEditable *editable;
424   GtkTreeIter iter;
425   gboolean parsed;
426   gboolean valid;
427   char *text_up_to_cursor;
428   GtkFilePath *parsed_folder_path;
429   char *parsed_file_part;
430
431   *common_prefix_ret = NULL;
432   *unique_path_ret = NULL;
433
434   if (chooser_entry->completion_store == NULL)
435     return;
436
437   editable = GTK_EDITABLE (chooser_entry);
438
439   text_up_to_cursor = gtk_editable_get_chars (editable, 0, gtk_editable_get_position (editable));
440
441   parsed = gtk_file_system_parse (chooser_entry->file_system,
442                                   chooser_entry->base_folder,
443                                   text_up_to_cursor,
444                                   &parsed_folder_path,
445                                   &parsed_file_part,
446                                   NULL); /* NULL-GError */
447
448   printf ("Text up to cursor: \"%s\"\n", text_up_to_cursor);
449   printf ("parsed_folder_path: \"%s\"\nparsed_file_part: \"%s\"\n",
450           parsed_folder_path ? (char *) parsed_folder_path : "<NONE>",
451           parsed_file_part ? parsed_file_part : "<NONE>");
452
453   g_free (text_up_to_cursor);
454
455   if (!parsed)
456     return;
457
458   /* FIXME: assert that the current folder path is the same as the parsed path? */
459
460   valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
461
462   while (valid)
463     {
464       gchar *display_name;
465       GtkFilePath *path;
466
467       gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store),
468                           &iter,
469                           DISPLAY_NAME_COLUMN, &display_name,
470                           PATH_COLUMN, &path,
471                           -1);
472
473       if (g_str_has_prefix (display_name, parsed_file_part))
474         {
475           if (!*common_prefix_ret)
476             {
477               *common_prefix_ret = g_strdup (display_name);
478               *unique_path_ret = gtk_file_path_copy (path);
479             }
480           else
481             {
482               gchar *p = *common_prefix_ret;
483               const gchar *q = display_name;
484                   
485               while (*p && *p == *q)
486                 {
487                   p++;
488                   q++;
489                 }
490                   
491               *p = '\0';
492
493               gtk_file_path_free (*unique_path_ret);
494               *unique_path_ret = NULL;
495             }
496         }
497
498       g_free (display_name);
499       gtk_file_path_free (path);
500       valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
501     }
502
503   gtk_file_path_free (parsed_folder_path);
504   g_free (parsed_file_part);
505 }
506
507 /* Finds a common prefix based on the contents of the entry and mandatorily appends it */
508 static void
509 append_common_prefix (GtkFileChooserEntry *chooser_entry,
510                       gboolean             highlight)
511 {
512   gchar *common_prefix;
513   GtkFilePath *unique_path;
514
515   find_common_prefix (chooser_entry, &common_prefix, &unique_path);
516
517   if (unique_path)
518     {
519       common_prefix = maybe_append_separator_to_path (chooser_entry,
520                                                       unique_path,
521                                                       common_prefix);
522       gtk_file_path_free (unique_path);
523     }
524
525   printf ("common prefix: \"%s\"\n",
526           common_prefix ? common_prefix : "<NONE>");
527
528   if (common_prefix)
529     {
530       gint cursor_pos;
531       gint common_prefix_len;
532       gint pos;
533
534       cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry));
535       common_prefix_len = g_utf8_strlen (common_prefix, -1);
536
537       pos = chooser_entry->file_part_pos;
538
539       chooser_entry->in_change = TRUE;
540       printf ("Deleting range (%d, %d)\n", pos, cursor_pos);
541       gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
542                                 pos, cursor_pos);
543       printf ("Inserting common prefix at %d\n", pos);
544       gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
545                                 common_prefix, -1, 
546                                 &pos);
547       chooser_entry->in_change = FALSE;
548
549       if (highlight)
550         {
551           printf ("Selecting range (%d, %d)\n", cursor_pos, pos);
552           gtk_editable_select_region (GTK_EDITABLE (chooser_entry),
553                                       cursor_pos,
554                                       pos); /* cursor_pos + common_prefix_len); */
555           chooser_entry->has_completion = TRUE;
556         }
557       else
558         gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos);
559
560       g_free (common_prefix);
561     }
562 }
563
564 static gboolean
565 check_completion_callback (GtkFileChooserEntry *chooser_entry)
566 {
567   GDK_THREADS_ENTER ();
568
569   g_assert (chooser_entry->file_part);
570
571   chooser_entry->check_completion_idle = 0;
572
573   if (strcmp (chooser_entry->file_part, "") == 0)
574     goto done;
575
576   /* We only insert the common prefix without requiring the user to hit Tab in
577    * the "open" modes.  For "save" modes, the user must hit Tab to cause the prefix
578    * to be inserted.  That happens in gtk_file_chooser_entry_focus().
579    */
580   if ((chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN
581        || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
582       && gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length)
583     append_common_prefix (chooser_entry, TRUE);
584
585  done:
586
587   GDK_THREADS_LEAVE ();
588
589   return FALSE;
590 }
591
592 static guint
593 idle_add (GtkFileChooserEntry *chooser_entry, 
594           GCallback            cb)
595 {
596   GSource *source;
597   guint id;
598
599   source = g_idle_source_new ();
600   g_source_set_priority (source, G_PRIORITY_HIGH);
601   g_source_set_closure (source,
602                         g_cclosure_new_object (cb, G_OBJECT (chooser_entry)));
603   id = g_source_attach (source, NULL);
604   g_source_unref (source);
605
606   return id;
607 }
608
609 static void
610 add_completion_idle (GtkFileChooserEntry *chooser_entry)
611 {
612   /* idle to update the selection based on the file list */
613   if (chooser_entry->check_completion_idle == 0)
614     chooser_entry->check_completion_idle = 
615       idle_add (chooser_entry, G_CALLBACK (check_completion_callback));
616 }
617
618 static void
619 gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
620                                        const gchar *new_text,
621                                        gint         new_text_length,
622                                        gint        *position)
623 {
624   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
625
626   parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position);
627
628   if (! chooser_entry->in_change)
629     add_completion_idle (GTK_FILE_CHOOSER_ENTRY (editable));
630 }
631
632 static void
633 gtk_file_chooser_entry_grab_focus (GtkWidget *widget)
634 {
635   GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget);
636   _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget));
637 }
638
639 static gboolean
640 gtk_file_chooser_entry_focus (GtkWidget        *widget,
641                               GtkDirectionType  direction)
642 {
643   GtkFileChooserEntry *chooser_entry;
644   GtkEditable *editable;
645   GtkEntry *entry;
646   GdkModifierType state;
647   gboolean control_pressed;
648
649   chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
650   editable = GTK_EDITABLE (widget);
651   entry = GTK_ENTRY (widget);
652
653   if (!chooser_entry->eat_tabs)
654     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
655
656   control_pressed = FALSE;
657
658   if (gtk_get_current_event_state (&state))
659     {
660       if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
661         control_pressed = TRUE;
662     }
663
664   /* This is a bit evil -- it makes Tab never leave the entry. It basically
665    * makes it 'safe' for people to hit. */
666   if ((direction == GTK_DIR_TAB_FORWARD) &&
667       (GTK_WIDGET_HAS_FOCUS (widget)) &&
668       (! control_pressed))
669     {
670       gint pos = 0;
671
672       if (chooser_entry->has_completion)
673         {
674           gint sel_end;
675
676           if (gtk_editable_get_selection_bounds (editable, NULL, &sel_end))
677             gtk_editable_set_position (editable, sel_end);
678         }
679       else
680         append_common_prefix (chooser_entry, FALSE);
681
682       /* Trigger the completion window to pop up again by a 
683        * zero-length insertion, a bit of a hack.
684        */
685       gtk_editable_insert_text (editable, "", -1, &pos);
686
687       return TRUE;
688     }
689   else
690     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
691 }
692
693 static void
694 gtk_file_chooser_entry_activate (GtkEntry *entry)
695 {
696   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry);
697
698   if (chooser_entry->has_completion)
699     {
700       gtk_editable_set_position (GTK_EDITABLE (entry),
701                                  entry->text_length);
702     }
703   
704   GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry);
705 }
706
707 /* Fills the completion store from the contents of the current folder */
708 static void
709 populate_completion_store (GtkFileChooserEntry *chooser_entry)
710 {
711   GSList *paths;
712   GSList *tmp_list;
713
714   if (!gtk_file_folder_list_children (chooser_entry->current_folder, &paths, NULL)) /* NULL-GError */
715     return;
716
717   g_assert (chooser_entry->completion_store != NULL);
718
719   /* Bah.  Need to turn off sorting */
720   for (tmp_list = paths; tmp_list; tmp_list = tmp_list->next)
721     {
722       GtkFileInfo *info;
723       GtkFilePath *path;
724
725       path = tmp_list->data;
726
727       info = gtk_file_folder_get_info (chooser_entry->current_folder,
728                                        path,
729                                        NULL); /* NULL-GError */
730       if (info)
731         {
732           gchar *display_name = g_strdup (gtk_file_info_get_display_name (info));
733           GtkTreeIter iter;
734
735           display_name = maybe_append_separator_to_path (chooser_entry, path, display_name);
736
737           gtk_list_store_append (chooser_entry->completion_store, &iter);
738           gtk_list_store_set (chooser_entry->completion_store, &iter,
739                               DISPLAY_NAME_COLUMN, display_name,
740                               PATH_COLUMN, path,
741                               -1);
742
743           gtk_file_info_free (info);
744           g_free (display_name);
745         }
746     }
747
748   gtk_file_paths_free (paths);
749
750   /* FIXME: we want to turn off sorting temporarily.  I suck... */
751   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store),
752                                         DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING);
753 }
754
755 /* When we finish loading the current folder, this function should get called to
756  * perform the deferred autocompletion or explicit completion.
757  */
758 static void
759 perform_load_complete_action (GtkFileChooserEntry *chooser_entry)
760 {
761   switch (chooser_entry->load_complete_action)
762     {
763     case LOAD_COMPLETE_NOTHING:
764       break;
765
766     case LOAD_COMPLETE_AUTOCOMPLETE:
767       printf ("Load is complete; will autocomplete immediately\n");
768       autocomplete (chooser_entry);
769       break;
770
771     case LOAD_COMPLETE_INSERT_PREFIX:
772       /* FIXME */
773       break;
774
775     default:
776       g_assert_not_reached ();
777     }
778
779   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
780 }
781
782 static void
783 finish_folder_load (GtkFileChooserEntry *chooser_entry)
784 {
785   populate_completion_store (chooser_entry);
786   perform_load_complete_action (chooser_entry);
787 }
788
789 /* Callback when the current folder finishes loading */
790 static void
791 finished_loading_cb (GtkFileFolder *folder,
792                      gpointer       data)
793 {
794   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
795
796   finish_folder_load (chooser_entry);
797 }
798
799 /* Callback when the current folder's handle gets obtained (not necessarily loaded completely) */
800 static void
801 load_directory_get_folder_callback (GtkFileSystemHandle *handle,
802                                     GtkFileFolder       *folder,
803                                     const GError        *error,
804                                     gpointer             data)
805 {
806   gboolean cancelled = handle->cancelled;
807   GtkFileChooserEntry *chooser_entry = data;
808
809   if (handle != chooser_entry->load_folder_handle)
810     goto out;
811
812   chooser_entry->load_folder_handle = NULL;
813
814   if (cancelled || error)
815     goto out;
816
817   g_assert (folder != NULL);
818   chooser_entry->current_folder = folder;
819
820   if (gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
821     finish_folder_load (chooser_entry);
822   else
823     g_signal_connect (chooser_entry->current_folder, "finished-loading",
824                       G_CALLBACK (finished_loading_cb), chooser_entry);
825
826   /* FIXME: connect to the following two signals?  Create the completion store here, or wait until the folder is loaded? */
827   
828   chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS,
829                                                         G_TYPE_STRING,
830                                                         GTK_TYPE_FILE_PATH);
831
832   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)),
833                                   GTK_TREE_MODEL (chooser_entry->completion_store));
834
835 out:
836   g_object_unref (chooser_entry);
837   g_object_unref (handle);
838 }
839
840 static void
841 start_loading_current_folder (GtkFileChooserEntry *chooser_entry)
842 {
843   if (chooser_entry->current_folder_path == NULL ||
844       chooser_entry->file_system == NULL)
845     return;
846
847   g_assert (chooser_entry->current_folder == NULL);
848   g_assert (chooser_entry->completion_store == NULL);
849   g_assert (chooser_entry->load_folder_handle == NULL);
850
851   printf ("Starting async load of folder %s\n", (char *) chooser_entry->current_folder_path);
852
853   chooser_entry->load_folder_handle =
854     gtk_file_system_get_folder (chooser_entry->file_system,
855                                 chooser_entry->current_folder_path,
856                                 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER,
857                                 load_directory_get_folder_callback,
858                                 g_object_ref (chooser_entry));
859 }
860
861 static void
862 reload_current_folder (GtkFileChooserEntry *chooser_entry,
863                        GtkFilePath         *folder_path,
864                        gboolean             force_reload)
865 {
866   gboolean reload = FALSE;
867
868   if (chooser_entry->current_folder_path)
869     {
870       if ((folder_path && gtk_file_path_compare (folder_path, chooser_entry->current_folder_path) != 0)
871           || force_reload)
872         {
873           reload = TRUE;
874
875           /* We changed our current directory.  We need to clear out the old
876            * directory information.
877            */
878           if (chooser_entry->current_folder)
879             {
880               if (chooser_entry->load_folder_handle)
881                 {
882                   printf ("Cancelling folder load\n");
883                   gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
884                   chooser_entry->load_folder_handle = NULL;
885                 }
886
887               g_object_unref (chooser_entry->current_folder);
888               chooser_entry->current_folder = NULL;
889             }
890
891           if (chooser_entry->completion_store)
892             {
893               gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);
894               g_object_unref (chooser_entry->completion_store);
895               chooser_entry->completion_store = NULL;
896             }
897
898           gtk_file_path_free (chooser_entry->current_folder_path);
899           chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
900         }
901     }
902   else
903     {
904       chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
905       reload = TRUE;
906     }
907
908   if (reload)
909     start_loading_current_folder (chooser_entry);
910 }
911
912 static void
913 refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry)
914 {
915   GtkEditable *editable;
916   gchar *text_up_to_cursor;
917   GtkFilePath *folder_path;
918   gchar *file_part;
919   gsize total_len, file_part_len;
920   gint file_part_pos;
921
922   editable = GTK_EDITABLE (chooser_entry);
923
924   text_up_to_cursor = gtk_editable_get_chars (editable, 0, gtk_editable_get_position (editable));
925   
926   if (!chooser_entry->file_system ||
927       !chooser_entry->base_folder ||
928       !gtk_file_system_parse (chooser_entry->file_system,
929                               chooser_entry->base_folder, text_up_to_cursor,
930                               &folder_path, &file_part, NULL)) /* NULL-GError */
931     {
932       folder_path = gtk_file_path_copy (chooser_entry->base_folder);
933       file_part = g_strdup ("");
934       file_part_pos = -1;
935     }
936   else
937     {
938       file_part_len = strlen (file_part);
939       total_len = strlen (text_up_to_cursor);
940       if (total_len > file_part_len)
941         file_part_pos = g_utf8_strlen (text_up_to_cursor, total_len - file_part_len);
942       else
943         file_part_pos = 0;
944     }
945
946   printf ("Parsed text \"%s\", file_part=\"%s\", file_part_pos=%d, folder_path=\"%s\"\n",
947           text_up_to_cursor,
948           file_part,
949           file_part_pos,
950           folder_path ? (char *) folder_path : "(NULL)");
951
952   g_free (text_up_to_cursor);
953
954   g_free (chooser_entry->file_part);
955
956   chooser_entry->file_part = file_part;
957   chooser_entry->file_part_pos = file_part_pos;
958
959   reload_current_folder (chooser_entry, folder_path, file_part_pos == -1);
960   gtk_file_path_free (folder_path);
961 }
962
963 static void
964 autocomplete (GtkFileChooserEntry *chooser_entry)
965 {
966   g_assert (chooser_entry->current_folder != NULL);
967   g_assert (gtk_file_folder_is_finished_loading (chooser_entry->current_folder));
968   g_assert (gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length);
969
970   printf ("Doing autocompletion since our folder is finished loading\n");
971
972   append_common_prefix (chooser_entry, TRUE);
973 }
974
975 static void
976 start_autocompletion (GtkFileChooserEntry *chooser_entry)
977 {
978   printf ("Starting autocompletion\n");
979
980   refresh_current_folder_and_file_part (chooser_entry);
981
982   if (!chooser_entry->current_folder)
983     {
984       /* We don't beep or anything, since this is autocompletion - the user
985        * didn't request any action explicitly.
986        */
987       printf ("No current_folder; not doing autocompletion after all\n");
988       return;
989     }
990
991   if (gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
992     {
993       printf ("File folder is finished loading; doing autocompletion immediately\n");
994       autocomplete (chooser_entry);
995     }
996   else
997     {
998       printf ("File folder is not yet loaded; will do autocompletion later\n");
999       chooser_entry->load_complete_action = LOAD_COMPLETE_AUTOCOMPLETE;
1000     }
1001 }
1002
1003 static gboolean
1004 start_autocompletion_idle_handler (gpointer data)
1005 {
1006   GtkFileChooserEntry *chooser_entry;
1007
1008   chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
1009
1010   if (gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length)
1011     start_autocompletion (chooser_entry);
1012
1013   chooser_entry->start_autocompletion_idle_id = 0;
1014
1015   return FALSE;
1016 }
1017
1018 static void
1019 install_start_autocompletion_idle (GtkFileChooserEntry *chooser_entry)
1020 {
1021   /* We set up an idle handler because we must test the cursor position.  However,
1022    * the cursor is not updated in GtkEntry::changed, so we wait for the idle loop.
1023    */
1024
1025   if (chooser_entry->start_autocompletion_idle_id != 0)
1026     return;
1027
1028   chooser_entry->start_autocompletion_idle_id = g_idle_add (start_autocompletion_idle_handler, chooser_entry);
1029 }
1030
1031 static void
1032 gtk_file_chooser_entry_changed (GtkEditable *editable)
1033 {
1034   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
1035
1036   if (chooser_entry->in_change)
1037     return;
1038
1039   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1040
1041   if ((chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN
1042        || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER))
1043     install_start_autocompletion_idle (chooser_entry);
1044
1045   /* FIXME: see when the cursor changes (see GtkEntry's cursor_position property, or GtkEditable::set_position and GtkEntry::move_cursor).
1046    * When the cursor changes, cancel the load_complete_action.
1047    * When the entry is activated, cancel the load_complete_action.
1048    * In general, cancel the load_complete_action when the entry loses the focus.
1049    */
1050 }
1051
1052 static void
1053 clear_completion_callback (GtkFileChooserEntry *chooser_entry,
1054                            GParamSpec          *pspec)
1055 {
1056   if (chooser_entry->has_completion)
1057     {
1058       chooser_entry->has_completion = FALSE;
1059       gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
1060     }
1061 }
1062
1063 #ifdef G_OS_WIN32
1064 static gint
1065 insert_text_callback (GtkFileChooserEntry *chooser_entry,
1066                       const gchar         *new_text,
1067                       gint                 new_text_length,
1068                       gint                *position,
1069                       gpointer             user_data)
1070 {
1071   const gchar *colon = memchr (new_text, ':', new_text_length);
1072   gint i;
1073
1074   /* Disallow these characters altogether */
1075   for (i = 0; i < new_text_length; i++)
1076     {
1077       if (new_text[i] == '<' ||
1078           new_text[i] == '>' ||
1079           new_text[i] == '"' ||
1080           new_text[i] == '|' ||
1081           new_text[i] == '*' ||
1082           new_text[i] == '?')
1083         break;
1084     }
1085
1086   if (i < new_text_length ||
1087       /* Disallow entering text that would cause a colon to be anywhere except
1088        * after a drive letter.
1089        */
1090       (colon != NULL &&
1091        *position + (colon - new_text) != 1) ||
1092       (new_text_length > 0 &&
1093        *position <= 1 &&
1094        GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1095        gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':'))
1096     {
1097       gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
1098       g_signal_stop_emission_by_name (chooser_entry, "insert_text");
1099       return FALSE;
1100     }
1101
1102   return TRUE;
1103 }
1104
1105 static void
1106 delete_text_callback (GtkFileChooserEntry *chooser_entry,
1107                       gint                 start_pos,
1108                       gint                 end_pos,
1109                       gpointer             user_data)
1110 {
1111   /* If deleting a drive letter, delete the colon, too */
1112   if (start_pos == 0 && end_pos == 1 &&
1113       GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1114       gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':')
1115     {
1116       g_signal_handlers_block_by_func (chooser_entry,
1117                                        G_CALLBACK (delete_text_callback),
1118                                        user_data);
1119       gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), 0, 1);
1120       g_signal_handlers_unblock_by_func (chooser_entry,
1121                                          G_CALLBACK (delete_text_callback),
1122                                          user_data);
1123     }
1124 }
1125 #endif
1126
1127 /**
1128  * _gtk_file_chooser_entry_new:
1129  * @eat_tabs: If %FALSE, allow focus navigation with the tab key.
1130  *
1131  * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
1132  * is an internal implementation widget for the GTK+ file chooser
1133  * which is an entry with completion with respect to a
1134  * #GtkFileSystem object.
1135  *
1136  * Return value: the newly created #GtkFileChooserEntry
1137  **/
1138 GtkWidget *
1139 _gtk_file_chooser_entry_new (gboolean eat_tabs)
1140 {
1141   GtkFileChooserEntry *chooser_entry;
1142
1143   chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
1144   chooser_entry->eat_tabs = (eat_tabs != FALSE);
1145
1146   return GTK_WIDGET (chooser_entry);
1147 }
1148
1149 /**
1150  * _gtk_file_chooser_entry_set_file_system:
1151  * @chooser_entry: a #GtkFileChooser
1152  * @file_system: an object implementing #GtkFileSystem
1153  *
1154  * Sets the file system for @chooser_entry.
1155  **/
1156 void
1157 _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry,
1158                                          GtkFileSystem       *file_system)
1159 {
1160   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1161   g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system));
1162
1163   if (file_system != chooser_entry->file_system)
1164     {
1165       if (chooser_entry->file_system)
1166         g_object_unref (chooser_entry->file_system);
1167
1168       chooser_entry->file_system = g_object_ref (file_system);
1169     }
1170 }
1171
1172 /**
1173  * _gtk_file_chooser_entry_set_base_folder:
1174  * @chooser_entry: a #GtkFileChooserEntry
1175  * @path: path of a folder in the chooser entries current file system.
1176  *
1177  * Sets the folder with respect to which completions occur.
1178  **/
1179 void
1180 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
1181                                          const GtkFilePath   *path)
1182 {
1183   if (chooser_entry->base_folder)
1184     gtk_file_path_free (chooser_entry->base_folder);
1185
1186   chooser_entry->base_folder = gtk_file_path_copy (path);
1187
1188   gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
1189   _gtk_file_chooser_entry_select_filename (chooser_entry);
1190 }
1191
1192 /**
1193  * _gtk_file_chooser_entry_get_current_folder:
1194  * @chooser_entry: a #GtkFileChooserEntry
1195  *
1196  * Gets the current folder for the #GtkFileChooserEntry. If the
1197  * user has only entered a filename, this will be the base folder
1198  * (see _gtk_file_chooser_entry_set_base_folder()), but if the
1199  * user has entered a relative or absolute path, then it will
1200  * be different. If the user has entered a relative or absolute
1201  * path that doesn't point to a folder in the file system, it will
1202  * be %NULL.
1203  *
1204  * Return value: the path of current folder - this value is owned by the
1205  *  chooser entry and must not be modified or freed.
1206  **/
1207 const GtkFilePath *
1208 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
1209 {
1210   if (chooser_entry->has_completion)
1211     {
1212       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
1213                                  GTK_ENTRY (chooser_entry)->text_length);
1214     }
1215   return chooser_entry->current_folder_path;
1216 }
1217
1218 /**
1219  * _gtk_file_chooser_entry_get_file_part:
1220  * @chooser_entry: a #GtkFileChooserEntry
1221  *
1222  * Gets the non-folder portion of whatever the user has entered
1223  * into the file selector. What is returned is a UTF-8 string,
1224  * and if a filename path is needed, gtk_file_system_make_path()
1225  * must be used
1226   *
1227  * Return value: the entered filename - this value is owned by the
1228  *  chooser entry and must not be modified or freed.
1229  **/
1230 const gchar *
1231 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
1232 {
1233   if (chooser_entry->has_completion)
1234     {
1235       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
1236                                  GTK_ENTRY (chooser_entry)->text_length);
1237     }
1238   return chooser_entry->file_part;
1239 }
1240
1241 /**
1242  * _gtk_file_chooser_entry_set_file_part:
1243  * @chooser_entry: a #GtkFileChooserEntry
1244  * @file_part: text to display in the entry, in UTF-8
1245  *
1246  * Sets the current text shown in the file chooser entry.
1247  **/
1248 void
1249 _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry,
1250                                        const gchar         *file_part)
1251 {
1252   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1253
1254   gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part);
1255 }
1256
1257
1258 /**
1259  * _gtk_file_chooser_entry_set_action:
1260  * @chooser_entry: a #GtkFileChooserEntry
1261  * @action: the action which is performed by the file selector using this entry
1262  *
1263  * Sets action which is performed by the file selector using this entry. 
1264  * The #GtkFileChooserEntry will use different completion strategies for 
1265  * different actions.
1266  **/
1267 void
1268 _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry,
1269                                     GtkFileChooserAction action)
1270 {
1271   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1272   
1273   if (chooser_entry->action != action)
1274     {
1275       GtkEntryCompletion *comp;
1276
1277       chooser_entry->action = action;
1278
1279       comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
1280
1281       switch (action)
1282         {
1283         case GTK_FILE_CHOOSER_ACTION_OPEN:
1284         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1285           gtk_entry_completion_set_popup_single_match (comp, FALSE);
1286           break;
1287         case GTK_FILE_CHOOSER_ACTION_SAVE:
1288         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1289           gtk_entry_completion_set_popup_single_match (comp, TRUE);
1290           break;
1291         }
1292     }
1293 }
1294
1295
1296 /**
1297  * _gtk_file_chooser_entry_get_action:
1298  * @chooser_entry: a #GtkFileChooserEntry
1299  *
1300  * Gets the action for this entry. 
1301  *
1302  * Returns: the action
1303  **/
1304 GtkFileChooserAction
1305 _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry)
1306 {
1307   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry),
1308                         GTK_FILE_CHOOSER_ACTION_OPEN);
1309   
1310   return chooser_entry->action;
1311 }
1312
1313 gboolean
1314 _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry,
1315                                        const GtkFilePath   *path)
1316 {
1317   gboolean retval = FALSE;
1318
1319   if (chooser_entry->current_folder)
1320     {
1321       GtkFileInfo *file_info;
1322
1323       file_info = gtk_file_folder_get_info (chooser_entry->current_folder,
1324                                             path, NULL);
1325       if (file_info)
1326         {
1327           retval = gtk_file_info_get_is_folder (file_info);
1328           gtk_file_info_free (file_info);
1329         }
1330     }
1331
1332   return retval;
1333 }
1334
1335
1336 /*
1337  * _gtk_file_chooser_entry_select_filename:
1338  * @chooser_entry: a #GtkFileChooserEntry
1339  *
1340  * Selects the filename (without the extension) for user edition.
1341  */
1342 void
1343 _gtk_file_chooser_entry_select_filename (GtkFileChooserEntry *chooser_entry)
1344 {
1345   const gchar *str, *ext;
1346   glong len = -1;
1347
1348   if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE)
1349     {
1350       str = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
1351       ext = g_strrstr (str, ".");
1352
1353       if (ext)
1354        len = g_utf8_pointer_to_offset (str, ext);
1355     }
1356
1357   gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, (gint) len);
1358 }
1359