]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserentry.c
Debug printfs for the common prefix
[~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   printf ("common prefix: \"%s\"\n",
523           common_prefix ? common_prefix : "<NONE>");
524
525   if (common_prefix)
526     {
527       gint cursor_pos;
528       gint common_prefix_len;
529       gint pos;
530
531       cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry));
532       common_prefix_len = g_utf8_strlen (common_prefix, -1);
533
534       pos = chooser_entry->file_part_pos;
535
536       chooser_entry->in_change = TRUE;
537       printf ("Deleting range (%d, %d)\n", pos, cursor_pos);
538       gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
539                                 pos, cursor_pos);
540       printf ("Inserting common prefix at %d\n", pos);
541       gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
542                                 common_prefix, -1, 
543                                 &pos);
544       chooser_entry->in_change = FALSE;
545
546       if (highlight)
547         {
548           printf ("Selecting range (%d, %d)\n", cursor_pos, pos);
549           gtk_editable_select_region (GTK_EDITABLE (chooser_entry),
550                                       cursor_pos,
551                                       pos); /* cursor_pos + common_prefix_len); */
552           chooser_entry->has_completion = TRUE;
553         }
554       else
555         gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos);
556
557       g_free (common_prefix);
558     }
559 }
560
561 static gboolean
562 check_completion_callback (GtkFileChooserEntry *chooser_entry)
563 {
564   GDK_THREADS_ENTER ();
565
566   g_assert (chooser_entry->file_part);
567
568   chooser_entry->check_completion_idle = 0;
569
570   if (strcmp (chooser_entry->file_part, "") == 0)
571     goto done;
572
573   /* We only insert the common prefix without requiring the user to hit Tab in
574    * the "open" modes.  For "save" modes, the user must hit Tab to cause the prefix
575    * to be inserted.  That happens in gtk_file_chooser_entry_focus().
576    */
577   if ((chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN
578        || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
579       && gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length)
580     append_common_prefix (chooser_entry, TRUE);
581
582  done:
583
584   GDK_THREADS_LEAVE ();
585
586   return FALSE;
587 }
588
589 static guint
590 idle_add (GtkFileChooserEntry *chooser_entry, 
591           GCallback            cb)
592 {
593   GSource *source;
594   guint id;
595
596   source = g_idle_source_new ();
597   g_source_set_priority (source, G_PRIORITY_HIGH);
598   g_source_set_closure (source,
599                         g_cclosure_new_object (cb, G_OBJECT (chooser_entry)));
600   id = g_source_attach (source, NULL);
601   g_source_unref (source);
602
603   return id;
604 }
605
606 static void
607 add_completion_idle (GtkFileChooserEntry *chooser_entry)
608 {
609   /* idle to update the selection based on the file list */
610   if (chooser_entry->check_completion_idle == 0)
611     chooser_entry->check_completion_idle = 
612       idle_add (chooser_entry, G_CALLBACK (check_completion_callback));
613 }
614
615
616 static void
617 update_current_folder_files (GtkFileChooserEntry *chooser_entry,
618                              GSList              *added_uris)
619 {
620   GSList *tmp_list;
621
622   g_assert (chooser_entry->completion_store != NULL);
623
624   /* Bah.  Need to turn off sorting */
625   for (tmp_list = added_uris; tmp_list; tmp_list = tmp_list->next)
626     {
627       GtkFileInfo *info;
628       GtkFilePath *path;
629
630       path = tmp_list->data;
631
632       info = gtk_file_folder_get_info (chooser_entry->current_folder,
633                                        path,
634                                        NULL); /* NULL-GError */
635       if (info)
636         {
637           gchar *display_name = g_strdup (gtk_file_info_get_display_name (info));
638           GtkTreeIter iter;
639
640           display_name = maybe_append_separator_to_path (chooser_entry, path, display_name);
641
642           gtk_list_store_append (chooser_entry->completion_store, &iter);
643           gtk_list_store_set (chooser_entry->completion_store, &iter,
644                               DISPLAY_NAME_COLUMN, display_name,
645                               PATH_COLUMN, path,
646                               -1);
647
648           gtk_file_info_free (info);
649           g_free (display_name);
650         }
651     }
652
653   /* FIXME: we want to turn off sorting temporarily.  I suck... */
654   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store),
655                                         DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING);
656
657   add_completion_idle (chooser_entry);
658 }
659
660 static void
661 files_added_cb (GtkFileSystem       *file_system,
662                 GSList              *added_uris,
663                 GtkFileChooserEntry *chooser_entry)
664 {
665   update_current_folder_files (chooser_entry, added_uris);
666 }
667
668 static void
669 files_deleted_cb (GtkFileSystem       *file_system,
670                   GSList              *deleted_uris,
671                   GtkFileChooserEntry *chooser_entry)
672 {
673   /* FIXME: gravy... */
674 }
675
676 static void
677 gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
678                                        const gchar *new_text,
679                                        gint         new_text_length,
680                                        gint        *position)
681 {
682   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
683
684   parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position);
685
686   if (! chooser_entry->in_change)
687     add_completion_idle (GTK_FILE_CHOOSER_ENTRY (editable));
688 }
689
690 static void
691 gtk_file_chooser_entry_grab_focus (GtkWidget *widget)
692 {
693   GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget);
694   _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget));
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         {
732           gint sel_end;
733
734           if (gtk_editable_get_selection_bounds (editable, NULL, &sel_end))
735             gtk_editable_set_position (editable, sel_end);
736         }
737       else
738         append_common_prefix (chooser_entry, FALSE);
739
740       /* Trigger the completion window to pop up again by a 
741        * zero-length insertion, a bit of a hack.
742        */
743       gtk_editable_insert_text (editable, "", -1, &pos);
744
745       return TRUE;
746     }
747   else
748     return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction);
749 }
750
751 static void
752 gtk_file_chooser_entry_activate (GtkEntry *entry)
753 {
754   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry);
755
756   if (chooser_entry->has_completion)
757     {
758       gtk_editable_set_position (GTK_EDITABLE (entry),
759                                  entry->text_length);
760     }
761   
762   GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry);
763 }
764
765 static void
766 load_directory_get_folder_callback (GtkFileSystemHandle *handle,
767                                     GtkFileFolder       *folder,
768                                     const GError        *error,
769                                     gpointer             data)
770 {
771   gboolean cancelled = handle->cancelled;
772   GtkFileChooserEntry *chooser_entry = data;
773
774   if (handle != chooser_entry->load_folder_handle)
775     goto out;
776
777   chooser_entry->load_folder_handle = NULL;
778
779   if (cancelled || error)
780     goto out;
781
782   /* FIXME: connect to "finished-loading".  In that callback, see our
783    * load_complete_action and do the appropriate thing.
784    *
785    * Do we need to populate the completion store here?  (O(n^2))
786    * Maybe we should wait until the folder is finished loading.
787    */
788
789   chooser_entry->current_folder = folder;
790   g_signal_connect (chooser_entry->current_folder, "files-added",
791                     G_CALLBACK (files_added_cb), chooser_entry);
792   g_signal_connect (chooser_entry->current_folder, "files-removed",
793                     G_CALLBACK (files_deleted_cb), chooser_entry);
794
795   chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS,
796                                                         G_TYPE_STRING,
797                                                         GTK_TYPE_FILE_PATH);
798
799   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)),
800                                   GTK_TREE_MODEL (chooser_entry->completion_store));
801
802 out:
803   g_object_unref (chooser_entry);
804   g_object_unref (handle);
805 }
806
807 static void
808 load_current_folder (GtkFileChooserEntry *chooser_entry)
809 {
810   if (chooser_entry->current_folder_path == NULL ||
811       chooser_entry->file_system == NULL)
812     return;
813
814   g_assert (chooser_entry->current_folder == NULL);
815   g_assert (chooser_entry->completion_store == NULL);
816   g_assert (chooser_entry->load_folder_handle == NULL);
817
818   printf ("Starting async load of folder %s\n", (char *) chooser_entry->current_folder_path);
819
820   chooser_entry->load_folder_handle =
821     gtk_file_system_get_folder (chooser_entry->file_system,
822                                 chooser_entry->current_folder_path,
823                                 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER,
824                                 load_directory_get_folder_callback,
825                                 g_object_ref (chooser_entry));
826 }
827
828 static void
829 reload_current_folder (GtkFileChooserEntry *chooser_entry,
830                        GtkFilePath         *folder_path,
831                        gboolean             force_reload)
832 {
833   gboolean reload = FALSE;
834
835   if (chooser_entry->current_folder_path)
836     {
837       if ((folder_path && gtk_file_path_compare (folder_path, chooser_entry->current_folder_path) != 0)
838           || force_reload)
839         {
840           reload = TRUE;
841
842           /* We changed our current directory.  We need to clear out the old
843            * directory information.
844            */
845           if (chooser_entry->current_folder)
846             {
847               g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
848                                                     G_CALLBACK (files_added_cb), chooser_entry);
849               g_signal_handlers_disconnect_by_func (chooser_entry->current_folder,
850                                                     G_CALLBACK (files_deleted_cb), chooser_entry);
851
852               if (chooser_entry->load_folder_handle)
853                 {
854                   printf ("Cancelling folder load\n");
855                   gtk_file_system_cancel_operation (chooser_entry->load_folder_handle);
856                   chooser_entry->load_folder_handle = NULL;
857                 }
858
859               g_object_unref (chooser_entry->current_folder);
860               chooser_entry->current_folder = NULL;
861             }
862
863           if (chooser_entry->completion_store)
864             {
865               gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);
866               g_object_unref (chooser_entry->completion_store);
867               chooser_entry->completion_store = NULL;
868             }
869
870           gtk_file_path_free (chooser_entry->current_folder_path);
871           chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
872         }
873     }
874   else
875     {
876       chooser_entry->current_folder_path = gtk_file_path_copy (folder_path);
877       reload = TRUE;
878     }
879
880   if (reload)
881     load_current_folder (chooser_entry);
882 }
883
884 static void
885 refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry)
886 {
887   GtkEditable *editable;
888   gchar *text_up_to_cursor;
889   GtkFilePath *folder_path;
890   gchar *file_part;
891   gsize total_len, file_part_len;
892   gint file_part_pos;
893
894   editable = GTK_EDITABLE (chooser_entry);
895
896   text_up_to_cursor = gtk_editable_get_chars (editable, 0, gtk_editable_get_position (editable));
897   
898   if (!chooser_entry->file_system ||
899       !chooser_entry->base_folder ||
900       !gtk_file_system_parse (chooser_entry->file_system,
901                               chooser_entry->base_folder, text_up_to_cursor,
902                               &folder_path, &file_part, NULL)) /* NULL-GError */
903     {
904       folder_path = gtk_file_path_copy (chooser_entry->base_folder);
905       file_part = g_strdup ("");
906       file_part_pos = -1;
907     }
908   else
909     {
910       file_part_len = strlen (file_part);
911       total_len = strlen (text_up_to_cursor);
912       if (total_len > file_part_len)
913         file_part_pos = g_utf8_strlen (text_up_to_cursor, total_len - file_part_len);
914       else
915         file_part_pos = 0;
916     }
917
918   printf ("Parsed text \"%s\", file_part=\"%s\", file_part_pos=%d, folder_path=\"%s\"\n",
919           text_up_to_cursor,
920           file_part,
921           file_part_pos,
922           folder_path ? (char *) folder_path : "(NULL)");
923
924   g_free (text_up_to_cursor);
925
926   g_free (chooser_entry->file_part);
927
928   chooser_entry->file_part = file_part;
929   chooser_entry->file_part_pos = file_part_pos;
930
931   reload_current_folder (chooser_entry, folder_path, file_part_pos == -1);
932   gtk_file_path_free (folder_path);
933 }
934
935 static void
936 autocomplete (GtkFileChooserEntry *chooser_entry)
937 {
938   g_assert (chooser_entry->current_folder != NULL);
939   g_assert (gtk_file_folder_is_finished_loading (chooser_entry->current_folder));
940   g_assert (gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length);
941
942   printf ("Doing autocompletion since our folder is finished loading\n");
943
944   append_common_prefix (chooser_entry, TRUE);
945 }
946
947 static void
948 start_autocompletion (GtkFileChooserEntry *chooser_entry)
949 {
950   printf ("Starting autocompletion\n");
951
952   refresh_current_folder_and_file_part (chooser_entry);
953
954   if (!chooser_entry->current_folder)
955     {
956       /* We don't beep or anything, since this is autocompletion - the user
957        * didn't request any action explicitly.
958        */
959       printf ("No current_folder; not doing autocompletion after all\n");
960       return;
961     }
962
963   if (gtk_file_folder_is_finished_loading (chooser_entry->current_folder))
964     {
965       printf ("File folder is finished loading; doing autocompletion immediately\n");
966       autocomplete (chooser_entry);
967     }
968   else
969     {
970       printf ("File folder is not yet loaded; will do autocompletion later\n");
971       chooser_entry->load_complete_action = LOAD_COMPLETE_AUTOCOMPLETE;
972     }
973 }
974
975 static gboolean
976 start_autocompletion_idle_handler (gpointer data)
977 {
978   GtkFileChooserEntry *chooser_entry;
979
980   chooser_entry = GTK_FILE_CHOOSER_ENTRY (data);
981
982   if (gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == GTK_ENTRY (chooser_entry)->text_length)
983     start_autocompletion (chooser_entry);
984
985   chooser_entry->start_autocompletion_idle_id = 0;
986
987   return FALSE;
988 }
989
990 static void
991 install_start_autocompletion_idle (GtkFileChooserEntry *chooser_entry)
992 {
993   /* We set up an idle handler because we must test the cursor position.  However,
994    * the cursor is not updated in GtkEntry::changed, so we wait for the idle loop.
995    */
996
997   if (chooser_entry->start_autocompletion_idle_id != 0)
998     return;
999
1000   chooser_entry->start_autocompletion_idle_id = g_idle_add (start_autocompletion_idle_handler, chooser_entry);
1001 }
1002
1003 static void
1004 gtk_file_chooser_entry_changed (GtkEditable *editable)
1005 {
1006   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
1007
1008   if (chooser_entry->in_change)
1009     return;
1010
1011   chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING;
1012
1013   if ((chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN
1014        || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER))
1015     install_start_autocompletion_idle (chooser_entry);
1016
1017   /* FIXME: see when the cursor changes (see GtkEntry's cursor_position property, or GtkEditable::set_position and GtkEntry::move_cursor).
1018    * When the cursor changes, cancel the load_complete_action.
1019    * When the entry is activated, cancel the load_complete_action.
1020    * In general, cancel the load_complete_action when the entry loses the focus.
1021    */
1022 }
1023
1024 static void
1025 clear_completion_callback (GtkFileChooserEntry *chooser_entry,
1026                            GParamSpec          *pspec)
1027 {
1028   if (chooser_entry->has_completion)
1029     {
1030       chooser_entry->has_completion = FALSE;
1031       gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
1032     }
1033 }
1034
1035 #ifdef G_OS_WIN32
1036 static gint
1037 insert_text_callback (GtkFileChooserEntry *chooser_entry,
1038                       const gchar         *new_text,
1039                       gint                 new_text_length,
1040                       gint                *position,
1041                       gpointer             user_data)
1042 {
1043   const gchar *colon = memchr (new_text, ':', new_text_length);
1044   gint i;
1045
1046   /* Disallow these characters altogether */
1047   for (i = 0; i < new_text_length; i++)
1048     {
1049       if (new_text[i] == '<' ||
1050           new_text[i] == '>' ||
1051           new_text[i] == '"' ||
1052           new_text[i] == '|' ||
1053           new_text[i] == '*' ||
1054           new_text[i] == '?')
1055         break;
1056     }
1057
1058   if (i < new_text_length ||
1059       /* Disallow entering text that would cause a colon to be anywhere except
1060        * after a drive letter.
1061        */
1062       (colon != NULL &&
1063        *position + (colon - new_text) != 1) ||
1064       (new_text_length > 0 &&
1065        *position <= 1 &&
1066        GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1067        gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':'))
1068     {
1069       gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
1070       g_signal_stop_emission_by_name (chooser_entry, "insert_text");
1071       return FALSE;
1072     }
1073
1074   return TRUE;
1075 }
1076
1077 static void
1078 delete_text_callback (GtkFileChooserEntry *chooser_entry,
1079                       gint                 start_pos,
1080                       gint                 end_pos,
1081                       gpointer             user_data)
1082 {
1083   /* If deleting a drive letter, delete the colon, too */
1084   if (start_pos == 0 && end_pos == 1 &&
1085       GTK_ENTRY (chooser_entry)->text_length >= 2 &&
1086       gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':')
1087     {
1088       g_signal_handlers_block_by_func (chooser_entry,
1089                                        G_CALLBACK (delete_text_callback),
1090                                        user_data);
1091       gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), 0, 1);
1092       g_signal_handlers_unblock_by_func (chooser_entry,
1093                                          G_CALLBACK (delete_text_callback),
1094                                          user_data);
1095     }
1096 }
1097 #endif
1098
1099 /**
1100  * _gtk_file_chooser_entry_new:
1101  * @eat_tabs: If %FALSE, allow focus navigation with the tab key.
1102  *
1103  * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
1104  * is an internal implementation widget for the GTK+ file chooser
1105  * which is an entry with completion with respect to a
1106  * #GtkFileSystem object.
1107  *
1108  * Return value: the newly created #GtkFileChooserEntry
1109  **/
1110 GtkWidget *
1111 _gtk_file_chooser_entry_new (gboolean eat_tabs)
1112 {
1113   GtkFileChooserEntry *chooser_entry;
1114
1115   chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
1116   chooser_entry->eat_tabs = (eat_tabs != FALSE);
1117
1118   return GTK_WIDGET (chooser_entry);
1119 }
1120
1121 /**
1122  * _gtk_file_chooser_entry_set_file_system:
1123  * @chooser_entry: a #GtkFileChooser
1124  * @file_system: an object implementing #GtkFileSystem
1125  *
1126  * Sets the file system for @chooser_entry.
1127  **/
1128 void
1129 _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry,
1130                                          GtkFileSystem       *file_system)
1131 {
1132   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1133   g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system));
1134
1135   if (file_system != chooser_entry->file_system)
1136     {
1137       if (chooser_entry->file_system)
1138         g_object_unref (chooser_entry->file_system);
1139
1140       chooser_entry->file_system = g_object_ref (file_system);
1141     }
1142 }
1143
1144 /**
1145  * _gtk_file_chooser_entry_set_base_folder:
1146  * @chooser_entry: a #GtkFileChooserEntry
1147  * @path: path of a folder in the chooser entries current file system.
1148  *
1149  * Sets the folder with respect to which completions occur.
1150  **/
1151 void
1152 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
1153                                          const GtkFilePath   *path)
1154 {
1155   if (chooser_entry->base_folder)
1156     gtk_file_path_free (chooser_entry->base_folder);
1157
1158   chooser_entry->base_folder = gtk_file_path_copy (path);
1159
1160   gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
1161   _gtk_file_chooser_entry_select_filename (chooser_entry);
1162 }
1163
1164 /**
1165  * _gtk_file_chooser_entry_get_current_folder:
1166  * @chooser_entry: a #GtkFileChooserEntry
1167  *
1168  * Gets the current folder for the #GtkFileChooserEntry. If the
1169  * user has only entered a filename, this will be the base folder
1170  * (see _gtk_file_chooser_entry_set_base_folder()), but if the
1171  * user has entered a relative or absolute path, then it will
1172  * be different. If the user has entered a relative or absolute
1173  * path that doesn't point to a folder in the file system, it will
1174  * be %NULL.
1175  *
1176  * Return value: the path of current folder - this value is owned by the
1177  *  chooser entry and must not be modified or freed.
1178  **/
1179 const GtkFilePath *
1180 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
1181 {
1182   if (chooser_entry->has_completion)
1183     {
1184       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
1185                                  GTK_ENTRY (chooser_entry)->text_length);
1186     }
1187   return chooser_entry->current_folder_path;
1188 }
1189
1190 /**
1191  * _gtk_file_chooser_entry_get_file_part:
1192  * @chooser_entry: a #GtkFileChooserEntry
1193  *
1194  * Gets the non-folder portion of whatever the user has entered
1195  * into the file selector. What is returned is a UTF-8 string,
1196  * and if a filename path is needed, gtk_file_system_make_path()
1197  * must be used
1198   *
1199  * Return value: the entered filename - this value is owned by the
1200  *  chooser entry and must not be modified or freed.
1201  **/
1202 const gchar *
1203 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
1204 {
1205   if (chooser_entry->has_completion)
1206     {
1207       gtk_editable_set_position (GTK_EDITABLE (chooser_entry),
1208                                  GTK_ENTRY (chooser_entry)->text_length);
1209     }
1210   return chooser_entry->file_part;
1211 }
1212
1213 /**
1214  * _gtk_file_chooser_entry_set_file_part:
1215  * @chooser_entry: a #GtkFileChooserEntry
1216  * @file_part: text to display in the entry, in UTF-8
1217  *
1218  * Sets the current text shown in the file chooser entry.
1219  **/
1220 void
1221 _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry,
1222                                        const gchar         *file_part)
1223 {
1224   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1225
1226   gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part);
1227 }
1228
1229
1230 /**
1231  * _gtk_file_chooser_entry_set_action:
1232  * @chooser_entry: a #GtkFileChooserEntry
1233  * @action: the action which is performed by the file selector using this entry
1234  *
1235  * Sets action which is performed by the file selector using this entry. 
1236  * The #GtkFileChooserEntry will use different completion strategies for 
1237  * different actions.
1238  **/
1239 void
1240 _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry,
1241                                     GtkFileChooserAction action)
1242 {
1243   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
1244   
1245   if (chooser_entry->action != action)
1246     {
1247       GtkEntryCompletion *comp;
1248
1249       chooser_entry->action = action;
1250
1251       comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
1252
1253       switch (action)
1254         {
1255         case GTK_FILE_CHOOSER_ACTION_OPEN:
1256         case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
1257           gtk_entry_completion_set_popup_single_match (comp, FALSE);
1258           break;
1259         case GTK_FILE_CHOOSER_ACTION_SAVE:
1260         case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
1261           gtk_entry_completion_set_popup_single_match (comp, TRUE);
1262           break;
1263         }
1264     }
1265 }
1266
1267
1268 /**
1269  * _gtk_file_chooser_entry_get_action:
1270  * @chooser_entry: a #GtkFileChooserEntry
1271  *
1272  * Gets the action for this entry. 
1273  *
1274  * Returns: the action
1275  **/
1276 GtkFileChooserAction
1277 _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry)
1278 {
1279   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry),
1280                         GTK_FILE_CHOOSER_ACTION_OPEN);
1281   
1282   return chooser_entry->action;
1283 }
1284
1285 gboolean
1286 _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry,
1287                                        const GtkFilePath   *path)
1288 {
1289   gboolean retval = FALSE;
1290
1291   if (chooser_entry->current_folder)
1292     {
1293       GtkFileInfo *file_info;
1294
1295       file_info = gtk_file_folder_get_info (chooser_entry->current_folder,
1296                                             path, NULL);
1297       if (file_info)
1298         {
1299           retval = gtk_file_info_get_is_folder (file_info);
1300           gtk_file_info_free (file_info);
1301         }
1302     }
1303
1304   return retval;
1305 }
1306
1307
1308 /*
1309  * _gtk_file_chooser_entry_select_filename:
1310  * @chooser_entry: a #GtkFileChooserEntry
1311  *
1312  * Selects the filename (without the extension) for user edition.
1313  */
1314 void
1315 _gtk_file_chooser_entry_select_filename (GtkFileChooserEntry *chooser_entry)
1316 {
1317   const gchar *str, *ext;
1318   glong len = -1;
1319
1320   if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE)
1321     {
1322       str = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
1323       ext = g_strrstr (str, ".");
1324
1325       if (ext)
1326        len = g_utf8_pointer_to_offset (str, ext);
1327     }
1328
1329   gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, (gint) len);
1330 }
1331