]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserentry.c
a8f799ba30446c6453d502a9b3762294b4bb585e
[~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 "gtkalignment.h"
25 #include "gtkcelllayout.h"
26 #include "gtkcellrenderertext.h"
27 #include "gtkentry.h"
28 #include "gtkfilechooserentry.h"
29 #include "gtklabel.h"
30 #include "gtkmain.h"
31 #include "gtkwindow.h"
32 #include "gtkintl.h"
33 #include "gtkalias.h"
34
35 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
36
37 #define GTK_FILE_CHOOSER_ENTRY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
38 #define GTK_IS_FILE_CHOOSER_ENTRY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY))
39 #define GTK_FILE_CHOOSER_ENTRY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
40
41 struct _GtkFileChooserEntryClass
42 {
43   GtkEntryClass parent_class;
44 };
45
46 /* Action to take when the current folder finishes loading (for explicit or automatic completion) */
47 typedef enum {
48   LOAD_COMPLETE_NOTHING,
49   LOAD_COMPLETE_AUTOCOMPLETE,
50   LOAD_COMPLETE_EXPLICIT_COMPLETION
51 } LoadCompleteAction;
52
53 struct _GtkFileChooserEntry
54 {
55   GtkEntry parent_instance;
56
57   GtkFileChooserAction action;
58
59   GtkFileSystem *file_system;
60   GtkFilePath *base_folder;
61   gchar *file_part;
62   gint file_part_pos;
63
64   /* Folder being loaded or already loaded */
65   GtkFilePath *current_folder_path;
66   GtkFileFolder *current_folder;
67   GtkFileSystemHandle *load_folder_handle;
68
69   LoadCompleteAction load_complete_action;
70
71   GtkListStore *completion_store;
72
73   guint start_autocompletion_idle_id;
74
75   GtkWidget *completion_feedback_window;
76   GtkWidget *completion_feedback_label;
77   guint completion_feedback_timeout_id;
78
79   guint has_completion : 1;
80   guint in_change      : 1;
81   guint eat_tabs       : 1;
82 };
83
84 enum
85 {
86   DISPLAY_NAME_COLUMN,
87   PATH_COLUMN,
88   N_COLUMNS
89 };
90
91 #define COMPLETION_FEEDBACK_TIMEOUT_MS 2000
92
93 static void     gtk_file_chooser_entry_iface_init     (GtkEditableClass *iface);
94
95 static void     gtk_file_chooser_entry_finalize       (GObject          *object);
96 static void     gtk_file_chooser_entry_dispose        (GObject          *object);
97 static void     gtk_file_chooser_entry_grab_focus     (GtkWidget        *widget);
98 static void     gtk_file_chooser_entry_unmap          (GtkWidget        *widget);
99 static gboolean gtk_file_chooser_entry_focus          (GtkWidget        *widget,
100                                                        GtkDirectionType  direction);
101 static gboolean gtk_file_chooser_entry_focus_out_event (GtkWidget       *widget,
102                                                         GdkEventFocus   *event);
103 static void     gtk_file_chooser_entry_activate       (GtkEntry         *entry);
104 static void     gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
105                                                        const gchar *new_text,
106                                                        gint         new_text_length,
107                                                        gint        *position);
108 static void     gtk_file_chooser_entry_do_delete_text (GtkEditable *editable,
109                                                        gint         start_pos,
110                                                        gint         end_pos);
111 static void     gtk_file_chooser_entry_set_position (GtkEditable *editable,
112                                                      gint         position);
113 static void     gtk_file_chooser_entry_set_selection_bounds (GtkEditable *editable,
114                                                              gint         start_pos,
115                                                              gint         end_pos);
116
117 #ifdef G_OS_WIN32
118 static gint     insert_text_callback      (GtkFileChooserEntry *widget,
119                                            const gchar         *new_text,
120                                            gint                 new_text_length,
121                                            gint                *position,
122                                            gpointer             user_data);
123 static void     delete_text_callback      (GtkFileChooserEntry *widget,
124                                            gint                 start_pos,
125                                            gint                 end_pos,
126                                            gpointer             user_data);
127 #endif
128
129 static gboolean match_selected_callback   (GtkEntryCompletion  *completion,
130                                            GtkTreeModel        *model,
131                                            GtkTreeIter         *iter,
132                                            GtkFileChooserEntry *chooser_entry);
133 static gboolean completion_match_func     (GtkEntryCompletion  *comp,
134                                            const char          *key,
135                                            GtkTreeIter         *iter,
136                                            gpointer             data);
137 static char    *maybe_append_separator_to_path (GtkFileChooserEntry *chooser_entry,
138                                                 GtkFilePath         *path,
139                                                 gchar               *display_name);
140
141 typedef enum {
142   REFRESH_UP_TO_CURSOR_POSITION,
143   REFRESH_WHOLE_TEXT
144 } RefreshMode;
145
146 static void refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry,
147                                                   RefreshMode refresh_mode);
148 static void finished_loading_cb (GtkFileFolder *folder,
149                                  gpointer       data);
150 static void autocomplete (GtkFileChooserEntry *chooser_entry);
151 static void install_start_autocompletion_idle (GtkFileChooserEntry *chooser_entry);
152 static void remove_completion_feedback (GtkFileChooserEntry *chooser_entry);
153 static void pop_up_completion_feedback (GtkFileChooserEntry *chooser_entry,
154                                         const gchar         *feedback);
155
156 static GtkEditableClass *parent_editable_iface;
157
158 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserEntry, _gtk_file_chooser_entry, GTK_TYPE_ENTRY,
159                          G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
160                                                 gtk_file_chooser_entry_iface_init))
161
162 static void
163 _gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class)
164 {
165   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
166   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
167   GtkEntryClass *entry_class = GTK_ENTRY_CLASS (class);
168
169   gobject_class->finalize = gtk_file_chooser_entry_finalize;
170   gobject_class->dispose = gtk_file_chooser_entry_dispose;
171
172   widget_class->grab_focus = gtk_file_chooser_entry_grab_focus;
173   widget_class->unmap = gtk_file_chooser_entry_unmap;
174   widget_class->focus = gtk_file_chooser_entry_focus;
175   widget_class->focus_out_event = gtk_file_chooser_entry_focus_out_event;
176
177   entry_class->activate = gtk_file_chooser_entry_activate;
178 }
179
180 static void
181 gtk_file_chooser_entry_iface_init (GtkEditableClass *iface)
182 {
183   parent_editable_iface = g_type_interface_peek_parent (iface);
184
185   iface->do_insert_text = gtk_file_chooser_entry_do_insert_text;
186   iface->do_delete_text = gtk_file_chooser_entry_do_delete_text;
187   iface->set_position = gtk_file_chooser_entry_set_position;
188   iface->set_selection_bounds = gtk_file_chooser_entry_set_selection_bounds;
189 }
190
191 static void
192 _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
193 {
194   GtkEntryCompletion *comp;
195   GtkCellRenderer *cell;
196
197   g_object_set (chooser_entry, "truncate-multiline", TRUE, NULL);
198
199   comp = gtk_entry_completion_new ();
200   gtk_entry_completion_set_popup_single_match (comp, FALSE);
201
202   gtk_entry_completion_set_match_func (comp,
203                                        completion_match_func,
204                                        chooser_entry,
205                                        NULL);
206
207   cell = gtk_cell_renderer_text_new ();
208   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (comp),
209                               cell, TRUE);
210   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (comp),
211                                  cell,
212                                  "text", 0);
213
214   g_signal_connect (comp, "match_selected",
215                     G_CALLBACK (match_selected_callback), chooser_entry);
216
217   gtk_entry_set_completion (GTK_ENTRY (chooser_entry), comp);
218   g_object_unref (comp);
219
220 #ifdef G_OS_WIN32
221   g_signal_connect (chooser_entry, "insert_text",
222                     G_CALLBACK (insert_text_callback), NULL);
223   g_signal_connect (chooser_entry, "delete_text",
224                     G_CALLBACK (delete_text_callback), NULL);
225 #endif
226 }
227
228 static void
229 gtk_file_chooser_entry_finalize (GObject *object)
230 {
231   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
232
233   gtk_file_path_free (chooser_entry->base_folder);
234   gtk_file_path_free (chooser_entry->current_folder_path);
235   g_free (chooser_entry->file_part);
236
237   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->finalize (object);
238 }
239
240 static void
241 gtk_file_chooser_entry_dispose (GObject *object)
242 {
243   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
244
245   remove_completion_feedback (chooser_entry);
246
247   if (chooser_entry->start_autocompletion_idle_id != 0)
248     {
249       g_source_remove (chooser_entry->start_autocompletion_idle_id);
250       chooser_entry->start_autocompletion_idle_id = 0;
251     }
252
253   if (chooser_entry->completion_store)
254     {
255       g_object_unref (chooser_entry->completion_store);
256       chooser_entry->completion_store = NULL;
257     }
258
259   if (chooser_entry->load_folder_handle)
260     {
261       gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
262       chooser_entry->load_folder_handle = NULL;
263     }
264
265   if (chooser_entry->current_folder)
266     {
267       g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
268                                             G_CALLBACK (finished_loading_cb), chooser_entry);
269       g_object_unref (chooser_entry->current_folder);
270       chooser_entry->current_folder = NULL;
271     }
272
273   if (chooser_entry->file_system)
274     {
275       g_object_unref (chooser_entry->file_system);
276       chooser_entry->file_system = NULL;
277     }
278
279   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object);
280 }
281
282 /* Match functions for the GtkEntryCompletion */
283 static gboolean
284 match_selected_callback (GtkEntryCompletion  *completion,
285                          GtkTreeModel        *model,
286                          GtkTreeIter         *iter,
287                          GtkFileChooserEntry *chooser_entry)
288 {
289   char *display_name;
290   GtkFilePath *path;
291   gint pos;
292   
293   gtk_tree_model_get (model, iter,
294                       DISPLAY_NAME_COLUMN, &display_name,
295                       PATH_COLUMN, &path,
296                       -1);
297
298   if (!display_name || !path)
299     {
300       /* these shouldn't complain if passed NULL */
301       gtk_file_path_free (path);
302       g_free (display_name);
303       return FALSE;
304     }
305
306   display_name = maybe_append_separator_to_path (chooser_entry, path, display_name);
307
308   pos = chooser_entry->file_part_pos;
309
310   /* We don't set in_change here as we want to update the current_folder
311    * variable */
312   gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
313                             pos, -1);
314   gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
315                             display_name, -1, 
316                             &pos);
317   gtk_editable_set_position (GTK_EDITABLE (chooser_entry), -1);
318
319   gtk_file_path_free (path);
320   g_free (display_name);
321
322   return TRUE;
323 }
324
325 /* Match function for the GtkEntryCompletion */
326 static gboolean
327 completion_match_func (GtkEntryCompletion *comp,
328                        const char         *key_unused,
329                        GtkTreeIter        *iter,
330                        gpointer            data)
331 {
332   GtkFileChooserEntry *chooser_entry;
333   char *name;
334   gboolean result;
335   char *norm_file_part;
336   char *norm_name;
337
338   chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
339
340   /* We ignore the key because it is the contents of the entry.  Instead, we
341    * just use our precomputed file_part.
342    */
343   if (!chooser_entry->file_part)
344     {
345       return FALSE;
346     }
347
348   gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store), iter, DISPLAY_NAME_COLUMN, &name, -1);
349   if (!name)
350     {
351       return FALSE; /* Uninitialized row, ugh */
352     }
353
354   /* If we have an empty file_part, then we're at the root of a directory.  In
355    * that case, we want to match all non-dot files.  We might want to match
356    * dot_files too if show_hidden is TRUE on the fileselector in the future.
357    */
358   /* Additionally, support for gnome .hidden files would be sweet, too */
359   if (chooser_entry->file_part[0] == '\000')
360     {
361       if (name[0] == '.')
362         result = FALSE;
363       else
364         result = TRUE;
365       g_free (name);
366
367       return result;
368     }
369
370
371   norm_file_part = g_utf8_normalize (chooser_entry->file_part, -1, G_NORMALIZE_ALL);
372   norm_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
373
374 #ifdef G_PLATFORM_WIN32
375   {
376     gchar *temp;
377
378     temp = norm_file_part;
379     norm_file_part = g_utf8_casefold (norm_file_part, -1);
380     g_free (temp);
381
382     temp = norm_name;
383     norm_name = g_utf8_casefold (norm_name, -1);
384     g_free (temp);
385   }
386 #endif
387
388   result = (strncmp (norm_file_part, norm_name, strlen (norm_file_part)) == 0);
389
390   g_free (norm_file_part);
391   g_free (norm_name);
392   g_free (name);
393   
394   return result;
395 }
396
397 static void
398 clear_completions (GtkFileChooserEntry *chooser_entry)
399 {
400   chooser_entry->has_completion = FALSE;
401   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
402
403   remove_completion_feedback (chooser_entry);
404 }
405
406 static void
407 beep (GtkFileChooserEntry *chooser_entry)
408 {
409   gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (chooser_entry)));
410 }
411
412 /* This function will append a directory separator to paths to
413  * display_name iff the path associated with it is a directory.
414  * maybe_append_separator_to_path will g_free the display_name and
415  * return a new one if needed.  Otherwise, it will return the old one.
416  * You should be safe calling
417  *
418  * display_name = maybe_append_separator_to_path (entry, path, display_name);
419  * ...
420  * g_free (display_name);
421  */
422 static char *
423 maybe_append_separator_to_path (GtkFileChooserEntry *chooser_entry,
424                                 GtkFilePath         *path,
425                                 gchar               *display_name)
426 {
427   if (!g_str_has_suffix (display_name, G_DIR_SEPARATOR_S) && path)
428     {
429       GtkFileInfo *info;
430             
431       info = gtk_file_folder_get_info (chooser_entry->current_folder,
432                                        path, NULL); /* NULL-GError */
433
434       if (info)
435         {
436           if (gtk_file_info_get_is_folder (info))
437             {
438               gchar *tmp = display_name;
439               display_name = g_strconcat (tmp, G_DIR_SEPARATOR_S, NULL);
440               g_free (tmp);
441             }
442           
443           gtk_file_info_free (info);
444         }
445     }
446
447   return display_name;
448 }
449
450 /* Determines if the completion model has entries with a common prefix relative
451  * to the current contents of the entry.  Also, if there's one and only one such
452  * path, stores it in unique_path_ret.
453  */
454 static gboolean
455 find_common_prefix (GtkFileChooserEntry *chooser_entry,
456                     gchar               **common_prefix_ret,
457                     GtkFilePath         **unique_path_ret,
458                     gboolean             *is_complete_not_unique_ret,
459                     gboolean             *prefix_expands_the_file_part_ret,
460                     GError              **error)
461 {
462   GtkEditable *editable;
463   GtkTreeIter iter;
464   gboolean parsed;
465   gboolean valid;
466   char *text_up_to_cursor;
467   GtkFilePath *parsed_folder_path;
468   char *parsed_file_part;
469
470   *common_prefix_ret = NULL;
471   *unique_path_ret = NULL;
472   *is_complete_not_unique_ret = FALSE;
473   *prefix_expands_the_file_part_ret = FALSE;
474
475   editable = GTK_EDITABLE (chooser_entry);
476
477   text_up_to_cursor = gtk_editable_get_chars (editable, 0, gtk_editable_get_position (editable));
478
479   parsed = gtk_file_system_parse (chooser_entry->file_system,
480                                   chooser_entry->base_folder,
481                                   text_up_to_cursor,
482                                   &parsed_folder_path,
483                                   &parsed_file_part,
484                                   error);
485
486   g_free (text_up_to_cursor);
487
488   if (!parsed)
489     return FALSE;
490
491   g_assert (parsed_folder_path != NULL
492             && chooser_entry->current_folder_path != NULL
493             && gtk_file_path_compare (parsed_folder_path, chooser_entry->current_folder_path) == 0);
494
495   gtk_file_path_free (parsed_folder_path);
496
497   /* First pass: find the common prefix */
498
499   valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
500
501   while (valid)
502     {
503       gchar *display_name;
504       GtkFilePath *path;
505
506       gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store),
507                           &iter,
508                           DISPLAY_NAME_COLUMN, &display_name,
509                           PATH_COLUMN, &path,
510                           -1);
511
512       if (g_str_has_prefix (display_name, parsed_file_part))
513         {
514           if (!*common_prefix_ret)
515             {
516               *common_prefix_ret = g_strdup (display_name);
517               *unique_path_ret = gtk_file_path_copy (path);
518             }
519           else
520             {
521               gchar *p = *common_prefix_ret;
522               const gchar *q = display_name;
523
524               while (*p && *p == *q)
525                 {
526                   p++;
527                   q++;
528                 }
529
530               *p = '\0';
531
532               gtk_file_path_free (*unique_path_ret);
533               *unique_path_ret = NULL;
534             }
535         }
536
537       g_free (display_name);
538       gtk_file_path_free (path);
539       valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
540     }
541
542   /* Second pass: see if the prefix we found is a complete match */
543
544   if (*common_prefix_ret != NULL)
545     {
546       valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
547
548       while (valid)
549         {
550           gchar *display_name;
551           int len;
552
553           gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store),
554                               &iter,
555                               DISPLAY_NAME_COLUMN, &display_name,
556                               -1);
557           len = strlen (display_name);
558           g_assert (len > 0);
559
560           if (G_IS_DIR_SEPARATOR (display_name[len - 1]))
561             len--;
562
563           if (strncmp (*common_prefix_ret, display_name, len) == 0)
564             *is_complete_not_unique_ret = TRUE;
565
566           g_free (display_name);
567           valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
568         }
569
570       /* Finally:  Did we generate a new completion, or was the user's input already completed as far as it could go? */
571
572       *prefix_expands_the_file_part_ret = g_utf8_strlen (*common_prefix_ret, -1) > g_utf8_strlen (parsed_file_part, -1);
573     }
574
575   g_free (parsed_file_part);
576
577   return TRUE;
578 }
579
580 typedef enum {
581   INVALID_INPUT,                /* what the user typed is bogus */
582   NO_MATCH,                     /* no matches based on what the user typed */
583   NOTHING_INSERTED_COMPLETE,    /* what the user typed is already completed as far as it will go */
584   COMPLETED,                    /* completion inserted (ambiguous suffix) */
585   COMPLETED_UNIQUE,             /* completion inserted, and it is a complete name and a unique match */
586   COMPLETE_BUT_NOT_UNIQUE       /* completion inserted, it is a complete name but not unique */
587 } CommonPrefixResult;
588
589 /* Finds a common prefix based on the contents of the entry and mandatorily appends it */
590 static CommonPrefixResult
591 append_common_prefix (GtkFileChooserEntry *chooser_entry,
592                       gboolean             highlight,
593                       gboolean             show_errors)
594 {
595   gchar *common_prefix;
596   GtkFilePath *unique_path;
597   gboolean is_complete_not_unique;
598   gboolean prefix_expands_the_file_part;
599   GError *error;
600   CommonPrefixResult result;
601   gboolean have_result;
602
603   clear_completions (chooser_entry);
604
605   if (chooser_entry->completion_store == NULL)
606     return NO_MATCH;
607
608   error = NULL;
609   if (!find_common_prefix (chooser_entry, &common_prefix, &unique_path, &is_complete_not_unique, &prefix_expands_the_file_part, &error))
610     {
611       if (show_errors)
612         {
613           beep (chooser_entry);
614           pop_up_completion_feedback (chooser_entry, _("Invalid path"));
615         }
616
617       g_error_free (error);
618
619       return INVALID_INPUT;
620     }
621
622   have_result = FALSE;
623
624   if (unique_path)
625     {
626       common_prefix = maybe_append_separator_to_path (chooser_entry,
627                                                       unique_path,
628                                                       common_prefix);
629       gtk_file_path_free (unique_path);
630
631       if (common_prefix)
632         result = COMPLETED_UNIQUE;
633       else
634         result = INVALID_INPUT;
635
636       have_result = TRUE;
637     }
638   else
639     {
640       if (is_complete_not_unique)
641         {
642           result = COMPLETE_BUT_NOT_UNIQUE;
643           have_result = TRUE;
644         }
645     }
646
647   if (common_prefix)
648     {
649       gint cursor_pos;
650       gint common_prefix_len;
651       gint pos;
652
653       cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry));
654       common_prefix_len = g_utf8_strlen (common_prefix, -1);
655
656       pos = chooser_entry->file_part_pos;
657
658       if (prefix_expands_the_file_part)
659         {
660           chooser_entry->in_change = TRUE;
661           gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
662                                     pos, cursor_pos);
663           gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
664                                     common_prefix, -1, 
665                                     &pos);
666           chooser_entry->in_change = FALSE;
667
668           if (highlight)
669             {
670               gtk_editable_select_region (GTK_EDITABLE (chooser_entry),
671                                           cursor_pos,
672                                           pos); /* equivalent to cursor_pos + common_prefix_len); */
673               chooser_entry->has_completion = TRUE;
674             }
675           else
676             gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos);
677         }
678       else
679         {
680           result = NOTHING_INSERTED_COMPLETE;
681           have_result = TRUE;
682         }
683
684       g_free (common_prefix);
685
686       if (have_result)
687         return result;
688       else
689         return COMPLETED;
690     }
691   else
692     {
693       if (have_result)
694         return result;
695       else
696         return NO_MATCH;
697     }
698 }
699
700 static void
701 gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
702                                        const gchar *new_text,
703                                        gint         new_text_length,
704                                        gint        *position)
705 {
706   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
707   gint old_text_len;
708   gint insert_pos;
709
710   old_text_len = GTK_ENTRY (chooser_entry)->text_length;
711   insert_pos = *position;
712
713   parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position);
714
715   if (chooser_entry->in_change)
716     return;
717
718   remove_completion_feedback (chooser_entry);
719
720   if ((chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN
721        || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
722       && insert_pos == old_text_len)
723     install_start_autocompletion_idle (chooser_entry);
724 }
725
726 static void
727 clear_completions_if_not_in_change (GtkFileChooserEntry *chooser_entry)
728 {
729   if (chooser_entry->in_change)
730     return;
731
732   clear_completions (chooser_entry);
733 }
734
735 static void
736 gtk_file_chooser_entry_do_delete_text (GtkEditable *editable,
737                                        gint         start_pos,
738                                        gint         end_pos)
739 {
740   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
741
742   parent_editable_iface->do_delete_text (editable, start_pos, end_pos);
743
744   clear_completions_if_not_in_change (chooser_entry);
745 }
746
747 static void
748 gtk_file_chooser_entry_set_position (GtkEditable *editable,
749                                      gint         position)
750 {
751   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
752
753   parent_editable_iface->set_position (editable, position);
754
755   clear_completions_if_not_in_change (chooser_entry);
756 }
757
758 static void
759 gtk_file_chooser_entry_set_selection_bounds (GtkEditable *editable,
760                                              gint         start_pos,
761                                              gint         end_pos)
762 {
763   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
764
765   parent_editable_iface->set_selection_bounds (editable, start_pos, end_pos);
766
767   clear_completions_if_not_in_change (chooser_entry);
768 }
769
770 static void
771 gtk_file_chooser_entry_grab_focus (GtkWidget *widget)
772 {
773   GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget);
774   _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget));
775 }
776
777 static void
778 gtk_file_chooser_entry_unmap (GtkWidget *widget)
779 {
780   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
781
782   remove_completion_feedback (chooser_entry);
783
784   GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->unmap (widget);
785 }
786
787 static gboolean
788 completion_feedback_window_expose_event_cb (GtkWidget      *widget,
789                                             GdkEventExpose *event,
790                                             gpointer        data)
791 {
792   /* Stolen from gtk_tooltip_paint_window() */
793
794   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
795
796   gtk_paint_flat_box (chooser_entry->completion_feedback_window->style,
797                       chooser_entry->completion_feedback_window->window,
798                       GTK_STATE_NORMAL,
799                       GTK_SHADOW_OUT,
800                       NULL,
801                       chooser_entry->completion_feedback_window,
802                       "tooltip",
803                       0, 0,
804                       chooser_entry->completion_feedback_window->allocation.width,
805                       chooser_entry->completion_feedback_window->allocation.height);
806
807   return FALSE;
808 }
809
810 static void
811 create_completion_feedback_window (GtkFileChooserEntry *chooser_entry)
812 {
813   /* Stolen from gtk_tooltip_init() */
814
815   GtkWidget *alignment;
816
817   chooser_entry->completion_feedback_window = gtk_window_new (GTK_WINDOW_POPUP);
818   gtk_window_set_type_hint (GTK_WINDOW (chooser_entry->completion_feedback_window),
819                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
820   gtk_widget_set_app_paintable (chooser_entry->completion_feedback_window, TRUE);
821   gtk_window_set_resizable (GTK_WINDOW (chooser_entry->completion_feedback_window), FALSE);
822   gtk_widget_set_name (chooser_entry->completion_feedback_window, "gtk-tooltip");
823
824   alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
825   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
826                              chooser_entry->completion_feedback_window->style->ythickness,
827                              chooser_entry->completion_feedback_window->style->ythickness,
828                              chooser_entry->completion_feedback_window->style->xthickness,
829                              chooser_entry->completion_feedback_window->style->xthickness);
830   gtk_container_add (GTK_CONTAINER (chooser_entry->completion_feedback_window), alignment);
831   gtk_widget_show (alignment);
832
833   g_signal_connect (chooser_entry->completion_feedback_window, "expose_event",
834                     G_CALLBACK (completion_feedback_window_expose_event_cb), chooser_entry);
835
836   chooser_entry->completion_feedback_label = gtk_label_new (NULL);
837   gtk_container_add (GTK_CONTAINER (alignment), chooser_entry->completion_feedback_label);
838   gtk_widget_show (chooser_entry->completion_feedback_label);
839 }
840
841 static gboolean
842 completion_feedback_timeout_cb (gpointer data)
843 {
844   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
845
846   chooser_entry->completion_feedback_timeout_id = 0;
847
848   remove_completion_feedback (chooser_entry);
849   return FALSE;
850 }
851
852 static void
853 install_completion_feedback_timer (GtkFileChooserEntry *chooser_entry)
854 {
855   g_assert (chooser_entry->completion_feedback_timeout_id == 0);
856
857   chooser_entry->completion_feedback_timeout_id = gdk_threads_add_timeout (COMPLETION_FEEDBACK_TIMEOUT_MS,
858                                                                            completion_feedback_timeout_cb,
859                                                                            chooser_entry);
860 }
861
862 /* Gets the x position of the text cursor in the entry, in widget coordinates */
863 static void
864 get_entry_cursor_x (GtkFileChooserEntry *chooser_entry,
865                     gint                *x_ret)
866 {
867   /* FIXME: see the docs for gtk_entry_get_layout_offsets().  As an exercise for
868    * the reader, you have to implement support for the entry's scroll offset and
869    * RTL layouts and all the fancy Pango stuff.
870    */
871
872   PangoLayout *layout;
873   gint layout_x, layout_y;
874   gint layout_index;
875   PangoRectangle strong_pos;
876
877   layout = gtk_entry_get_layout (GTK_ENTRY (chooser_entry));
878
879   gtk_entry_get_layout_offsets (GTK_ENTRY (chooser_entry), &layout_x, &layout_y);
880
881   layout_index = gtk_entry_text_index_to_layout_index (GTK_ENTRY (chooser_entry),
882                                                        GTK_ENTRY (chooser_entry)->current_pos);
883
884   pango_layout_get_cursor_pos (layout, layout_index, &strong_pos, NULL);
885
886   *x_ret = layout_x + strong_pos.x / PANGO_SCALE;
887 }
888
889 static void
890 show_completion_feedback_window (GtkFileChooserEntry *chooser_entry)
891 {
892   /* More or less stolen from gtk_tooltip_position() */
893
894   GtkRequisition feedback_req;
895   gint entry_x, entry_y;
896   gint cursor_x;
897   GtkAllocation *entry_allocation;
898   int feedback_x, feedback_y;
899
900   gtk_widget_size_request (chooser_entry->completion_feedback_window, &feedback_req);
901
902   gdk_window_get_origin (GTK_WIDGET (chooser_entry)->window, &entry_x, &entry_y);
903   entry_allocation = &(GTK_WIDGET (chooser_entry)->allocation);
904
905   get_entry_cursor_x (chooser_entry, &cursor_x);
906
907   feedback_x = entry_x + cursor_x + 2; /* FIXME: fit to the screen if we bump on the screen's edge */
908   feedback_y = entry_y + (entry_allocation->height - feedback_req.height) / 2;
909
910   gtk_window_move (GTK_WINDOW (chooser_entry->completion_feedback_window), feedback_x, feedback_y);
911   gtk_widget_show (chooser_entry->completion_feedback_window);
912
913   install_completion_feedback_timer (chooser_entry);
914 }
915
916 static void
917 pop_up_completion_feedback (GtkFileChooserEntry *chooser_entry,
918                             const gchar         *feedback)
919 {
920   if (chooser_entry->completion_feedback_window == NULL)
921     create_completion_feedback_window (chooser_entry);
922
923   gtk_label_set_text (GTK_LABEL (chooser_entry->completion_feedback_label), feedback);
924
925   show_completion_feedback_window (chooser_entry);
926 }
927
928 static void
929 remove_completion_feedback (GtkFileChooserEntry *chooser_entry)
930 {
931   if (chooser_entry->completion_feedback_window)
932     gtk_widget_destroy (chooser_entry->completion_feedback_window);
933
934   chooser_entry->completion_feedback_window = NULL;
935   chooser_entry->completion_feedback_label = NULL;
936
937   if (chooser_entry->completion_feedback_timeout_id != 0)
938     {
939       g_source_remove (chooser_entry->completion_feedback_timeout_id);
940       chooser_entry->completion_feedback_timeout_id = 0;
941     }
942 }
943
944 static void
945 explicitly_complete (GtkFileChooserEntry *chooser_entry)
946 {
947   CommonPrefixResult result;
948
949   g_assert (chooser_entry->current_folder != NULL);
950   g_assert (gtk_file_folder_is_finished_loading (chooser_entry->current_folder));
951
952   /* FIXME: see what Emacs does in case there is no common prefix, or there is more than one match:
953    *
954    * - If there is a common prefix, insert it (done)
955    * - If there is no common prefix, pop up the suggestion window
956    * - If there are no matches at all, beep and bring up a tooltip (done)
957    * - If the suggestion window is already up, scroll it
958    */
959   result = append_common_prefix (chooser_entry, FALSE, TRUE);
960
961   switch (result)
962     {
963     case INVALID_INPUT:
964       /* We already beeped in append_common_prefix(); do nothing here */
965       break;
966
967     case NO_MATCH:
968       beep (chooser_entry);
969       pop_up_completion_feedback (chooser_entry, _("No match"));
970       break;
971
972     case NOTHING_INSERTED_COMPLETE:
973       /* FIXME: pop up the suggestion window or scroll it */
974       break;
975
976     case COMPLETED:
977       /* Nothing to do */
978       break;
979
980     case COMPLETED_UNIQUE:
981       pop_up_completion_feedback (chooser_entry, _("Sole completion"));
982       /* FIXME: if the user keeps hitting Tab after completing a unique match, present feedback with "Sole completion") */
983       break;
984
985     case COMPLETE_BUT_NOT_UNIQUE:
986       pop_up_completion_feedback (chooser_entry, _("Complete, but not unique"));
987       break;
988
989     default:
990       g_assert_not_reached ();
991     }
992 }
993
994 static void
995 start_explicit_completion (GtkFileChooserEntry *chooser_entry)
996 {
997   refresh_current_folder_and_file_part (chooser_entry, REFRESH_UP_TO_CURSOR_POSITION);
998
999   if (!chooser_entry->current_folder_path)
1000     {
1001       /* Here, no folder path means we couldn't parse what the user typed. */
1002
1003       beep (chooser_entry);
1004       pop_up_completion_feedback (chooser_entry, _("Invalid path"));
1005
1006       chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1007       return;
1008     }
1009
1010   if (chooser_entry->current_folder
1011       && gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
1012     {
1013       explicitly_complete (chooser_entry);
1014     }
1015   else
1016     {
1017       chooser_entry->load_complete_action = LOAD_COMPLETE_EXPLICIT_COMPLETION;
1018
1019       pop_up_completion_feedback (chooser_entry, _("Completing..."));
1020     }
1021 }
1022
1023 static gboolean
1024 gtk_file_chooser_entry_focus (GtkWidget        *widget,
1025                               GtkDirectionType  direction)
1026 {
1027   GtkFileChooserEntry *chooser_entry;
1028   GtkEditable *editable;
1029   GtkEntry *entry;
1030   GdkModifierType state;
1031   gboolean control_pressed;
1032
1033   chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
1034   editable = GTK_EDITABLE (widget);
1035   entry = GTK_ENTRY (widget);
1036
1037   if (!chooser_entry->eat_tabs)
1038     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
1039
1040   control_pressed = FALSE;
1041
1042   if (gtk_get_current_event_state (&state))
1043     {
1044       if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
1045         control_pressed = TRUE;
1046     }
1047
1048   /* This is a bit evil -- it makes Tab never leave the entry. It basically
1049    * makes it 'safe' for people to hit. */
1050   if ((direction == GTK_DIR_TAB_FORWARD) &&
1051       (GTK_WIDGET_HAS_FOCUS (widget)) &&
1052       (! control_pressed))
1053     {
1054       if (chooser_entry->has_completion)
1055         gtk_editable_set_position (editable, entry->text_length);
1056       else
1057         start_explicit_completion (chooser_entry);
1058
1059       return TRUE;
1060     }
1061   else
1062     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
1063 }
1064
1065 static gboolean
1066 gtk_file_chooser_entry_focus_out_event (GtkWidget     *widget,
1067                                         GdkEventFocus *event)
1068 {
1069   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
1070
1071   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1072  
1073   return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus_out_event (widget, event);
1074 }
1075
1076 static void
1077 gtk_file_chooser_entry_activate (GtkEntry *entry)
1078 {
1079   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry);
1080
1081   if (chooser_entry->has_completion)
1082     {
1083       gtk_editable_set_position (GTK_EDITABLE (entry),
1084                                  entry->text_length);
1085     }
1086
1087   refresh_current_folder_and_file_part (chooser_entry, REFRESH_WHOLE_TEXT);
1088
1089   GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry);
1090 }
1091
1092 static void
1093 discard_completion_store (GtkFileChooserEntry *chooser_entry)
1094 {
1095   if (!chooser_entry->completion_store)
1096     return;
1097
1098   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);
1099   g_object_unref (chooser_entry->completion_store);
1100   chooser_entry->completion_store = NULL;
1101 }
1102
1103 /* Fills the completion store from the contents of the current folder */
1104 static void
1105 populate_completion_store (GtkFileChooserEntry *chooser_entry)
1106 {
1107   GSList *paths;
1108   GSList *tmp_list;
1109
1110   discard_completion_store (chooser_entry);
1111
1112   if (!gtk_file_folder_list_children (chooser_entry->current_folder, &paths, NULL)) /* NULL-GError */
1113     return;
1114
1115   chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS,
1116                                                         G_TYPE_STRING,
1117                                                         GTK_TYPE_FILE_PATH);
1118
1119   for (tmp_list = paths; tmp_list; tmp_list = tmp_list->next)
1120     {
1121       GtkFileInfo *info;
1122       GtkFilePath *path;
1123
1124       path = tmp_list->data;
1125
1126       info = gtk_file_folder_get_info (chooser_entry->current_folder,
1127                                        path,
1128                                        NULL); /* NULL-GError */
1129       if (info)
1130         {
1131           gchar *display_name = g_strdup (gtk_file_info_get_display_name (info));
1132           GtkTreeIter iter;
1133
1134           display_name = maybe_append_separator_to_path (chooser_entry, path, display_name);
1135
1136           gtk_list_store_append (chooser_entry->completion_store, &iter);
1137           gtk_list_store_set (chooser_entry->completion_store, &iter,
1138                               DISPLAY_NAME_COLUMN, display_name,
1139                               PATH_COLUMN, path,
1140                               -1);
1141
1142           gtk_file_info_free (info);
1143           g_free (display_name);
1144         }
1145     }
1146
1147   gtk_file_paths_free (paths);
1148
1149   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store),
1150                                         DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING);
1151
1152   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)),
1153                                   GTK_TREE_MODEL (chooser_entry->completion_store));
1154 }
1155
1156 /* When we finish loading the current folder, this function should get called to
1157  * perform the deferred autocompletion or explicit completion.
1158  */
1159 static void
1160 perform_load_complete_action (GtkFileChooserEntry *chooser_entry)
1161 {
1162   switch (chooser_entry->load_complete_action)
1163     {
1164     case LOAD_COMPLETE_NOTHING:
1165       break;
1166
1167     case LOAD_COMPLETE_AUTOCOMPLETE:
1168       autocomplete (chooser_entry);
1169       break;
1170
1171     case LOAD_COMPLETE_EXPLICIT_COMPLETION:
1172       explicitly_complete (chooser_entry);
1173       break;
1174
1175     default:
1176       g_assert_not_reached ();
1177     }
1178
1179   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1180 }
1181
1182 static void
1183 finish_folder_load (GtkFileChooserEntry *chooser_entry)
1184 {
1185   populate_completion_store (chooser_entry);
1186   perform_load_complete_action (chooser_entry);
1187
1188   gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL);
1189 }
1190
1191 /* Callback when the current folder finishes loading */
1192 static void
1193 finished_loading_cb (GtkFileFolder *folder,
1194                      gpointer       data)
1195 {
1196   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
1197
1198   finish_folder_load (chooser_entry);
1199 }
1200
1201 /* Callback when the current folder's handle gets obtained (not necessarily loaded completely) */
1202 static void
1203 load_directory_get_folder_callback (GtkFileSystemHandle *handle,
1204                                     GtkFileFolder       *folder,
1205                                     const GError        *error,
1206                                     gpointer             data)
1207 {
1208   gboolean cancelled = handle->cancelled;
1209   GtkFileChooserEntry *chooser_entry = data;
1210
1211   if (handle != chooser_entry->load_folder_handle)
1212     goto out;
1213
1214   chooser_entry->load_folder_handle = NULL;
1215
1216   if (error)
1217     {
1218       LoadCompleteAction old_load_complete_action;
1219
1220       old_load_complete_action = chooser_entry->load_complete_action;
1221
1222       clear_completions (chooser_entry);
1223
1224       if (old_load_complete_action == LOAD_COMPLETE_EXPLICIT_COMPLETION)
1225         {
1226           /* Since this came from explicit user action (Tab completion), we'll present errors visually */
1227
1228           beep (chooser_entry);
1229           pop_up_completion_feedback (chooser_entry, error->message);
1230         }
1231
1232       gtk_file_path_free (chooser_entry->current_folder_path);
1233       chooser_entry->current_folder_path = NULL;
1234     }
1235
1236   if (cancelled || error)
1237     goto out;
1238
1239   g_assert (folder != NULL);
1240   chooser_entry->current_folder = folder;
1241
1242   discard_completion_store (chooser_entry);
1243
1244   if (gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
1245     finish_folder_load (chooser_entry);
1246   else
1247     g_signal_connect (chooser_entry->current_folder, "finished-loading",
1248                       G_CALLBACK (finished_loading_cb), chooser_entry);
1249
1250 out:
1251   g_object_unref (chooser_entry);
1252   g_object_unref (handle);
1253 }
1254
1255 static void
1256 start_loading_current_folder (GtkFileChooserEntry *chooser_entry)
1257 {
1258   if (chooser_entry->current_folder_path == NULL ||
1259       chooser_entry->file_system == NULL)
1260     return;
1261
1262   g_assert (chooser_entry->current_folder == NULL);
1263   g_assert (chooser_entry->load_folder_handle == NULL);
1264
1265   chooser_entry->load_folder_handle =
1266     gtk_file_system_get_folder (chooser_entry->file_system,
1267                                 chooser_entry->current_folder_path,
1268                                 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER,
1269                                 load_directory_get_folder_callback,
1270                                 g_object_ref (chooser_entry));
1271 }
1272
1273 static void
1274 reload_current_folder (GtkFileChooserEntry *chooser_entry,
1275                        GtkFilePath         *folder_path,
1276                        gboolean             force_reload)
1277 {
1278   gboolean reload = FALSE;
1279
1280   if (chooser_entry->current_folder_path)
1281     {
1282       if ((folder_path && gtk_file_path_compare (folder_path, chooser_entry->current_folder_path) != 0)
1283           || force_reload)
1284         {
1285           reload = TRUE;
1286
1287           /* We changed our current directory.  We need to clear out the old
1288            * directory information.
1289            */
1290           if (chooser_entry->current_folder)
1291             {
1292               if (chooser_entry->load_folder_handle)
1293                 {
1294                   gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
1295                   chooser_entry->load_folder_handle = NULL;
1296                 }
1297
1298               g_object_unref (chooser_entry->current_folder);
1299               chooser_entry->current_folder = NULL;
1300             }
1301
1302           gtk_file_path_free (chooser_entry->current_folder_path);
1303           chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
1304         }
1305     }
1306   else
1307     {
1308       chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
1309       reload = TRUE;
1310     }
1311
1312   if (reload)
1313     start_loading_current_folder (chooser_entry);
1314 }
1315
1316 static void
1317 refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry,
1318                                       RefreshMode          refresh_mode)
1319 {
1320   GtkEditable *editable;
1321   gint end_pos;
1322   gchar *text;
1323   GtkFilePath *folder_path;
1324   gchar *file_part;
1325   gsize total_len, file_part_len;
1326   gint file_part_pos;
1327
1328   editable = GTK_EDITABLE (chooser_entry);
1329
1330   switch (refresh_mode)
1331     {
1332     case REFRESH_UP_TO_CURSOR_POSITION:
1333       end_pos = gtk_editable_get_position (editable);
1334       break;
1335
1336     case REFRESH_WHOLE_TEXT:
1337       end_pos = GTK_ENTRY (chooser_entry)->text_length;
1338       break;
1339
1340     default:
1341       g_assert_not_reached ();
1342       return;
1343     }
1344
1345   text = gtk_editable_get_chars (editable, 0, end_pos);
1346   
1347   if (!chooser_entry->file_system ||
1348       !chooser_entry->base_folder ||
1349       !gtk_file_system_parse (chooser_entry->file_system,
1350                               chooser_entry->base_folder, text,
1351                               &folder_path, &file_part, NULL)) /* NULL-GError */
1352     {
1353       folder_path = gtk_file_path_copy (chooser_entry->base_folder);
1354       file_part = g_strdup ("");
1355       file_part_pos = -1;
1356     }
1357   else
1358     {
1359       file_part_len = strlen (file_part);
1360       total_len = strlen (text);
1361       if (total_len > file_part_len)
1362         file_part_pos = g_utf8_strlen (text, total_len - file_part_len);
1363       else
1364         file_part_pos = 0;
1365     }
1366
1367   g_free (text);
1368
1369   g_free (chooser_entry->file_part);
1370
1371   chooser_entry->file_part = file_part;
1372   chooser_entry->file_part_pos = file_part_pos;
1373
1374   reload_current_folder (chooser_entry, folder_path, file_part_pos == -1);
1375   gtk_file_path_free (folder_path);
1376 }
1377
1378 static void
1379 autocomplete (GtkFileChooserEntry *chooser_entry)
1380 {
1381   g_assert (chooser_entry->current_folder != NULL);
1382   g_assert (gtk_file_folder_is_finished_loading (chooser_entry->current_folder));
1383   g_assert (gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length);
1384
1385   append_common_prefix (chooser_entry, TRUE, FALSE);
1386 }
1387
1388 static void
1389 start_autocompletion (GtkFileChooserEntry *chooser_entry)
1390 {
1391   refresh_current_folder_and_file_part (chooser_entry, REFRESH_UP_TO_CURSOR_POSITION);
1392
1393   if (!chooser_entry->current_folder)
1394     {
1395       /* We don't beep or anything, since this is autocompletion - the user
1396        * didn't request any action explicitly.
1397        */
1398       return;
1399     }
1400
1401   if (gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
1402     autocomplete (chooser_entry);
1403   else
1404     chooser_entry->load_complete_action = LOAD_COMPLETE_AUTOCOMPLETE;
1405 }
1406
1407 static gboolean
1408 start_autocompletion_idle_handler (gpointer data)
1409 {
1410   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
1411
1412   start_autocompletion (chooser_entry);
1413
1414   chooser_entry->start_autocompletion_idle_id = 0;
1415
1416   return FALSE;
1417 }
1418
1419 static void
1420 install_start_autocompletion_idle (GtkFileChooserEntry *chooser_entry)
1421 {
1422   if (chooser_entry->start_autocompletion_idle_id != 0)
1423     return;
1424
1425   chooser_entry->start_autocompletion_idle_id = g_idle_add (start_autocompletion_idle_handler, chooser_entry);
1426 }
1427
1428 #ifdef G_OS_WIN32
1429 static gint
1430 insert_text_callback (GtkFileChooserEntry *chooser_entry,
1431                       const gchar         *new_text,
1432                       gint                 new_text_length,
1433                       gint                *position,
1434                       gpointer             user_data)
1435 {
1436   const gchar *colon = memchr (new_text, ':', new_text_length);
1437   gint i;
1438
1439   /* Disallow these characters altogether */
1440   for (i = 0; i < new_text_length; i++)
1441     {
1442       if (new_text[i] == '<' ||
1443           new_text[i] == '>' ||
1444           new_text[i] == '"' ||
1445           new_text[i] == '|' ||
1446           new_text[i] == '*' ||
1447           new_text[i] == '?')
1448         break;
1449     }
1450
1451   if (i < new_text_length ||
1452       /* Disallow entering text that would cause a colon to be anywhere except
1453        * after a drive letter.
1454        */
1455       (colon != NULL &&
1456        *position + (colon - new_text) != 1) ||
1457       (new_text_length > 0 &&
1458        *position <= 1 &&
1459        GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1460        gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':'))
1461     {
1462       gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
1463       g_signal_stop_emission_by_name (chooser_entry, "insert_text");
1464       return FALSE;
1465     }
1466
1467   return TRUE;
1468 }
1469
1470 static void
1471 delete_text_callback (GtkFileChooserEntry *chooser_entry,
1472                       gint                 start_pos,
1473                       gint                 end_pos,
1474                       gpointer             user_data)
1475 {
1476   /* If deleting a drive letter, delete the colon, too */
1477   if (start_pos == 0 && end_pos == 1 &&
1478       GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1479       gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':')
1480     {
1481       g_signal_handlers_block_by_func (chooser_entry,
1482                                        G_CALLBACK (delete_text_callback),
1483                                        user_data);
1484       gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), 0, 1);
1485       g_signal_handlers_unblock_by_func (chooser_entry,
1486                                          G_CALLBACK (delete_text_callback),
1487                                          user_data);
1488     }
1489 }
1490 #endif
1491
1492 /**
1493  * _gtk_file_chooser_entry_new:
1494  * @eat_tabs: If %FALSE, allow focus navigation with the tab key.
1495  *
1496  * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
1497  * is an internal implementation widget for the GTK+ file chooser
1498  * which is an entry with completion with respect to a
1499  * #GtkFileSystem object.
1500  *
1501  * Return value: the newly created #GtkFileChooserEntry
1502  **/
1503 GtkWidget *
1504 _gtk_file_chooser_entry_new (gboolean eat_tabs)
1505 {
1506   GtkFileChooserEntry *chooser_entry;
1507
1508   chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
1509   chooser_entry->eat_tabs = (eat_tabs != FALSE);
1510
1511   return GTK_WIDGET (chooser_entry);
1512 }
1513
1514 /**
1515  * _gtk_file_chooser_entry_set_file_system:
1516  * @chooser_entry: a #GtkFileChooser
1517  * @file_system: an object implementing #GtkFileSystem
1518  *
1519  * Sets the file system for @chooser_entry.
1520  **/
1521 void
1522 _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry,
1523                                          GtkFileSystem       *file_system)
1524 {
1525   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1526   g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system));
1527
1528   if (file_system != chooser_entry->file_system)
1529     {
1530       if (chooser_entry->file_system)
1531         g_object_unref (chooser_entry->file_system);
1532
1533       chooser_entry->file_system = g_object_ref (file_system);
1534     }
1535 }
1536
1537 /**
1538  * _gtk_file_chooser_entry_set_base_folder:
1539  * @chooser_entry: a #GtkFileChooserEntry
1540  * @path: path of a folder in the chooser entries current file system.
1541  *
1542  * Sets the folder with respect to which completions occur.
1543  **/
1544 void
1545 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
1546                                          const GtkFilePath   *path)
1547 {
1548   if (chooser_entry->base_folder)
1549     gtk_file_path_free (chooser_entry->base_folder);
1550
1551   chooser_entry->base_folder = gtk_file_path_copy (path);
1552
1553   clear_completions (chooser_entry);
1554   _gtk_file_chooser_entry_select_filename (chooser_entry);
1555 }
1556
1557 /**
1558  * _gtk_file_chooser_entry_get_current_folder:
1559  * @chooser_entry: a #GtkFileChooserEntry
1560  *
1561  * Gets the current folder for the #GtkFileChooserEntry. If the
1562  * user has only entered a filename, this will be the base folder
1563  * (see _gtk_file_chooser_entry_set_base_folder()), but if the
1564  * user has entered a relative or absolute path, then it will
1565  * be different. If the user has entered a relative or absolute
1566  * path that doesn't point to a folder in the file system, it will
1567  * be %NULL.
1568  *
1569  * Return value: the path of current folder - this value is owned by the
1570  *  chooser entry and must not be modified or freed.
1571  **/
1572 const GtkFilePath *
1573 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
1574 {
1575   if (chooser_entry->has_completion)
1576     {
1577       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
1578                                  GTK_ENTRY (chooser_entry)->text_length);
1579     }
1580   return chooser_entry->current_folder_path;
1581 }
1582
1583 /**
1584  * _gtk_file_chooser_entry_get_file_part:
1585  * @chooser_entry: a #GtkFileChooserEntry
1586  *
1587  * Gets the non-folder portion of whatever the user has entered
1588  * into the file selector. What is returned is a UTF-8 string,
1589  * and if a filename path is needed, gtk_file_system_make_path()
1590  * must be used
1591   *
1592  * Return value: the entered filename - this value is owned by the
1593  *  chooser entry and must not be modified or freed.
1594  **/
1595 const gchar *
1596 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
1597 {
1598   if (chooser_entry->has_completion)
1599     {
1600       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
1601                                  GTK_ENTRY (chooser_entry)->text_length);
1602     }
1603
1604   refresh_current_folder_and_file_part (chooser_entry, REFRESH_WHOLE_TEXT);
1605
1606   return chooser_entry->file_part;
1607 }
1608
1609 /**
1610  * _gtk_file_chooser_entry_set_file_part:
1611  * @chooser_entry: a #GtkFileChooserEntry
1612  * @file_part: text to display in the entry, in UTF-8
1613  *
1614  * Sets the current text shown in the file chooser entry.
1615  **/
1616 void
1617 _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry,
1618                                        const gchar         *file_part)
1619 {
1620   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1621
1622   chooser_entry->in_change = TRUE;
1623   clear_completions (chooser_entry);
1624   gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part);
1625   chooser_entry->in_change = FALSE;
1626 }
1627
1628
1629 /**
1630  * _gtk_file_chooser_entry_set_action:
1631  * @chooser_entry: a #GtkFileChooserEntry
1632  * @action: the action which is performed by the file selector using this entry
1633  *
1634  * Sets action which is performed by the file selector using this entry. 
1635  * The #GtkFileChooserEntry will use different completion strategies for 
1636  * different actions.
1637  **/
1638 void
1639 _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry,
1640                                     GtkFileChooserAction action)
1641 {
1642   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1643   
1644   if (chooser_entry->action != action)
1645     {
1646       GtkEntryCompletion *comp;
1647
1648       chooser_entry->action = action;
1649
1650       comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
1651
1652       /* FIXME: do we need to actually set the following? */
1653
1654       switch (action)
1655         {
1656         case GTK_FILE_CHOOSER_ACTION_OPEN:
1657         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1658           gtk_entry_completion_set_popup_single_match (comp, FALSE);
1659           break;
1660         case GTK_FILE_CHOOSER_ACTION_SAVE:
1661         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1662           gtk_entry_completion_set_popup_single_match (comp, TRUE);
1663           break;
1664         }
1665     }
1666 }
1667
1668
1669 /**
1670  * _gtk_file_chooser_entry_get_action:
1671  * @chooser_entry: a #GtkFileChooserEntry
1672  *
1673  * Gets the action for this entry. 
1674  *
1675  * Returns: the action
1676  **/
1677 GtkFileChooserAction
1678 _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry)
1679 {
1680   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry),
1681                         GTK_FILE_CHOOSER_ACTION_OPEN);
1682   
1683   return chooser_entry->action;
1684 }
1685
1686 gboolean
1687 _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry,
1688                                        const GtkFilePath   *path)
1689 {
1690   gboolean retval = FALSE;
1691
1692   if (chooser_entry->current_folder)
1693     {
1694       GtkFileInfo *file_info;
1695
1696       file_info = gtk_file_folder_get_info (chooser_entry->current_folder,
1697                                             path, NULL);
1698       if (file_info)
1699         {
1700           retval = gtk_file_info_get_is_folder (file_info);
1701           gtk_file_info_free (file_info);
1702         }
1703     }
1704
1705   return retval;
1706 }
1707
1708
1709 /*
1710  * _gtk_file_chooser_entry_select_filename:
1711  * @chooser_entry: a #GtkFileChooserEntry
1712  *
1713  * Selects the filename (without the extension) for user edition.
1714  */
1715 void
1716 _gtk_file_chooser_entry_select_filename (GtkFileChooserEntry *chooser_entry)
1717 {
1718   const gchar *str, *ext;
1719   glong len = -1;
1720
1721   if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE)
1722     {
1723       str = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
1724       ext = g_strrstr (str, ".");
1725
1726       if (ext)
1727        len = g_utf8_pointer_to_offset (str, ext);
1728     }
1729
1730   gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, (gint) len);
1731 }
1732