]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserentry.c
a98041aabd3cc793dc5a89872908aadfff1cf9fe
[~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   gtk_widget_error_bell (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 static char *
451 trim_dir_separator_suffix (const char *str)
452 {
453   int len;
454
455   len = strlen (str);
456   if (len > 0 && G_IS_DIR_SEPARATOR (str[len - 1]))
457     return g_strndup (str, len - 1);
458   else
459     return g_strdup (str);
460 }
461
462 /* Determines if the completion model has entries with a common prefix relative
463  * to the current contents of the entry.  Also, if there's one and only one such
464  * path, stores it in unique_path_ret.
465  */
466 static gboolean
467 find_common_prefix (GtkFileChooserEntry *chooser_entry,
468                     gchar               **common_prefix_ret,
469                     GtkFilePath         **unique_path_ret,
470                     gboolean             *is_complete_not_unique_ret,
471                     gboolean             *prefix_expands_the_file_part_ret,
472                     GError              **error)
473 {
474   GtkEditable *editable;
475   GtkTreeIter iter;
476   gboolean parsed;
477   gboolean valid;
478   char *text_up_to_cursor;
479   GtkFilePath *parsed_folder_path;
480   char *parsed_file_part;
481
482   *common_prefix_ret = NULL;
483   *unique_path_ret = NULL;
484   *is_complete_not_unique_ret = FALSE;
485   *prefix_expands_the_file_part_ret = FALSE;
486
487   editable = GTK_EDITABLE (chooser_entry);
488
489   text_up_to_cursor = gtk_editable_get_chars (editable, 0, gtk_editable_get_position (editable));
490
491   parsed = gtk_file_system_parse (chooser_entry->file_system,
492                                   chooser_entry->base_folder,
493                                   text_up_to_cursor,
494                                   &parsed_folder_path,
495                                   &parsed_file_part,
496                                   error);
497
498   g_free (text_up_to_cursor);
499
500   if (!parsed)
501     return FALSE;
502
503   g_assert (parsed_folder_path != NULL
504             && chooser_entry->current_folder_path != NULL
505             && gtk_file_path_compare (parsed_folder_path, chooser_entry->current_folder_path) == 0);
506
507   gtk_file_path_free (parsed_folder_path);
508
509   /* First pass: find the common prefix */
510
511   valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
512
513   while (valid)
514     {
515       gchar *display_name;
516       GtkFilePath *path;
517
518       gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store),
519                           &iter,
520                           DISPLAY_NAME_COLUMN, &display_name,
521                           PATH_COLUMN, &path,
522                           -1);
523
524       if (g_str_has_prefix (display_name, parsed_file_part))
525         {
526           if (!*common_prefix_ret)
527             {
528               *common_prefix_ret = trim_dir_separator_suffix (display_name);
529               *unique_path_ret = gtk_file_path_copy (path);
530             }
531           else
532             {
533               gchar *p = *common_prefix_ret;
534               const gchar *q = display_name;
535
536               while (*p && *p == *q)
537                 {
538                   p++;
539                   q++;
540                 }
541
542               *p = '\0';
543
544               gtk_file_path_free (*unique_path_ret);
545               *unique_path_ret = NULL;
546             }
547         }
548
549       g_free (display_name);
550       gtk_file_path_free (path);
551       valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
552     }
553
554   /* Second pass: see if the prefix we found is a complete match */
555
556   if (*common_prefix_ret != NULL)
557     {
558       valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
559
560       while (valid)
561         {
562           gchar *display_name;
563           int len;
564
565           gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store),
566                               &iter,
567                               DISPLAY_NAME_COLUMN, &display_name,
568                               -1);
569           len = strlen (display_name);
570           g_assert (len > 0);
571
572           if (G_IS_DIR_SEPARATOR (display_name[len - 1]))
573             len--;
574
575           if (strncmp (*common_prefix_ret, display_name, len) == 0)
576             *is_complete_not_unique_ret = TRUE;
577
578           g_free (display_name);
579           valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
580         }
581
582       /* Finally:  Did we generate a new completion, or was the user's input already completed as far as it could go? */
583
584       *prefix_expands_the_file_part_ret = g_utf8_strlen (*common_prefix_ret, -1) > g_utf8_strlen (parsed_file_part, -1);
585     }
586
587   g_free (parsed_file_part);
588
589   return TRUE;
590 }
591
592 static gboolean
593 char_after_cursor_is_directory_separator (GtkFileChooserEntry *chooser_entry)
594 {
595   int cursor_pos;
596   gboolean result;
597
598   result = FALSE;
599
600   cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry));
601   if (cursor_pos < GTK_ENTRY (chooser_entry)->text_length)
602     {
603       char *next_char_str;
604
605       next_char_str = gtk_editable_get_chars (GTK_EDITABLE (chooser_entry), cursor_pos, cursor_pos + 1);
606       if (G_IS_DIR_SEPARATOR (*next_char_str))
607         result = TRUE;
608
609       g_free (next_char_str);
610     }
611
612   return result;
613 }
614
615 typedef enum {
616   INVALID_INPUT,                /* what the user typed is bogus */
617   NO_MATCH,                     /* no matches based on what the user typed */
618   NOTHING_INSERTED_COMPLETE,    /* what the user typed is already completed as far as it will go */
619   NOTHING_INSERTED_UNIQUE,      /* what the user typed is already completed, and is a unique match */
620   COMPLETED,                    /* completion inserted (ambiguous suffix) */
621   COMPLETED_UNIQUE,             /* completion inserted, and it is a complete name and a unique match */
622   COMPLETE_BUT_NOT_UNIQUE       /* completion inserted, it is a complete name but not unique */
623 } CommonPrefixResult;
624
625 /* Finds a common prefix based on the contents of the entry and mandatorily appends it */
626 static CommonPrefixResult
627 append_common_prefix (GtkFileChooserEntry *chooser_entry,
628                       gboolean             highlight,
629                       gboolean             show_errors)
630 {
631   gchar *common_prefix;
632   GtkFilePath *unique_path;
633   gboolean is_complete_not_unique;
634   gboolean prefix_expands_the_file_part;
635   GError *error;
636   CommonPrefixResult result;
637   gboolean have_result;
638
639   clear_completions (chooser_entry);
640
641   if (chooser_entry->completion_store == NULL)
642     return NO_MATCH;
643
644   error = NULL;
645   if (!find_common_prefix (chooser_entry, &common_prefix, &unique_path, &is_complete_not_unique, &prefix_expands_the_file_part, &error))
646     {
647       if (show_errors)
648         {
649           beep (chooser_entry);
650           pop_up_completion_feedback (chooser_entry, _("Invalid path"));
651         }
652
653       g_error_free (error);
654
655       return INVALID_INPUT;
656     }
657
658   have_result = FALSE;
659
660   if (unique_path)
661     {
662       if (!char_after_cursor_is_directory_separator (chooser_entry))
663         common_prefix = maybe_append_separator_to_path (chooser_entry,
664                                                         unique_path,
665                                                         common_prefix);
666
667       gtk_file_path_free (unique_path);
668
669       if (common_prefix)
670         {
671           if (prefix_expands_the_file_part)
672             result = COMPLETED_UNIQUE;
673           else
674             result = NOTHING_INSERTED_UNIQUE;
675         }
676       else
677         result = INVALID_INPUT;
678
679       have_result = TRUE;
680     }
681   else
682     {
683       if (is_complete_not_unique)
684         {
685           result = COMPLETE_BUT_NOT_UNIQUE;
686           have_result = TRUE;
687         }
688     }
689
690   if (common_prefix)
691     {
692       gint cursor_pos;
693       gint common_prefix_len;
694       gint pos;
695
696       cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry));
697       common_prefix_len = g_utf8_strlen (common_prefix, -1);
698
699       pos = chooser_entry->file_part_pos;
700
701       if (prefix_expands_the_file_part)
702         {
703           chooser_entry->in_change = TRUE;
704           gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
705                                     pos, cursor_pos);
706           gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
707                                     common_prefix, -1, 
708                                     &pos);
709           chooser_entry->in_change = FALSE;
710
711           if (highlight)
712             {
713               gtk_editable_select_region (GTK_EDITABLE (chooser_entry),
714                                           cursor_pos,
715                                           pos); /* equivalent to cursor_pos + common_prefix_len); */
716               chooser_entry->has_completion = TRUE;
717             }
718           else
719             gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos);
720         }
721       else if (!have_result)
722         {
723           result = NOTHING_INSERTED_COMPLETE;
724           have_result = TRUE;
725         }
726
727       g_free (common_prefix);
728
729       if (have_result)
730         return result;
731       else
732         return COMPLETED;
733     }
734   else
735     {
736       if (have_result)
737         return result;
738       else
739         return NO_MATCH;
740     }
741 }
742
743 static void
744 gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
745                                        const gchar *new_text,
746                                        gint         new_text_length,
747                                        gint        *position)
748 {
749   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
750   gint old_text_len;
751   gint insert_pos;
752
753   old_text_len = GTK_ENTRY (chooser_entry)->text_length;
754   insert_pos = *position;
755
756   parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position);
757
758   if (chooser_entry->in_change)
759     return;
760
761   remove_completion_feedback (chooser_entry);
762
763   if ((chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN
764        || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
765       && insert_pos == old_text_len)
766     install_start_autocompletion_idle (chooser_entry);
767 }
768
769 static void
770 clear_completions_if_not_in_change (GtkFileChooserEntry *chooser_entry)
771 {
772   if (chooser_entry->in_change)
773     return;
774
775   clear_completions (chooser_entry);
776 }
777
778 static void
779 gtk_file_chooser_entry_do_delete_text (GtkEditable *editable,
780                                        gint         start_pos,
781                                        gint         end_pos)
782 {
783   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
784
785   parent_editable_iface->do_delete_text (editable, start_pos, end_pos);
786
787   clear_completions_if_not_in_change (chooser_entry);
788 }
789
790 static void
791 gtk_file_chooser_entry_set_position (GtkEditable *editable,
792                                      gint         position)
793 {
794   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
795
796   parent_editable_iface->set_position (editable, position);
797
798   clear_completions_if_not_in_change (chooser_entry);
799 }
800
801 static void
802 gtk_file_chooser_entry_set_selection_bounds (GtkEditable *editable,
803                                              gint         start_pos,
804                                              gint         end_pos)
805 {
806   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
807
808   parent_editable_iface->set_selection_bounds (editable, start_pos, end_pos);
809
810   clear_completions_if_not_in_change (chooser_entry);
811 }
812
813 static void
814 gtk_file_chooser_entry_grab_focus (GtkWidget *widget)
815 {
816   GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget);
817   _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget));
818 }
819
820 static void
821 gtk_file_chooser_entry_unmap (GtkWidget *widget)
822 {
823   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
824
825   remove_completion_feedback (chooser_entry);
826
827   GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->unmap (widget);
828 }
829
830 static gboolean
831 completion_feedback_window_expose_event_cb (GtkWidget      *widget,
832                                             GdkEventExpose *event,
833                                             gpointer        data)
834 {
835   /* Stolen from gtk_tooltip_paint_window() */
836
837   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
838
839   gtk_paint_flat_box (chooser_entry->completion_feedback_window->style,
840                       chooser_entry->completion_feedback_window->window,
841                       GTK_STATE_NORMAL,
842                       GTK_SHADOW_OUT,
843                       NULL,
844                       chooser_entry->completion_feedback_window,
845                       "tooltip",
846                       0, 0,
847                       chooser_entry->completion_feedback_window->allocation.width,
848                       chooser_entry->completion_feedback_window->allocation.height);
849
850   return FALSE;
851 }
852
853 static void
854 set_invisible_mouse_cursor (GdkWindow *window)
855 {
856   /* Stolen from gtkentry.c:set_invisible_cursor() */
857   /* FIXME: implement a stupid public gdk_window_set_invisible_mouse_cursor() */
858
859   GdkBitmap *empty_bitmap;
860   GdkCursor *cursor;
861   GdkColor useless;
862   char invisible_cursor_bits[] = { 0x0 };
863
864   useless.red = useless.green = useless.blue = 0;
865   useless.pixel = 0;
866
867   empty_bitmap = gdk_bitmap_create_from_data (window,
868                                               invisible_cursor_bits,
869                                               1, 1);
870
871   cursor = gdk_cursor_new_from_pixmap (empty_bitmap,
872                                        empty_bitmap,
873                                        &useless,
874                                        &useless, 0, 0);
875
876   gdk_window_set_cursor (window, cursor);
877
878   gdk_cursor_unref (cursor);
879
880   g_object_unref (empty_bitmap);
881 }
882
883 static void
884 completion_feedback_window_realize_cb (GtkWidget *widget,
885                                        gpointer data)
886 {
887   /* We hide the mouse cursor inside the completion feedback window, since
888    * GtkEntry hides the cursor when the user types.  We don't want the cursor to
889    * come back if the completion feedback ends up where the mouse is.
890    */
891   set_invisible_mouse_cursor (widget->window);
892 }
893
894 static void
895 create_completion_feedback_window (GtkFileChooserEntry *chooser_entry)
896 {
897   /* Stolen from gtk_tooltip_init() */
898
899   GtkWidget *alignment;
900
901   chooser_entry->completion_feedback_window = gtk_window_new (GTK_WINDOW_POPUP);
902   gtk_window_set_type_hint (GTK_WINDOW (chooser_entry->completion_feedback_window),
903                             GDK_WINDOW_TYPE_HINT_TOOLTIP);
904   gtk_widget_set_app_paintable (chooser_entry->completion_feedback_window, TRUE);
905   gtk_window_set_resizable (GTK_WINDOW (chooser_entry->completion_feedback_window), FALSE);
906   gtk_widget_set_name (chooser_entry->completion_feedback_window, "gtk-tooltip");
907
908   alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
909   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
910                              chooser_entry->completion_feedback_window->style->ythickness,
911                              chooser_entry->completion_feedback_window->style->ythickness,
912                              chooser_entry->completion_feedback_window->style->xthickness,
913                              chooser_entry->completion_feedback_window->style->xthickness);
914   gtk_container_add (GTK_CONTAINER (chooser_entry->completion_feedback_window), alignment);
915   gtk_widget_show (alignment);
916
917   g_signal_connect (chooser_entry->completion_feedback_window, "expose_event",
918                     G_CALLBACK (completion_feedback_window_expose_event_cb), chooser_entry);
919   g_signal_connect (chooser_entry->completion_feedback_window, "realize",
920                     G_CALLBACK (completion_feedback_window_realize_cb), chooser_entry);
921   /* FIXME: connect to motion-notify-event, and *show* the cursor when the mouse moves */
922
923   chooser_entry->completion_feedback_label = gtk_label_new (NULL);
924   gtk_container_add (GTK_CONTAINER (alignment), chooser_entry->completion_feedback_label);
925   gtk_widget_show (chooser_entry->completion_feedback_label);
926 }
927
928 static gboolean
929 completion_feedback_timeout_cb (gpointer data)
930 {
931   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
932
933   chooser_entry->completion_feedback_timeout_id = 0;
934
935   remove_completion_feedback (chooser_entry);
936   return FALSE;
937 }
938
939 static void
940 install_completion_feedback_timer (GtkFileChooserEntry *chooser_entry)
941 {
942   if (chooser_entry->completion_feedback_timeout_id != 0)
943     g_source_remove (chooser_entry->completion_feedback_timeout_id);
944
945   chooser_entry->completion_feedback_timeout_id = gdk_threads_add_timeout (COMPLETION_FEEDBACK_TIMEOUT_MS,
946                                                                            completion_feedback_timeout_cb,
947                                                                            chooser_entry);
948 }
949
950 /* Gets the x position of the text cursor in the entry, in widget coordinates */
951 static void
952 get_entry_cursor_x (GtkFileChooserEntry *chooser_entry,
953                     gint                *x_ret)
954 {
955   /* FIXME: see the docs for gtk_entry_get_layout_offsets().  As an exercise for
956    * the reader, you have to implement support for the entry's scroll offset and
957    * RTL layouts and all the fancy Pango stuff.
958    */
959
960   PangoLayout *layout;
961   gint layout_x, layout_y;
962   gint layout_index;
963   PangoRectangle strong_pos;
964
965   layout = gtk_entry_get_layout (GTK_ENTRY (chooser_entry));
966
967   gtk_entry_get_layout_offsets (GTK_ENTRY (chooser_entry), &layout_x, &layout_y);
968
969   layout_index = gtk_entry_text_index_to_layout_index (GTK_ENTRY (chooser_entry),
970                                                        GTK_ENTRY (chooser_entry)->current_pos);
971
972   pango_layout_get_cursor_pos (layout, layout_index, &strong_pos, NULL);
973
974   *x_ret = layout_x + strong_pos.x / PANGO_SCALE;
975 }
976
977 static void
978 show_completion_feedback_window (GtkFileChooserEntry *chooser_entry)
979 {
980   /* More or less stolen from gtk_tooltip_position() */
981
982   GtkRequisition feedback_req;
983   gint entry_x, entry_y;
984   gint cursor_x;
985   GtkAllocation *entry_allocation;
986   int feedback_x, feedback_y;
987
988   gtk_widget_size_request (chooser_entry->completion_feedback_window, &feedback_req);
989
990   gdk_window_get_origin (GTK_WIDGET (chooser_entry)->window, &entry_x, &entry_y);
991   entry_allocation = &(GTK_WIDGET (chooser_entry)->allocation);
992
993   get_entry_cursor_x (chooser_entry, &cursor_x);
994
995   /* FIXME: fit to the screen if we bump on the screen's edge */
996   feedback_x = entry_x + cursor_x + entry_allocation->height / 2; /* cheap "half M-width" */
997   feedback_y = entry_y + (entry_allocation->height - feedback_req.height) / 2;
998
999   gtk_window_move (GTK_WINDOW (chooser_entry->completion_feedback_window), feedback_x, feedback_y);
1000   gtk_widget_show (chooser_entry->completion_feedback_window);
1001
1002   install_completion_feedback_timer (chooser_entry);
1003 }
1004
1005 static void
1006 pop_up_completion_feedback (GtkFileChooserEntry *chooser_entry,
1007                             const gchar         *feedback)
1008 {
1009   if (chooser_entry->completion_feedback_window == NULL)
1010     create_completion_feedback_window (chooser_entry);
1011
1012   gtk_label_set_text (GTK_LABEL (chooser_entry->completion_feedback_label), feedback);
1013
1014   show_completion_feedback_window (chooser_entry);
1015 }
1016
1017 static void
1018 remove_completion_feedback (GtkFileChooserEntry *chooser_entry)
1019 {
1020   if (chooser_entry->completion_feedback_window)
1021     gtk_widget_destroy (chooser_entry->completion_feedback_window);
1022
1023   chooser_entry->completion_feedback_window = NULL;
1024   chooser_entry->completion_feedback_label = NULL;
1025
1026   if (chooser_entry->completion_feedback_timeout_id != 0)
1027     {
1028       g_source_remove (chooser_entry->completion_feedback_timeout_id);
1029       chooser_entry->completion_feedback_timeout_id = 0;
1030     }
1031 }
1032
1033 static void
1034 explicitly_complete (GtkFileChooserEntry *chooser_entry)
1035 {
1036   CommonPrefixResult result;
1037
1038   g_assert (chooser_entry->current_folder != NULL);
1039   g_assert (gtk_file_folder_is_finished_loading (chooser_entry->current_folder));
1040
1041   /* FIXME: see what Emacs does in case there is no common prefix, or there is more than one match:
1042    *
1043    * - If there is a common prefix, insert it (done)
1044    * - If there is no common prefix, pop up the suggestion window
1045    * - If there are no matches at all, beep and bring up a tooltip (done)
1046    * - If the suggestion window is already up, scroll it
1047    */
1048   result = append_common_prefix (chooser_entry, FALSE, TRUE);
1049
1050   switch (result)
1051     {
1052     case INVALID_INPUT:
1053       /* We already beeped in append_common_prefix(); do nothing here */
1054       break;
1055
1056     case NO_MATCH:
1057       beep (chooser_entry);
1058       pop_up_completion_feedback (chooser_entry, _("No match"));
1059       break;
1060
1061     case NOTHING_INSERTED_COMPLETE:
1062       /* FIXME: pop up the suggestion window or scroll it */
1063       break;
1064
1065     case NOTHING_INSERTED_UNIQUE:
1066       pop_up_completion_feedback (chooser_entry, _("Sole completion"));
1067       break;
1068
1069     case COMPLETED:
1070       /* Nothing to do */
1071       break;
1072
1073     case COMPLETED_UNIQUE:
1074       /* Nothing to do */
1075       break;
1076
1077     case COMPLETE_BUT_NOT_UNIQUE:
1078       pop_up_completion_feedback (chooser_entry, _("Complete, but not unique"));
1079       break;
1080
1081     default:
1082       g_assert_not_reached ();
1083     }
1084 }
1085
1086 static void
1087 start_explicit_completion (GtkFileChooserEntry *chooser_entry)
1088 {
1089   refresh_current_folder_and_file_part (chooser_entry, REFRESH_UP_TO_CURSOR_POSITION);
1090
1091   if (!chooser_entry->current_folder_path)
1092     {
1093       /* Here, no folder path means we couldn't parse what the user typed. */
1094
1095       beep (chooser_entry);
1096       pop_up_completion_feedback (chooser_entry, _("Invalid path"));
1097
1098       chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1099       return;
1100     }
1101
1102   if (chooser_entry->current_folder
1103       && gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
1104     {
1105       explicitly_complete (chooser_entry);
1106     }
1107   else
1108     {
1109       chooser_entry->load_complete_action = LOAD_COMPLETE_EXPLICIT_COMPLETION;
1110
1111       pop_up_completion_feedback (chooser_entry, _("Completing..."));
1112     }
1113 }
1114
1115 static gboolean
1116 gtk_file_chooser_entry_focus (GtkWidget        *widget,
1117                               GtkDirectionType  direction)
1118 {
1119   GtkFileChooserEntry *chooser_entry;
1120   GtkEditable *editable;
1121   GtkEntry *entry;
1122   GdkModifierType state;
1123   gboolean control_pressed;
1124
1125   chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
1126   editable = GTK_EDITABLE (widget);
1127   entry = GTK_ENTRY (widget);
1128
1129   if (!chooser_entry->eat_tabs)
1130     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
1131
1132   control_pressed = FALSE;
1133
1134   if (gtk_get_current_event_state (&state))
1135     {
1136       if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
1137         control_pressed = TRUE;
1138     }
1139
1140   /* This is a bit evil -- it makes Tab never leave the entry. It basically
1141    * makes it 'safe' for people to hit. */
1142   if ((direction == GTK_DIR_TAB_FORWARD) &&
1143       (GTK_WIDGET_HAS_FOCUS (widget)) &&
1144       (! control_pressed))
1145     {
1146       if (chooser_entry->has_completion)
1147         gtk_editable_set_position (editable, entry->text_length);
1148       else
1149         start_explicit_completion (chooser_entry);
1150
1151       return TRUE;
1152     }
1153   else
1154     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
1155 }
1156
1157 static gboolean
1158 gtk_file_chooser_entry_focus_out_event (GtkWidget     *widget,
1159                                         GdkEventFocus *event)
1160 {
1161   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
1162
1163   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1164  
1165   return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus_out_event (widget, event);
1166 }
1167
1168 static void
1169 commit_completion_and_refresh (GtkFileChooserEntry *chooser_entry)
1170 {
1171   if (chooser_entry->has_completion)
1172     {
1173       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
1174                                  GTK_ENTRY (chooser_entry)->text_length);
1175     }
1176
1177   refresh_current_folder_and_file_part (chooser_entry, REFRESH_WHOLE_TEXT);
1178 }
1179
1180 static void
1181 gtk_file_chooser_entry_activate (GtkEntry *entry)
1182 {
1183   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry);
1184
1185   commit_completion_and_refresh (chooser_entry);
1186   GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry);
1187 }
1188
1189 static void
1190 discard_completion_store (GtkFileChooserEntry *chooser_entry)
1191 {
1192   if (!chooser_entry->completion_store)
1193     return;
1194
1195   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);
1196   g_object_unref (chooser_entry->completion_store);
1197   chooser_entry->completion_store = NULL;
1198 }
1199
1200 /* Fills the completion store from the contents of the current folder */
1201 static void
1202 populate_completion_store (GtkFileChooserEntry *chooser_entry)
1203 {
1204   GSList *paths;
1205   GSList *tmp_list;
1206
1207   discard_completion_store (chooser_entry);
1208
1209   if (!gtk_file_folder_list_children (chooser_entry->current_folder, &paths, NULL)) /* NULL-GError */
1210     return;
1211
1212   chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS,
1213                                                         G_TYPE_STRING,
1214                                                         GTK_TYPE_FILE_PATH);
1215
1216   for (tmp_list = paths; tmp_list; tmp_list = tmp_list->next)
1217     {
1218       GtkFileInfo *info;
1219       GtkFilePath *path;
1220
1221       path = tmp_list->data;
1222
1223       info = gtk_file_folder_get_info (chooser_entry->current_folder,
1224                                        path,
1225                                        NULL); /* NULL-GError */
1226       if (info)
1227         {
1228           gchar *display_name = g_strdup (gtk_file_info_get_display_name (info));
1229           GtkTreeIter iter;
1230
1231           display_name = maybe_append_separator_to_path (chooser_entry, path, display_name);
1232
1233           gtk_list_store_append (chooser_entry->completion_store, &iter);
1234           gtk_list_store_set (chooser_entry->completion_store, &iter,
1235                               DISPLAY_NAME_COLUMN, display_name,
1236                               PATH_COLUMN, path,
1237                               -1);
1238
1239           gtk_file_info_free (info);
1240           g_free (display_name);
1241         }
1242     }
1243
1244   gtk_file_paths_free (paths);
1245
1246   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store),
1247                                         DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING);
1248
1249   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)),
1250                                   GTK_TREE_MODEL (chooser_entry->completion_store));
1251 }
1252
1253 /* When we finish loading the current folder, this function should get called to
1254  * perform the deferred autocompletion or explicit completion.
1255  */
1256 static void
1257 perform_load_complete_action (GtkFileChooserEntry *chooser_entry)
1258 {
1259   switch (chooser_entry->load_complete_action)
1260     {
1261     case LOAD_COMPLETE_NOTHING:
1262       break;
1263
1264     case LOAD_COMPLETE_AUTOCOMPLETE:
1265       autocomplete (chooser_entry);
1266       break;
1267
1268     case LOAD_COMPLETE_EXPLICIT_COMPLETION:
1269       explicitly_complete (chooser_entry);
1270       break;
1271
1272     default:
1273       g_assert_not_reached ();
1274     }
1275
1276   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1277 }
1278
1279 static void
1280 finish_folder_load (GtkFileChooserEntry *chooser_entry)
1281 {
1282   populate_completion_store (chooser_entry);
1283   perform_load_complete_action (chooser_entry);
1284
1285   gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL);
1286 }
1287
1288 /* Callback when the current folder finishes loading */
1289 static void
1290 finished_loading_cb (GtkFileFolder *folder,
1291                      gpointer       data)
1292 {
1293   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
1294
1295   finish_folder_load (chooser_entry);
1296 }
1297
1298 /* Callback when the current folder's handle gets obtained (not necessarily loaded completely) */
1299 static void
1300 load_directory_get_folder_callback (GtkFileSystemHandle *handle,
1301                                     GtkFileFolder       *folder,
1302                                     const GError        *error,
1303                                     gpointer             data)
1304 {
1305   gboolean cancelled = handle->cancelled;
1306   GtkFileChooserEntry *chooser_entry = data;
1307
1308   if (handle != chooser_entry->load_folder_handle)
1309     goto out;
1310
1311   chooser_entry->load_folder_handle = NULL;
1312
1313   if (error)
1314     {
1315       LoadCompleteAction old_load_complete_action;
1316
1317       old_load_complete_action = chooser_entry->load_complete_action;
1318
1319       clear_completions (chooser_entry);
1320
1321       if (old_load_complete_action == LOAD_COMPLETE_EXPLICIT_COMPLETION)
1322         {
1323           /* Since this came from explicit user action (Tab completion), we'll present errors visually */
1324
1325           beep (chooser_entry);
1326           pop_up_completion_feedback (chooser_entry, error->message);
1327         }
1328
1329       gtk_file_path_free (chooser_entry->current_folder_path);
1330       chooser_entry->current_folder_path = NULL;
1331     }
1332
1333   if (cancelled || error)
1334     goto out;
1335
1336   g_assert (folder != NULL);
1337   chooser_entry->current_folder = folder;
1338
1339   discard_completion_store (chooser_entry);
1340
1341   if (gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
1342     finish_folder_load (chooser_entry);
1343   else
1344     g_signal_connect (chooser_entry->current_folder, "finished-loading",
1345                       G_CALLBACK (finished_loading_cb), chooser_entry);
1346
1347 out:
1348   g_object_unref (chooser_entry);
1349   g_object_unref (handle);
1350 }
1351
1352 static void
1353 start_loading_current_folder (GtkFileChooserEntry *chooser_entry)
1354 {
1355   if (chooser_entry->current_folder_path == NULL ||
1356       chooser_entry->file_system == NULL)
1357     return;
1358
1359   g_assert (chooser_entry->current_folder == NULL);
1360   g_assert (chooser_entry->load_folder_handle == NULL);
1361
1362   chooser_entry->load_folder_handle =
1363     gtk_file_system_get_folder (chooser_entry->file_system,
1364                                 chooser_entry->current_folder_path,
1365                                 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER,
1366                                 load_directory_get_folder_callback,
1367                                 g_object_ref (chooser_entry));
1368 }
1369
1370 static void
1371 reload_current_folder (GtkFileChooserEntry *chooser_entry,
1372                        GtkFilePath         *folder_path,
1373                        gboolean             force_reload)
1374 {
1375   gboolean reload = FALSE;
1376
1377   if (chooser_entry->current_folder_path)
1378     {
1379       if ((folder_path && gtk_file_path_compare (folder_path, chooser_entry->current_folder_path) != 0)
1380           || force_reload)
1381         {
1382           reload = TRUE;
1383
1384           /* We changed our current directory.  We need to clear out the old
1385            * directory information.
1386            */
1387           if (chooser_entry->current_folder)
1388             {
1389               if (chooser_entry->load_folder_handle)
1390                 {
1391                   gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
1392                   chooser_entry->load_folder_handle = NULL;
1393                 }
1394
1395               g_object_unref (chooser_entry->current_folder);
1396               chooser_entry->current_folder = NULL;
1397             }
1398
1399           gtk_file_path_free (chooser_entry->current_folder_path);
1400           chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
1401         }
1402     }
1403   else
1404     {
1405       chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
1406       reload = TRUE;
1407     }
1408
1409   if (reload)
1410     start_loading_current_folder (chooser_entry);
1411 }
1412
1413 static void
1414 refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry,
1415                                       RefreshMode          refresh_mode)
1416 {
1417   GtkEditable *editable;
1418   gint end_pos;
1419   gchar *text;
1420   GtkFilePath *folder_path;
1421   gchar *file_part;
1422   gsize total_len, file_part_len;
1423   gint file_part_pos;
1424
1425   editable = GTK_EDITABLE (chooser_entry);
1426
1427   switch (refresh_mode)
1428     {
1429     case REFRESH_UP_TO_CURSOR_POSITION:
1430       end_pos = gtk_editable_get_position (editable);
1431       break;
1432
1433     case REFRESH_WHOLE_TEXT:
1434       end_pos = GTK_ENTRY (chooser_entry)->text_length;
1435       break;
1436
1437     default:
1438       g_assert_not_reached ();
1439       return;
1440     }
1441
1442   text = gtk_editable_get_chars (editable, 0, end_pos);
1443   
1444   if (!chooser_entry->file_system ||
1445       !chooser_entry->base_folder ||
1446       !gtk_file_system_parse (chooser_entry->file_system,
1447                               chooser_entry->base_folder, text,
1448                               &folder_path, &file_part, NULL)) /* NULL-GError */
1449     {
1450       folder_path = gtk_file_path_copy (chooser_entry->base_folder);
1451       file_part = g_strdup ("");
1452       file_part_pos = -1;
1453     }
1454   else
1455     {
1456       file_part_len = strlen (file_part);
1457       total_len = strlen (text);
1458       if (total_len > file_part_len)
1459         file_part_pos = g_utf8_strlen (text, total_len - file_part_len);
1460       else
1461         file_part_pos = 0;
1462     }
1463
1464   g_free (text);
1465
1466   g_free (chooser_entry->file_part);
1467
1468   chooser_entry->file_part = file_part;
1469   chooser_entry->file_part_pos = file_part_pos;
1470
1471   reload_current_folder (chooser_entry, folder_path, file_part_pos == -1);
1472   gtk_file_path_free (folder_path);
1473 }
1474
1475 static void
1476 autocomplete (GtkFileChooserEntry *chooser_entry)
1477 {
1478   g_assert (chooser_entry->current_folder != NULL);
1479   g_assert (gtk_file_folder_is_finished_loading (chooser_entry->current_folder));
1480   g_assert (gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length);
1481
1482   append_common_prefix (chooser_entry, TRUE, FALSE);
1483 }
1484
1485 static void
1486 start_autocompletion (GtkFileChooserEntry *chooser_entry)
1487 {
1488   refresh_current_folder_and_file_part (chooser_entry, REFRESH_UP_TO_CURSOR_POSITION);
1489
1490   if (!chooser_entry->current_folder)
1491     {
1492       /* We don't beep or anything, since this is autocompletion - the user
1493        * didn't request any action explicitly.
1494        */
1495       return;
1496     }
1497
1498   if (gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
1499     autocomplete (chooser_entry);
1500   else
1501     chooser_entry->load_complete_action = LOAD_COMPLETE_AUTOCOMPLETE;
1502 }
1503
1504 static gboolean
1505 start_autocompletion_idle_handler (gpointer data)
1506 {
1507   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
1508
1509   start_autocompletion (chooser_entry);
1510
1511   chooser_entry->start_autocompletion_idle_id = 0;
1512
1513   return FALSE;
1514 }
1515
1516 static void
1517 install_start_autocompletion_idle (GtkFileChooserEntry *chooser_entry)
1518 {
1519   if (chooser_entry->start_autocompletion_idle_id != 0)
1520     return;
1521
1522   chooser_entry->start_autocompletion_idle_id = g_idle_add (start_autocompletion_idle_handler, chooser_entry);
1523 }
1524
1525 #ifdef G_OS_WIN32
1526 static gint
1527 insert_text_callback (GtkFileChooserEntry *chooser_entry,
1528                       const gchar         *new_text,
1529                       gint                 new_text_length,
1530                       gint                *position,
1531                       gpointer             user_data)
1532 {
1533   const gchar *colon = memchr (new_text, ':', new_text_length);
1534   gint i;
1535
1536   /* Disallow these characters altogether */
1537   for (i = 0; i < new_text_length; i++)
1538     {
1539       if (new_text[i] == '<' ||
1540           new_text[i] == '>' ||
1541           new_text[i] == '"' ||
1542           new_text[i] == '|' ||
1543           new_text[i] == '*' ||
1544           new_text[i] == '?')
1545         break;
1546     }
1547
1548   if (i < new_text_length ||
1549       /* Disallow entering text that would cause a colon to be anywhere except
1550        * after a drive letter.
1551        */
1552       (colon != NULL &&
1553        *position + (colon - new_text) != 1) ||
1554       (new_text_length > 0 &&
1555        *position <= 1 &&
1556        GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1557        gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':'))
1558     {
1559       gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
1560       g_signal_stop_emission_by_name (chooser_entry, "insert_text");
1561       return FALSE;
1562     }
1563
1564   return TRUE;
1565 }
1566
1567 static void
1568 delete_text_callback (GtkFileChooserEntry *chooser_entry,
1569                       gint                 start_pos,
1570                       gint                 end_pos,
1571                       gpointer             user_data)
1572 {
1573   /* If deleting a drive letter, delete the colon, too */
1574   if (start_pos == 0 && end_pos == 1 &&
1575       GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1576       gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':')
1577     {
1578       g_signal_handlers_block_by_func (chooser_entry,
1579                                        G_CALLBACK (delete_text_callback),
1580                                        user_data);
1581       gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), 0, 1);
1582       g_signal_handlers_unblock_by_func (chooser_entry,
1583                                          G_CALLBACK (delete_text_callback),
1584                                          user_data);
1585     }
1586 }
1587 #endif
1588
1589 /**
1590  * _gtk_file_chooser_entry_new:
1591  * @eat_tabs: If %FALSE, allow focus navigation with the tab key.
1592  *
1593  * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
1594  * is an internal implementation widget for the GTK+ file chooser
1595  * which is an entry with completion with respect to a
1596  * #GtkFileSystem object.
1597  *
1598  * Return value: the newly created #GtkFileChooserEntry
1599  **/
1600 GtkWidget *
1601 _gtk_file_chooser_entry_new (gboolean eat_tabs)
1602 {
1603   GtkFileChooserEntry *chooser_entry;
1604
1605   chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
1606   chooser_entry->eat_tabs = (eat_tabs != FALSE);
1607
1608   return GTK_WIDGET (chooser_entry);
1609 }
1610
1611 /**
1612  * _gtk_file_chooser_entry_set_file_system:
1613  * @chooser_entry: a #GtkFileChooser
1614  * @file_system: an object implementing #GtkFileSystem
1615  *
1616  * Sets the file system for @chooser_entry.
1617  **/
1618 void
1619 _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry,
1620                                          GtkFileSystem       *file_system)
1621 {
1622   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1623   g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system));
1624
1625   if (file_system != chooser_entry->file_system)
1626     {
1627       if (chooser_entry->file_system)
1628         g_object_unref (chooser_entry->file_system);
1629
1630       chooser_entry->file_system = g_object_ref (file_system);
1631     }
1632 }
1633
1634 /**
1635  * _gtk_file_chooser_entry_set_base_folder:
1636  * @chooser_entry: a #GtkFileChooserEntry
1637  * @path: path of a folder in the chooser entries current file system.
1638  *
1639  * Sets the folder with respect to which completions occur.
1640  **/
1641 void
1642 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
1643                                          const GtkFilePath   *path)
1644 {
1645   if (chooser_entry->base_folder)
1646     gtk_file_path_free (chooser_entry->base_folder);
1647
1648   chooser_entry->base_folder = gtk_file_path_copy (path);
1649
1650   clear_completions (chooser_entry);
1651   _gtk_file_chooser_entry_select_filename (chooser_entry);
1652 }
1653
1654 /**
1655  * _gtk_file_chooser_entry_get_current_folder:
1656  * @chooser_entry: a #GtkFileChooserEntry
1657  *
1658  * Gets the current folder for the #GtkFileChooserEntry. If the
1659  * user has only entered a filename, this will be the base folder
1660  * (see _gtk_file_chooser_entry_set_base_folder()), but if the
1661  * user has entered a relative or absolute path, then it will
1662  * be different. If the user has entered a relative or absolute
1663  * path that doesn't point to a folder in the file system, it will
1664  * be %NULL.
1665  *
1666  * Return value: the path of current folder - this value is owned by the
1667  *  chooser entry and must not be modified or freed.
1668  **/
1669 const GtkFilePath *
1670 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
1671 {
1672   commit_completion_and_refresh (chooser_entry);
1673   return chooser_entry->current_folder_path;
1674 }
1675
1676 /**
1677  * _gtk_file_chooser_entry_get_file_part:
1678  * @chooser_entry: a #GtkFileChooserEntry
1679  *
1680  * Gets the non-folder portion of whatever the user has entered
1681  * into the file selector. What is returned is a UTF-8 string,
1682  * and if a filename path is needed, gtk_file_system_make_path()
1683  * must be used
1684   *
1685  * Return value: the entered filename - this value is owned by the
1686  *  chooser entry and must not be modified or freed.
1687  **/
1688 const gchar *
1689 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
1690 {
1691   commit_completion_and_refresh (chooser_entry);
1692   return chooser_entry->file_part;
1693 }
1694
1695 /**
1696  * _gtk_file_chooser_entry_set_file_part:
1697  * @chooser_entry: a #GtkFileChooserEntry
1698  * @file_part: text to display in the entry, in UTF-8
1699  *
1700  * Sets the current text shown in the file chooser entry.
1701  **/
1702 void
1703 _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry,
1704                                        const gchar         *file_part)
1705 {
1706   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1707
1708   chooser_entry->in_change = TRUE;
1709   clear_completions (chooser_entry);
1710   gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part);
1711   chooser_entry->in_change = FALSE;
1712 }
1713
1714
1715 /**
1716  * _gtk_file_chooser_entry_set_action:
1717  * @chooser_entry: a #GtkFileChooserEntry
1718  * @action: the action which is performed by the file selector using this entry
1719  *
1720  * Sets action which is performed by the file selector using this entry. 
1721  * The #GtkFileChooserEntry will use different completion strategies for 
1722  * different actions.
1723  **/
1724 void
1725 _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry,
1726                                     GtkFileChooserAction action)
1727 {
1728   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1729   
1730   if (chooser_entry->action != action)
1731     {
1732       GtkEntryCompletion *comp;
1733
1734       chooser_entry->action = action;
1735
1736       comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
1737
1738       /* FIXME: do we need to actually set the following? */
1739
1740       switch (action)
1741         {
1742         case GTK_FILE_CHOOSER_ACTION_OPEN:
1743         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1744           gtk_entry_completion_set_popup_single_match (comp, FALSE);
1745           break;
1746         case GTK_FILE_CHOOSER_ACTION_SAVE:
1747         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1748           gtk_entry_completion_set_popup_single_match (comp, TRUE);
1749           break;
1750         }
1751     }
1752 }
1753
1754
1755 /**
1756  * _gtk_file_chooser_entry_get_action:
1757  * @chooser_entry: a #GtkFileChooserEntry
1758  *
1759  * Gets the action for this entry. 
1760  *
1761  * Returns: the action
1762  **/
1763 GtkFileChooserAction
1764 _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry)
1765 {
1766   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry),
1767                         GTK_FILE_CHOOSER_ACTION_OPEN);
1768   
1769   return chooser_entry->action;
1770 }
1771
1772 gboolean
1773 _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry,
1774                                        const GtkFilePath   *path)
1775 {
1776   gboolean retval = FALSE;
1777
1778   if (chooser_entry->current_folder)
1779     {
1780       GtkFileInfo *file_info;
1781
1782       file_info = gtk_file_folder_get_info (chooser_entry->current_folder,
1783                                             path, NULL);
1784       if (file_info)
1785         {
1786           retval = gtk_file_info_get_is_folder (file_info);
1787           gtk_file_info_free (file_info);
1788         }
1789     }
1790
1791   return retval;
1792 }
1793
1794
1795 /*
1796  * _gtk_file_chooser_entry_select_filename:
1797  * @chooser_entry: a #GtkFileChooserEntry
1798  *
1799  * Selects the filename (without the extension) for user edition.
1800  */
1801 void
1802 _gtk_file_chooser_entry_select_filename (GtkFileChooserEntry *chooser_entry)
1803 {
1804   const gchar *str, *ext;
1805   glong len = -1;
1806
1807   if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE)
1808     {
1809       str = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
1810       ext = g_strrstr (str, ".");
1811
1812       if (ext)
1813        len = g_utf8_pointer_to_offset (str, ext);
1814     }
1815
1816   gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, (gint) len);
1817 }
1818