]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserentry.c
Apply a cleanup patch by Kjartan Maraas (#341812)
[~andy/gtk] / gtk / gtkfilechooserentry.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilechooserentry.c: Entry with filename completion
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include <config.h>
22 #include <string.h>
23
24 #include "gtkcelllayout.h"
25 #include "gtkcellrenderertext.h"
26 #include "gtkentry.h"
27 #include "gtkfilechooserentry.h"
28 #include "gtkmain.h"
29 #include "gtkintl.h"
30 #include "gtkalias.h"
31
32 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
33
34 #define GTK_FILE_CHOOSER_ENTRY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
35 #define GTK_IS_FILE_CHOOSER_ENTRY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY))
36 #define GTK_FILE_CHOOSER_ENTRY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
37
38 struct _GtkFileChooserEntryClass
39 {
40   GtkEntryClass parent_class;
41 };
42
43 struct _GtkFileChooserEntry
44 {
45   GtkEntry parent_instance;
46
47   GtkFileChooserAction action;
48
49   GtkFileSystem *file_system;
50   GtkFilePath *base_folder;
51   GtkFilePath *current_folder_path;
52   gchar *file_part;
53   gint file_part_pos;
54   guint check_completion_idle;
55   guint load_directory_idle;
56
57   GtkFileFolder *current_folder;
58   GtkFileSystemHandle *load_folder_handle;
59
60   GtkListStore *completion_store;
61
62   guint has_completion : 1;
63   guint in_change      : 1;
64   guint eat_tabs       : 1;
65 };
66
67 enum
68 {
69   DISPLAY_NAME_COLUMN,
70   PATH_COLUMN,
71   N_COLUMNS
72 };
73
74 static void     gtk_file_chooser_entry_iface_init     (GtkEditableClass *iface);
75
76 static void     gtk_file_chooser_entry_finalize       (GObject          *object);
77 static void     gtk_file_chooser_entry_dispose        (GObject          *object);
78 static gboolean gtk_file_chooser_entry_focus          (GtkWidget        *widget,
79                                                        GtkDirectionType  direction);
80 static void     gtk_file_chooser_entry_activate       (GtkEntry         *entry);
81 static void     gtk_file_chooser_entry_changed        (GtkEditable      *editable);
82 static void     gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
83                                                        const gchar *new_text,
84                                                        gint         new_text_length,
85                                                        gint        *position);
86
87 static void     clear_completion_callback (GtkFileChooserEntry *chooser_entry,
88                                            GParamSpec          *pspec);
89 static gboolean match_selected_callback   (GtkEntryCompletion  *completion,
90                                            GtkTreeModel        *model,
91                                            GtkTreeIter         *iter,
92                                            GtkFileChooserEntry *chooser_entry);
93 static gboolean completion_match_func     (GtkEntryCompletion  *comp,
94                                            const char          *key,
95                                            GtkTreeIter         *iter,
96                                            gpointer             data);
97 static void     files_added_cb            (GtkFileSystem       *file_system,
98                                            GSList              *added_uris,
99                                            GtkFileChooserEntry *chooser_entry);
100 static void     files_deleted_cb          (GtkFileSystem       *file_system,
101                                            GSList              *deleted_uris,
102                                            GtkFileChooserEntry *chooser_entry);
103 static char    *maybe_append_separator_to_path (GtkFileChooserEntry *chooser_entry,
104                                                 GtkFilePath         *path,
105                                                 gchar               *display_name);
106
107 static GtkEditableClass *parent_editable_iface;
108
109 G_DEFINE_TYPE_WITH_CODE (GtkFileChooserEntry, _gtk_file_chooser_entry, GTK_TYPE_ENTRY,
110                          G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
111                                                 gtk_file_chooser_entry_iface_init))
112
113 static void
114 _gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class)
115 {
116   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
117   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
118   GtkEntryClass *entry_class = GTK_ENTRY_CLASS (class);
119
120   gobject_class->finalize = gtk_file_chooser_entry_finalize;
121   gobject_class->dispose = gtk_file_chooser_entry_dispose;
122
123   widget_class->focus = gtk_file_chooser_entry_focus;
124
125   entry_class->activate = gtk_file_chooser_entry_activate;
126 }
127
128 static void
129 gtk_file_chooser_entry_iface_init (GtkEditableClass *iface)
130 {
131   parent_editable_iface = g_type_interface_peek_parent (iface);
132
133   iface->do_insert_text = gtk_file_chooser_entry_do_insert_text;
134   iface->changed = gtk_file_chooser_entry_changed;
135 }
136
137 static void
138 _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
139 {
140   GtkEntryCompletion *comp;
141   GtkCellRenderer *cell;
142
143   g_object_set (chooser_entry, "truncate-multiline", TRUE, NULL);
144
145   comp = gtk_entry_completion_new ();
146   gtk_entry_completion_set_popup_single_match (comp, FALSE);
147
148   gtk_entry_completion_set_match_func (comp,
149                                        completion_match_func,
150                                        chooser_entry,
151                                        NULL);
152
153   cell = gtk_cell_renderer_text_new ();
154   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (comp),
155                               cell, TRUE);
156   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (comp),
157                                  cell,
158                                  "text", 0);
159
160   g_signal_connect (comp, "match_selected",
161                     G_CALLBACK (match_selected_callback), chooser_entry);
162
163   gtk_entry_set_completion (GTK_ENTRY (chooser_entry), comp);
164   g_object_unref (comp);
165
166   g_signal_connect (chooser_entry, "notify::cursor-position",
167                     G_CALLBACK (clear_completion_callback), NULL);
168   g_signal_connect (chooser_entry, "notify::selection-bound",
169                     G_CALLBACK (clear_completion_callback), NULL);
170 }
171
172 static void
173 gtk_file_chooser_entry_finalize (GObject *object)
174 {
175   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
176
177   gtk_file_path_free (chooser_entry->base_folder);
178   gtk_file_path_free (chooser_entry->current_folder_path);
179   g_free (chooser_entry->file_part);
180
181   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->finalize (object);
182 }
183
184 static void
185 gtk_file_chooser_entry_dispose (GObject *object)
186 {
187   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
188
189   if (chooser_entry->completion_store)
190     {
191       g_object_unref (chooser_entry->completion_store);
192       chooser_entry->completion_store = NULL;
193     }
194
195   if (chooser_entry->load_folder_handle)
196     {
197       gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
198       chooser_entry->load_folder_handle = NULL;
199     }
200
201   if (chooser_entry->current_folder)
202     {
203       g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
204                                             G_CALLBACK (files_added_cb), chooser_entry);
205       g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
206                                             G_CALLBACK (files_deleted_cb), chooser_entry);
207       g_object_unref (chooser_entry->current_folder);
208       chooser_entry->current_folder = NULL;
209     }
210
211   if (chooser_entry->file_system)
212     {
213       g_object_unref (chooser_entry->file_system);
214       chooser_entry->file_system = NULL;
215     }
216
217   if (chooser_entry->check_completion_idle)
218     {
219       g_source_remove (chooser_entry->check_completion_idle);
220       chooser_entry->check_completion_idle = 0;
221     }
222
223   if (chooser_entry->load_directory_idle)
224     {
225       g_source_remove (chooser_entry->load_directory_idle);
226       chooser_entry->load_directory_idle = 0;
227     }
228
229   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object);
230 }
231
232 /* Match functions for the GtkEntryCompletion */
233 static gboolean
234 match_selected_callback (GtkEntryCompletion  *completion,
235                          GtkTreeModel        *model,
236                          GtkTreeIter         *iter,
237                          GtkFileChooserEntry *chooser_entry)
238 {
239   char *display_name;
240   GtkFilePath *path;
241   gint pos;
242   
243   gtk_tree_model_get (model, iter,
244                       DISPLAY_NAME_COLUMN, &display_name,
245                       PATH_COLUMN, &path,
246                       -1);
247
248   if (!display_name || !path)
249     {
250       /* these shouldn't complain if passed NULL */
251       gtk_file_path_free (path);
252       g_free (display_name);
253       return FALSE;
254     }
255
256   display_name = maybe_append_separator_to_path (chooser_entry, path, display_name);
257
258   pos = chooser_entry->file_part_pos;
259
260   /* We don't set in_change here as we want to update the current_folder
261    * variable */
262   gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
263                             pos, -1);
264   gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
265                             display_name, -1, 
266                             &pos);
267   gtk_editable_set_position (GTK_EDITABLE (chooser_entry), -1);
268
269   gtk_file_path_free (path);
270   g_free (display_name);
271
272   return TRUE;
273 }
274
275 /* Match function for the GtkEntryCompletion */
276 static gboolean
277 completion_match_func (GtkEntryCompletion *comp,
278                        const char         *key_unused,
279                        GtkTreeIter        *iter,
280                        gpointer            data)
281 {
282   GtkFileChooserEntry *chooser_entry;
283   char *name;
284   gboolean result;
285   char *norm_file_part;
286   char *norm_name;
287
288   chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
289
290   /* We ignore the key because it is the contents of the entry.  Instead, we
291    * just use our precomputed file_part.
292    */
293   if (!chooser_entry->file_part)
294     {
295       return FALSE;
296     }
297
298   gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store), iter, DISPLAY_NAME_COLUMN, &name, -1);
299   if (!name)
300     {
301       return FALSE; /* Uninitialized row, ugh */
302     }
303
304   /* If we have an empty file_part, then we're at the root of a directory.  In
305    * that case, we want to match all non-dot files.  We might want to match
306    * dot_files too if show_hidden is TRUE on the fileselector in the future.
307    */
308   /* Additionally, support for gnome .hidden files would be sweet, too */
309   if (chooser_entry->file_part[0] == '\000')
310     {
311       if (name[0] == '.')
312         result = FALSE;
313       else
314         result = TRUE;
315       g_free (name);
316
317       return result;
318     }
319
320
321   norm_file_part = g_utf8_normalize (chooser_entry->file_part, -1, G_NORMALIZE_ALL);
322   norm_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
323
324 #ifdef G_PLATFORM_WIN32
325   {
326     gchar *temp;
327
328     temp = norm_file_part;
329     norm_file_part = g_utf8_casefold (norm_file_part, -1);
330     g_free (temp);
331
332     temp = norm_name;
333     norm_name = g_utf8_casefold (norm_name, -1);
334     g_free (temp);
335   }
336 #endif
337
338   result = (strncmp (norm_file_part, norm_name, strlen (norm_file_part)) == 0);
339
340   g_free (norm_file_part);
341   g_free (norm_name);
342   g_free (name);
343   
344   return result;
345 }
346
347 /* This function will append a directory separator to paths to
348  * display_name iff the path associated with it is a directory.
349  * maybe_append_separator_to_path will g_free the display_name and
350  * return a new one if needed.  Otherwise, it will return the old one.
351  * You should be safe calling
352  *
353  * display_name = maybe_append_separator_to_path (entry, path, display_name);
354  * ...
355  * g_free (display_name);
356  */
357 static char *
358 maybe_append_separator_to_path (GtkFileChooserEntry *chooser_entry,
359                                 GtkFilePath         *path,
360                                 gchar               *display_name)
361 {
362   if (path)
363     {
364       GtkFileInfo *info;
365             
366       info = gtk_file_folder_get_info (chooser_entry->current_folder,
367                                        path, NULL); /* NULL-GError */
368
369       if (info)
370         {
371           if (gtk_file_info_get_is_folder (info))
372             {
373               gchar *tmp = display_name;
374               display_name = g_strconcat (tmp, G_DIR_SEPARATOR_S, NULL);
375               g_free (tmp);
376             }
377           
378           gtk_file_info_free (info);
379         }
380
381     }
382
383   return display_name;
384 }
385
386 /* Determines if the completion model has entries with a common prefix relative
387  * to the current contents of the entry.  Also, if there's one and only one such
388  * path, stores it in unique_path_ret.
389  */
390 static void
391 find_common_prefix (GtkFileChooserEntry *chooser_entry,
392                     gchar               **common_prefix_ret,
393                     GtkFilePath         **unique_path_ret)
394 {
395   GtkTreeIter iter;
396   gboolean valid;
397
398   *common_prefix_ret = NULL;
399   *unique_path_ret = NULL;
400
401   if (chooser_entry->completion_store == NULL)
402     return;
403
404   valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
405
406   while (valid)
407     {
408       gchar *display_name;
409       GtkFilePath *path;
410
411       gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store),
412                           &iter,
413                           DISPLAY_NAME_COLUMN, &display_name,
414                           PATH_COLUMN, &path,
415                           -1);
416
417       if (g_str_has_prefix (display_name, chooser_entry->file_part))
418         {
419           if (!*common_prefix_ret)
420             {
421               *common_prefix_ret = g_strdup (display_name);
422               *unique_path_ret = gtk_file_path_copy (path);
423             }
424           else
425             {
426               gchar *p = *common_prefix_ret;
427               const gchar *q = display_name;
428                   
429               while (*p && *p == *q)
430                 {
431                   p++;
432                   q++;
433                 }
434                   
435               *p = '\0';
436
437               gtk_file_path_free (*unique_path_ret);
438               *unique_path_ret = NULL;
439             }
440         }
441
442       g_free (display_name);
443       gtk_file_path_free (path);
444       valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter);
445     }
446 }
447
448 /* Finds a common prefix based on the contents of the entry and mandatorily appends it */
449 static void
450 append_common_prefix (GtkFileChooserEntry *chooser_entry,
451                       gboolean             highlight)
452 {
453   gchar *common_prefix;
454   GtkFilePath *unique_path;
455
456   find_common_prefix (chooser_entry, &common_prefix, &unique_path);
457
458   if (unique_path)
459     {
460       common_prefix = maybe_append_separator_to_path (chooser_entry,
461                                                       unique_path,
462                                                       common_prefix);
463       gtk_file_path_free (unique_path);
464     }
465
466   if (common_prefix)
467     {
468       gint file_part_len;
469       gint common_prefix_len;
470       gint pos;
471
472       file_part_len = g_utf8_strlen (chooser_entry->file_part, -1);
473       common_prefix_len = g_utf8_strlen (common_prefix, -1);
474
475       if (common_prefix_len > file_part_len)
476         {
477           pos = chooser_entry->file_part_pos;
478
479           chooser_entry->in_change = TRUE;
480           gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
481                                     pos, -1);
482           gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
483                                     common_prefix, -1, 
484                                     &pos);
485           chooser_entry->in_change = FALSE;
486
487           if (highlight)
488             {
489               gtk_editable_select_region (GTK_EDITABLE (chooser_entry),
490                                           chooser_entry->file_part_pos + file_part_len,
491                                           chooser_entry->file_part_pos + common_prefix_len);
492               chooser_entry->has_completion = TRUE;
493             }
494         }
495
496       g_free (common_prefix);
497     }
498 }
499
500 static gboolean
501 check_completion_callback (GtkFileChooserEntry *chooser_entry)
502 {
503   GDK_THREADS_ENTER ();
504
505   g_assert (chooser_entry->file_part);
506
507   chooser_entry->check_completion_idle = 0;
508
509   if (strcmp (chooser_entry->file_part, "") == 0)
510     goto done;
511
512   /* We only insert the common prefix without requiring the user to hit Tab in
513    * the "open" modes.  For "save" modes, the user must hit Tab to cause the prefix
514    * to be inserted.  That happens in gtk_file_chooser_entry_focus().
515    */
516   if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN
517       || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
518     append_common_prefix (chooser_entry, TRUE);
519
520  done:
521
522   GDK_THREADS_LEAVE ();
523
524   return FALSE;
525 }
526
527 static guint
528 idle_add (GtkFileChooserEntry *chooser_entry, 
529           GCallback            cb)
530 {
531   GSource *source;
532   guint id;
533
534   source = g_idle_source_new ();
535   g_source_set_priority (source, G_PRIORITY_HIGH);
536   g_source_set_closure (source,
537                         g_cclosure_new_object (cb, G_OBJECT (chooser_entry)));
538   id = g_source_attach (source, NULL);
539   g_source_unref (source);
540
541   return id;
542 }
543
544 static void
545 add_completion_idle (GtkFileChooserEntry *chooser_entry)
546 {
547   /* idle to update the selection based on the file list */
548   if (chooser_entry->check_completion_idle == 0)
549     chooser_entry->check_completion_idle = 
550       idle_add (chooser_entry, G_CALLBACK (check_completion_callback));
551 }
552
553
554 static void
555 update_current_folder_files (GtkFileChooserEntry *chooser_entry,
556                              GSList              *added_uris)
557 {
558   GSList *tmp_list;
559
560   g_assert (chooser_entry->completion_store != NULL);
561
562   /* Bah.  Need to turn off sorting */
563   for (tmp_list = added_uris; tmp_list; tmp_list = tmp_list->next)
564     {
565       GtkFileInfo *info;
566       GtkFilePath *path;
567
568       path = tmp_list->data;
569
570       info = gtk_file_folder_get_info (chooser_entry->current_folder,
571                                        path,
572                                        NULL); /* NULL-GError */
573       if (info)
574         {
575           const gchar *display_name = gtk_file_info_get_display_name (info);
576           GtkTreeIter iter;
577
578           gtk_list_store_append (chooser_entry->completion_store, &iter);
579           gtk_list_store_set (chooser_entry->completion_store, &iter,
580                               DISPLAY_NAME_COLUMN, display_name,
581                               PATH_COLUMN, path,
582                               -1);
583
584           gtk_file_info_free (info);
585         }
586     }
587
588   /* FIXME: we want to turn off sorting temporarily.  I suck... */
589   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store),
590                                         DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING);
591
592   add_completion_idle (chooser_entry);
593 }
594
595 static void
596 files_added_cb (GtkFileSystem       *file_system,
597                 GSList              *added_uris,
598                 GtkFileChooserEntry *chooser_entry)
599 {
600   update_current_folder_files (chooser_entry, added_uris);
601 }
602
603 static void
604 files_deleted_cb (GtkFileSystem       *file_system,
605                   GSList              *deleted_uris,
606                   GtkFileChooserEntry *chooser_entry)
607 {
608   /* FIXME: gravy... */
609 }
610
611 static void
612 load_directory_get_folder_callback (GtkFileSystemHandle *handle,
613                                     GtkFileFolder       *folder,
614                                     const GError        *error,
615                                     gpointer             data)
616 {
617   gboolean cancelled = handle->cancelled;
618   GtkFileChooserEntry *chooser_entry = data;
619
620   if (handle != chooser_entry->load_folder_handle)
621     goto out;
622
623   chooser_entry->load_folder_handle = NULL;
624
625   if (cancelled || error)
626     goto out;
627
628   chooser_entry->current_folder = folder;
629   g_signal_connect (chooser_entry->current_folder, "files-added",
630                     G_CALLBACK (files_added_cb), chooser_entry);
631   g_signal_connect (chooser_entry->current_folder, "files-removed",
632                     G_CALLBACK (files_deleted_cb), chooser_entry);
633   
634   chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS,
635                                                         G_TYPE_STRING,
636                                                         GTK_TYPE_FILE_PATH);
637
638   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)),
639                                   GTK_TREE_MODEL (chooser_entry->completion_store));
640
641 out:
642   g_object_unref (chooser_entry);
643   g_object_unref (handle);
644 }
645
646 static gboolean
647 load_directory_callback (GtkFileChooserEntry *chooser_entry)
648 {
649   GDK_THREADS_ENTER ();
650
651   chooser_entry->load_directory_idle = 0;
652
653   /* guard against bogus settings*/
654   if (chooser_entry->current_folder_path == NULL ||
655       chooser_entry->file_system == NULL)
656     goto done;
657
658   if (chooser_entry->current_folder != NULL)
659     {
660       g_warning ("idle activate multiple times without clearing the folder object first.");
661       goto done;
662     }
663   g_assert (chooser_entry->completion_store == NULL);
664
665   /* Load the folder */
666   if (chooser_entry->load_folder_handle)
667     gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
668
669   chooser_entry->load_folder_handle =
670     gtk_file_system_get_folder (chooser_entry->file_system,
671                                 chooser_entry->current_folder_path,
672                                 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER,
673                                 load_directory_get_folder_callback,
674                                 g_object_ref (chooser_entry));
675
676  done:
677   
678   GDK_THREADS_LEAVE ();
679
680   return FALSE;
681 }
682
683 static void
684 gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
685                                        const gchar *new_text,
686                                        gint         new_text_length,
687                                        gint        *position)
688 {
689   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
690
691   parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position);
692
693   if (! chooser_entry->in_change)
694     add_completion_idle (GTK_FILE_CHOOSER_ENTRY (editable));
695 }
696
697 static gboolean
698 gtk_file_chooser_entry_focus (GtkWidget        *widget,
699                               GtkDirectionType  direction)
700 {
701   GtkFileChooserEntry *chooser_entry;
702   GtkEditable *editable;
703   GtkEntry *entry;
704   GdkModifierType state;
705   gboolean control_pressed;
706
707   chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
708   editable = GTK_EDITABLE (widget);
709   entry = GTK_ENTRY (widget);
710
711   if (!chooser_entry->eat_tabs)
712     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
713
714   control_pressed = FALSE;
715
716   if (gtk_get_current_event_state (&state))
717     {
718       if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
719         control_pressed = TRUE;
720     }
721
722   /* This is a bit evil -- it makes Tab never leave the entry. It basically
723    * makes it 'safe' for people to hit. */
724   if ((direction == GTK_DIR_TAB_FORWARD) &&
725       (GTK_WIDGET_HAS_FOCUS (widget)) &&
726       (! control_pressed))
727     {
728       gint pos = 0;
729
730       if (!chooser_entry->has_completion
731           && gtk_editable_get_position (editable) == entry->text_length)
732         append_common_prefix (chooser_entry, FALSE);
733
734       gtk_editable_set_position (editable, entry->text_length);
735
736       /* Trigger the completion window to pop up again by a 
737        * zero-length insertion, a bit of a hack.
738        */
739       gtk_editable_insert_text (editable, "", -1, &pos);
740
741       return TRUE;
742     }
743   else
744     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
745 }
746
747 static void
748 gtk_file_chooser_entry_activate (GtkEntry *entry)
749 {
750   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry);
751
752   if (chooser_entry->has_completion)
753     {
754       gtk_editable_set_position (GTK_EDITABLE (entry),
755                                  entry->text_length);
756     }
757   
758   GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry);
759 }
760
761 /* This will see if a path typed by the user is new, and installs the loading
762  * idle if it is.
763  */
764 static void
765 gtk_file_chooser_entry_maybe_update_directory (GtkFileChooserEntry *chooser_entry,
766                                                GtkFilePath         *folder_path,
767                                                gboolean             force_reload)
768 {
769   gboolean queue_idle = FALSE;
770
771   if (chooser_entry->current_folder_path)
772     {
773       if (gtk_file_path_compare (folder_path, chooser_entry->current_folder_path) != 0 || force_reload)
774         {
775           /* We changed our current directory.  We need to clear out the old
776            * directory information.
777            */
778           if (chooser_entry->current_folder)
779             {
780               g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
781                                                     G_CALLBACK (files_added_cb), chooser_entry);
782               g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
783                                                     G_CALLBACK (files_deleted_cb), chooser_entry);
784
785               g_object_unref (chooser_entry->current_folder);
786               chooser_entry->current_folder = NULL;
787             }
788           if (chooser_entry->completion_store)
789             {
790               gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);
791               g_object_unref (chooser_entry->completion_store);
792               chooser_entry->completion_store = NULL;
793             }
794
795           queue_idle = TRUE;
796         }
797
798       gtk_file_path_free (chooser_entry->current_folder_path);
799     }
800   else
801     {
802       queue_idle = TRUE;
803     }
804
805   chooser_entry->current_folder_path = folder_path;
806
807   if (queue_idle && chooser_entry->load_directory_idle == 0)
808     chooser_entry->load_directory_idle =
809       idle_add (chooser_entry, G_CALLBACK (load_directory_callback));
810 }
811
812
813
814 static void
815 gtk_file_chooser_entry_changed (GtkEditable *editable)
816 {
817   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
818   const gchar *text;
819   GtkFilePath *folder_path;
820   gchar *file_part;
821   gsize total_len, file_part_len;
822   gint file_part_pos;
823
824   if (chooser_entry->in_change)
825     return;
826
827   text = gtk_entry_get_text (GTK_ENTRY (editable));
828   
829   if (!chooser_entry->file_system ||
830       !chooser_entry->base_folder ||
831       !gtk_file_system_parse (chooser_entry->file_system,
832                               chooser_entry->base_folder, text,
833                               &folder_path, &file_part, NULL)) /* NULL-GError */
834     {
835       folder_path = gtk_file_path_copy (chooser_entry->base_folder);
836       file_part = g_strdup ("");
837       file_part_pos = -1;
838     }
839   else
840     {
841       file_part_len = strlen (file_part);
842       total_len = strlen (text);
843       if (total_len > file_part_len)
844         file_part_pos = g_utf8_strlen (text, total_len - file_part_len);
845       else
846         file_part_pos = 0;
847     }
848
849   if (chooser_entry->file_part)
850     g_free (chooser_entry->file_part);
851
852   chooser_entry->file_part = file_part;
853   chooser_entry->file_part_pos = file_part_pos;
854
855   gtk_file_chooser_entry_maybe_update_directory (chooser_entry, folder_path, file_part_pos == -1);
856 }
857
858 static void
859 clear_completion_callback (GtkFileChooserEntry *chooser_entry,
860                            GParamSpec          *pspec)
861 {
862   if (chooser_entry->has_completion)
863     {
864       chooser_entry->has_completion = FALSE;
865       gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
866     }
867 }
868
869 /**
870  * _gtk_file_chooser_entry_new:
871  * @eat_tabs: If %FALSE, allow focus navigation with the tab key.
872  *
873  * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
874  * is an internal implementation widget for the GTK+ file chooser
875  * which is an entry with completion with respect to a
876  * #GtkFileSystem object.
877  *
878  * Return value: the newly created #GtkFileChooserEntry
879  **/
880 GtkWidget *
881 _gtk_file_chooser_entry_new (gboolean eat_tabs)
882 {
883   GtkFileChooserEntry *chooser_entry;
884
885   chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
886   chooser_entry->eat_tabs = (eat_tabs != FALSE);
887
888   return GTK_WIDGET (chooser_entry);
889 }
890
891 /**
892  * _gtk_file_chooser_entry_set_file_system:
893  * @chooser_entry: a #GtkFileChooser
894  * @file_system: an object implementing #GtkFileSystem
895  *
896  * Sets the file system for @chooser_entry.
897  **/
898 void
899 _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry,
900                                          GtkFileSystem       *file_system)
901 {
902   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
903   g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system));
904
905   if (file_system != chooser_entry->file_system)
906     {
907       if (chooser_entry->file_system)
908         g_object_unref (chooser_entry->file_system);
909
910       chooser_entry->file_system = g_object_ref (file_system);
911     }
912 }
913
914 /**
915  * _gtk_file_chooser_entry_set_base_folder:
916  * @chooser_entry: a #GtkFileChooserEntry
917  * @path: path of a folder in the chooser entries current file system.
918  *
919  * Sets the folder with respect to which completions occur.
920  **/
921 void
922 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
923                                          const GtkFilePath   *path)
924 {
925   if (chooser_entry->base_folder)
926     gtk_file_path_free (chooser_entry->base_folder);
927
928   chooser_entry->base_folder = gtk_file_path_copy (path);
929
930   gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
931   gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, -1);
932 }
933
934 /**
935  * _gtk_file_chooser_entry_get_current_folder:
936  * @chooser_entry: a #GtkFileChooserEntry
937  *
938  * Gets the current folder for the #GtkFileChooserEntry. If the
939  * user has only entered a filename, this will be the base folder
940  * (see _gtk_file_chooser_entry_set_base_folder()), but if the
941  * user has entered a relative or absolute path, then it will
942  * be different. If the user has entered a relative or absolute
943  * path that doesn't point to a folder in the file system, it will
944  * be %NULL.
945  *
946  * Return value: the path of current folder - this value is owned by the
947  *  chooser entry and must not be modified or freed.
948  **/
949 const GtkFilePath *
950 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
951 {
952   if (chooser_entry->has_completion)
953     {
954       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
955                                  GTK_ENTRY (chooser_entry)->text_length);
956     }
957   return chooser_entry->current_folder_path;
958 }
959
960 /**
961  * _gtk_file_chooser_entry_get_file_part:
962  * @chooser_entry: a #GtkFileChooserEntry
963  *
964  * Gets the non-folder portion of whatever the user has entered
965  * into the file selector. What is returned is a UTF-8 string,
966  * and if a filename path is needed, gtk_file_system_make_path()
967  * must be used
968   *
969  * Return value: the entered filename - this value is owned by the
970  *  chooser entry and must not be modified or freed.
971  **/
972 const gchar *
973 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
974 {
975   if (chooser_entry->has_completion)
976     {
977       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
978                                  GTK_ENTRY (chooser_entry)->text_length);
979     }
980   return chooser_entry->file_part;
981 }
982
983 /**
984  * _gtk_file_chooser_entry_set_file_part:
985  * @chooser_entry: a #GtkFileChooserEntry
986  * @file_part: text to display in the entry, in UTF-8
987  *
988  * Sets the current text shown in the file chooser entry.
989  **/
990 void
991 _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry,
992                                        const gchar         *file_part)
993 {
994   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
995
996   gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part);
997 }
998
999
1000 /**
1001  * _gtk_file_chooser_entry_set_action:
1002  * @chooser_entry: a #GtkFileChooserEntry
1003  * @action: the action which is performed by the file selector using this entry
1004  *
1005  * Sets action which is performed by the file selector using this entry. 
1006  * The #GtkFileChooserEntry will use different completion strategies for 
1007  * different actions.
1008  **/
1009 void
1010 _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry,
1011                                     GtkFileChooserAction action)
1012 {
1013   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1014   
1015   if (chooser_entry->action != action)
1016     {
1017       GtkEntryCompletion *comp;
1018
1019       chooser_entry->action = action;
1020
1021       comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
1022
1023       switch (action)
1024         {
1025         case GTK_FILE_CHOOSER_ACTION_OPEN:
1026         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1027           gtk_entry_completion_set_popup_single_match (comp, FALSE);
1028           break;
1029         case GTK_FILE_CHOOSER_ACTION_SAVE:
1030         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1031           gtk_entry_completion_set_popup_single_match (comp, TRUE);
1032           break;
1033         }
1034     }
1035 }
1036
1037
1038 /**
1039  * _gtk_file_chooser_entry_get_action:
1040  * @chooser_entry: a #GtkFileChooserEntry
1041  *
1042  * Gets the action for this entry. 
1043  *
1044  * Returns: the action
1045  **/
1046 GtkFileChooserAction
1047 _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry)
1048 {
1049   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry),
1050                         GTK_FILE_CHOOSER_ACTION_OPEN);
1051   
1052   return chooser_entry->action;
1053 }
1054
1055 gboolean
1056 _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry,
1057                                        const GtkFilePath   *path)
1058 {
1059   gboolean retval = FALSE;
1060
1061   if (chooser_entry->current_folder)
1062     {
1063       GtkFileInfo *file_info;
1064
1065       file_info = gtk_file_folder_get_info (chooser_entry->current_folder,
1066                                             path, NULL);
1067       if (file_info)
1068         {
1069           retval = gtk_file_info_get_is_folder (file_info);
1070           gtk_file_info_free (file_info);
1071         }
1072     }
1073
1074   return retval;
1075 }