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