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 
23 #include "gtkfilechooserentry.h"
24 
25 #include <string.h>
26 
27 #include "gtkalignment.h"
28 #include "gtkcelllayout.h"
29 #include "gtkcellrenderertext.h"
30 #include "gtkentryprivate.h"
31 #include "gtkfilesystemmodel.h"
32 #include "gtklabel.h"
33 #include "gtkmain.h"
34 #include "gtkwindow.h"
35 #include "gtkintl.h"
36 #include "gtkalias.h"
37 
38 #include "gdkkeysyms.h"
39 
40 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
41 
42 #define GTK_FILE_CHOOSER_ENTRY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
43 #define GTK_IS_FILE_CHOOSER_ENTRY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY))
44 #define GTK_FILE_CHOOSER_ENTRY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
45 
46 struct _GtkFileChooserEntryClass
47 {
48   GtkEntryClass parent_class;
49 };
50 
51 struct _GtkFileChooserEntry
52 {
53   GtkEntry parent_instance;
54 
55   GtkFileChooserAction action;
56 
57   GFile *base_folder;
58   GFile *current_folder_file;
59   gchar *dir_part;
60   gchar *file_part;
61 
62   GtkTreeModel *completion_store;
63 
64   guint current_folder_loaded : 1;
65   guint complete_on_load : 1;
66   guint eat_tabs       : 1;
67   guint local_only     : 1;
68 };
69 
70 enum
71 {
72   DISPLAY_NAME_COLUMN,
73   FULL_PATH_COLUMN,
74   N_COLUMNS
75 };
76 
77 static void     gtk_file_chooser_entry_finalize       (GObject          *object);
78 static void     gtk_file_chooser_entry_dispose        (GObject          *object);
79 static void     gtk_file_chooser_entry_grab_focus     (GtkWidget        *widget);
80 static gboolean gtk_file_chooser_entry_tab_handler    (GtkWidget *widget,
81 						       GdkEventKey *event);
82 static gboolean gtk_file_chooser_entry_focus_out_event (GtkWidget       *widget,
83 							GdkEventFocus   *event);
84 
85 #ifdef G_OS_WIN32
86 static gint     insert_text_callback      (GtkFileChooserEntry *widget,
87 					   const gchar         *new_text,
88 					   gint                 new_text_length,
89 					   gint                *position,
90 					   gpointer             user_data);
91 static void     delete_text_callback      (GtkFileChooserEntry *widget,
92 					   gint                 start_pos,
93 					   gint                 end_pos,
94 					   gpointer             user_data);
95 #endif
96 
97 static gboolean match_selected_callback   (GtkEntryCompletion  *completion,
98 					   GtkTreeModel        *model,
99 					   GtkTreeIter         *iter,
100 					   GtkFileChooserEntry *chooser_entry);
101 
102 static void set_complete_on_load (GtkFileChooserEntry *chooser_entry,
103                                   gboolean             complete_on_load);
104 static void refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry);
105 static void set_completion_folder (GtkFileChooserEntry *chooser_entry,
106                                    GFile               *folder,
107 				   char                *dir_part);
108 static void finished_loading_cb (GtkFileSystemModel  *model,
109                                  GError              *error,
110 		                 GtkFileChooserEntry *chooser_entry);
111 
G_DEFINE_TYPE(GtkFileChooserEntry,_gtk_file_chooser_entry,GTK_TYPE_ENTRY)112 G_DEFINE_TYPE (GtkFileChooserEntry, _gtk_file_chooser_entry, GTK_TYPE_ENTRY)
113 
114 static char *
115 gtk_file_chooser_entry_get_completion_text (GtkFileChooserEntry *chooser_entry)
116 {
117   GtkEditable *editable = GTK_EDITABLE (chooser_entry);
118   int start, end;
119 
120   gtk_editable_get_selection_bounds (editable, &start, &end);
121   return gtk_editable_get_chars (editable, 0, MIN (start, end));
122 }
123 
124 static void
gtk_file_chooser_entry_dispatch_properties_changed(GObject * object,guint n_pspecs,GParamSpec ** pspecs)125 gtk_file_chooser_entry_dispatch_properties_changed (GObject     *object,
126                                                     guint        n_pspecs,
127                                                     GParamSpec **pspecs)
128 {
129   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
130   guint i;
131 
132   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispatch_properties_changed (object, n_pspecs, pspecs);
133 
134   /* Don't do this during or after disposal */
135   if (gtk_widget_get_parent (GTK_WIDGET (object)) != NULL)
136     {
137       /* What we are after: The text in front of the cursor was modified.
138        * Unfortunately, there's no other way to catch this.
139        */
140       for (i = 0; i < n_pspecs; i++)
141         {
142           if (pspecs[i]->name == I_("cursor-position") ||
143               pspecs[i]->name == I_("selection-bound") ||
144               pspecs[i]->name == I_("text"))
145             {
146               set_complete_on_load (chooser_entry, FALSE);
147               refresh_current_folder_and_file_part (chooser_entry);
148               break;
149             }
150         }
151     }
152 }
153 
154 static void
_gtk_file_chooser_entry_class_init(GtkFileChooserEntryClass * class)155 _gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class)
156 {
157   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
158   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
159 
160   gobject_class->finalize = gtk_file_chooser_entry_finalize;
161   gobject_class->dispose = gtk_file_chooser_entry_dispose;
162   gobject_class->dispatch_properties_changed = gtk_file_chooser_entry_dispatch_properties_changed;
163 
164   widget_class->grab_focus = gtk_file_chooser_entry_grab_focus;
165   widget_class->focus_out_event = gtk_file_chooser_entry_focus_out_event;
166 }
167 
168 static void
_gtk_file_chooser_entry_init(GtkFileChooserEntry * chooser_entry)169 _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
170 {
171   GtkEntryCompletion *comp;
172   GtkCellRenderer *cell;
173 
174   chooser_entry->local_only = TRUE;
175 
176   g_object_set (chooser_entry, "truncate-multiline", TRUE, NULL);
177 
178   comp = gtk_entry_completion_new ();
179   gtk_entry_completion_set_popup_single_match (comp, FALSE);
180   gtk_entry_completion_set_minimum_key_length (comp, 0);
181   /* see docs for gtk_entry_completion_set_text_column() */
182   g_object_set (comp, "text-column", FULL_PATH_COLUMN, NULL);
183 
184   /* Need a match func here or entry completion uses a wrong one.
185    * We do our own filtering after all. */
186   gtk_entry_completion_set_match_func (comp,
187 				       (GtkEntryCompletionMatchFunc) gtk_true,
188 				       chooser_entry,
189 				       NULL);
190 
191   cell = gtk_cell_renderer_text_new ();
192   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (comp),
193                               cell, TRUE);
194   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (comp),
195                                  cell,
196                                  "text", DISPLAY_NAME_COLUMN);
197 
198   g_signal_connect (comp, "match-selected",
199 		    G_CALLBACK (match_selected_callback), chooser_entry);
200 
201   gtk_entry_set_completion (GTK_ENTRY (chooser_entry), comp);
202   g_object_unref (comp);
203   /* NB: This needs to happen after the completion is set, so this handler
204    * runs before the handler installed by entrycompletion */
205   g_signal_connect (chooser_entry, "key-press-event",
206                     G_CALLBACK (gtk_file_chooser_entry_tab_handler), NULL);
207 
208 #ifdef G_OS_WIN32
209   g_signal_connect (chooser_entry, "insert-text",
210 		    G_CALLBACK (insert_text_callback), NULL);
211   g_signal_connect (chooser_entry, "delete-text",
212 		    G_CALLBACK (delete_text_callback), NULL);
213 #endif
214 }
215 
216 static void
gtk_file_chooser_entry_finalize(GObject * object)217 gtk_file_chooser_entry_finalize (GObject *object)
218 {
219   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
220 
221   if (chooser_entry->base_folder)
222     g_object_unref (chooser_entry->base_folder);
223 
224   if (chooser_entry->current_folder_file)
225     g_object_unref (chooser_entry->current_folder_file);
226 
227   g_free (chooser_entry->dir_part);
228   g_free (chooser_entry->file_part);
229 
230   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->finalize (object);
231 }
232 
233 static void
gtk_file_chooser_entry_dispose(GObject * object)234 gtk_file_chooser_entry_dispose (GObject *object)
235 {
236   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
237 
238   set_completion_folder (chooser_entry, NULL, NULL);
239 
240   G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object);
241 }
242 
243 /* Match functions for the GtkEntryCompletion */
244 static gboolean
match_selected_callback(GtkEntryCompletion * completion,GtkTreeModel * model,GtkTreeIter * iter,GtkFileChooserEntry * chooser_entry)245 match_selected_callback (GtkEntryCompletion  *completion,
246                          GtkTreeModel        *model,
247                          GtkTreeIter         *iter,
248                          GtkFileChooserEntry *chooser_entry)
249 {
250   char *path;
251   gint pos;
252 
253   gtk_tree_model_get (model, iter,
254                       FULL_PATH_COLUMN, &path,
255                       -1);
256 
257   gtk_editable_delete_text (GTK_EDITABLE (chooser_entry),
258                             0,
259                             gtk_editable_get_position (GTK_EDITABLE (chooser_entry)));
260   pos = 0;
261   gtk_editable_insert_text (GTK_EDITABLE (chooser_entry),
262                             path,
263                             -1,
264                             &pos);
265 
266   gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos);
267 
268   g_free (path);
269 
270   return TRUE;
271 }
272 
273 static void
set_complete_on_load(GtkFileChooserEntry * chooser_entry,gboolean complete_on_load)274 set_complete_on_load (GtkFileChooserEntry *chooser_entry,
275                       gboolean             complete_on_load)
276 {
277   /* a completion was triggered, but we couldn't do it.
278    * So no text was inserted when pressing tab, so we beep
279    */
280   if (chooser_entry->complete_on_load && !complete_on_load)
281     gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
282 
283   chooser_entry->complete_on_load = complete_on_load;
284 }
285 
286 static gboolean
is_valid_scheme_character(char c)287 is_valid_scheme_character (char c)
288 {
289   return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
290 }
291 
292 static gboolean
has_uri_scheme(const char * str)293 has_uri_scheme (const char *str)
294 {
295   const char *p;
296 
297   p = str;
298 
299   if (!is_valid_scheme_character (*p))
300     return FALSE;
301 
302   do
303     p++;
304   while (is_valid_scheme_character (*p));
305 
306   return (strncmp (p, "://", 3) == 0);
307 }
308 
309 static GFile *
gtk_file_chooser_get_file_for_text(GtkFileChooserEntry * chooser_entry,const gchar * str)310 gtk_file_chooser_get_file_for_text (GtkFileChooserEntry *chooser_entry,
311                                     const gchar         *str)
312 {
313   GFile *file;
314 
315   if (str[0] == '~' || g_path_is_absolute (str) || has_uri_scheme (str))
316     file = g_file_parse_name (str);
317   else if (chooser_entry->base_folder != NULL)
318     file = g_file_resolve_relative_path (chooser_entry->base_folder, str);
319   else
320     file = NULL;
321 
322   return file;
323 }
324 
325 static gboolean
is_directory_shortcut(const char * text)326 is_directory_shortcut (const char *text)
327 {
328   return strcmp (text, ".") == 0 ||
329          strcmp (text, "..") == 0 ||
330          strcmp (text, "~" ) == 0;
331 }
332 
333 static GFile *
gtk_file_chooser_get_directory_for_text(GtkFileChooserEntry * chooser_entry,const char * text)334 gtk_file_chooser_get_directory_for_text (GtkFileChooserEntry *chooser_entry,
335                                          const char *         text)
336 {
337   GFile *file, *parent;
338 
339   file = gtk_file_chooser_get_file_for_text (chooser_entry, text);
340 
341   if (file == NULL)
342     return NULL;
343 
344   if (text[0] == 0 || text[strlen (text) - 1] == G_DIR_SEPARATOR ||
345       is_directory_shortcut (text))
346     return file;
347 
348   parent = g_file_get_parent (file);
349   g_object_unref (file);
350 
351   return parent;
352 }
353 
354 /* Finds a common prefix based on the contents of the entry
355  * and mandatorily appends it
356  */
357 static void
explicitly_complete(GtkFileChooserEntry * chooser_entry)358 explicitly_complete (GtkFileChooserEntry *chooser_entry)
359 {
360   chooser_entry->complete_on_load = FALSE;
361 
362   if (chooser_entry->completion_store)
363     {
364       char *completion, *text;
365       gsize completion_len, text_len;
366 
367       text = gtk_file_chooser_entry_get_completion_text (chooser_entry);
368       text_len = strlen (text);
369       completion = _gtk_entry_completion_compute_prefix (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), text);
370       completion_len = completion ? strlen (completion) : 0;
371 
372       if (completion_len > text_len)
373         {
374           GtkEditable *editable = GTK_EDITABLE (chooser_entry);
375           int pos = gtk_editable_get_position (editable);
376 
377           gtk_editable_insert_text (editable,
378                                     completion + text_len,
379                                     completion_len - text_len,
380                                     &pos);
381           gtk_editable_set_position (editable, pos);
382           return;
383         }
384     }
385 
386   gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
387 }
388 
389 static void
gtk_file_chooser_entry_grab_focus(GtkWidget * widget)390 gtk_file_chooser_entry_grab_focus (GtkWidget *widget)
391 {
392   GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget);
393   _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget));
394 }
395 
396 static void
start_explicit_completion(GtkFileChooserEntry * chooser_entry)397 start_explicit_completion (GtkFileChooserEntry *chooser_entry)
398 {
399   if (chooser_entry->current_folder_loaded)
400     explicitly_complete (chooser_entry);
401   else
402     set_complete_on_load (chooser_entry, TRUE);
403 }
404 
405 static gboolean
gtk_file_chooser_entry_tab_handler(GtkWidget * widget,GdkEventKey * event)406 gtk_file_chooser_entry_tab_handler (GtkWidget *widget,
407 				    GdkEventKey *event)
408 {
409   GtkFileChooserEntry *chooser_entry;
410   GtkEditable *editable;
411   GdkModifierType state;
412   gint start, end;
413 
414   chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
415   editable = GTK_EDITABLE (widget);
416 
417   if (!chooser_entry->eat_tabs)
418     return FALSE;
419 
420   if (event->keyval != GDK_KEY_Tab)
421     return FALSE;
422 
423   if (gtk_get_current_event_state (&state) &&
424       (state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
425     return FALSE;
426 
427   /* This is a bit evil -- it makes Tab never leave the entry. It basically
428    * makes it 'safe' for people to hit. */
429   gtk_editable_get_selection_bounds (editable, &start, &end);
430 
431   if (start != end)
432     gtk_editable_set_position (editable, MAX (start, end));
433   else
434     start_explicit_completion (chooser_entry);
435 
436   return TRUE;
437 }
438 
439 static gboolean
gtk_file_chooser_entry_focus_out_event(GtkWidget * widget,GdkEventFocus * event)440 gtk_file_chooser_entry_focus_out_event (GtkWidget     *widget,
441 					GdkEventFocus *event)
442 {
443   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
444 
445   set_complete_on_load (chooser_entry, FALSE);
446 
447   return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus_out_event (widget, event);
448 }
449 
450 static void
update_inline_completion(GtkFileChooserEntry * chooser_entry)451 update_inline_completion (GtkFileChooserEntry *chooser_entry)
452 {
453   GtkEntryCompletion *completion = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
454 
455   if (!chooser_entry->current_folder_loaded)
456     {
457       gtk_entry_completion_set_inline_completion (completion, FALSE);
458       return;
459     }
460 
461   switch (chooser_entry->action)
462     {
463     case GTK_FILE_CHOOSER_ACTION_OPEN:
464     case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
465       gtk_entry_completion_set_inline_completion (completion, TRUE);
466       break;
467     case GTK_FILE_CHOOSER_ACTION_SAVE:
468     case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
469       gtk_entry_completion_set_inline_completion (completion, FALSE);
470       break;
471     }
472 }
473 
474 static void
discard_completion_store(GtkFileChooserEntry * chooser_entry)475 discard_completion_store (GtkFileChooserEntry *chooser_entry)
476 {
477   if (!chooser_entry->completion_store)
478     return;
479 
480   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);
481   update_inline_completion (chooser_entry);
482   g_object_unref (chooser_entry->completion_store);
483   chooser_entry->completion_store = NULL;
484 }
485 
486 static gboolean
completion_store_set(GtkFileSystemModel * model,GFile * file,GFileInfo * info,int column,GValue * value,gpointer data)487 completion_store_set (GtkFileSystemModel  *model,
488                       GFile               *file,
489                       GFileInfo           *info,
490                       int                  column,
491                       GValue              *value,
492                       gpointer             data)
493 {
494   GtkFileChooserEntry *chooser_entry = data;
495 
496   const char *prefix = "";
497   const char *suffix = "";
498 
499   switch (column)
500     {
501     case FULL_PATH_COLUMN:
502       prefix = chooser_entry->dir_part;
503       /* fall through */
504     case DISPLAY_NAME_COLUMN:
505       if (_gtk_file_info_consider_as_directory (info))
506         suffix = G_DIR_SEPARATOR_S;
507 
508       g_value_take_string (value,
509 			   g_strconcat (prefix,
510 					g_file_info_get_display_name (info),
511 					suffix,
512 					NULL));
513       break;
514     default:
515       g_assert_not_reached ();
516       break;
517     }
518 
519   return TRUE;
520 }
521 
522 /* Fills the completion store from the contents of the current folder */
523 static void
populate_completion_store(GtkFileChooserEntry * chooser_entry)524 populate_completion_store (GtkFileChooserEntry *chooser_entry)
525 {
526   chooser_entry->completion_store = GTK_TREE_MODEL (
527       _gtk_file_system_model_new_for_directory (chooser_entry->current_folder_file,
528                                                 "standard::name,standard::display-name,standard::type",
529                                                 completion_store_set,
530                                                 chooser_entry,
531                                                 N_COLUMNS,
532                                                 G_TYPE_STRING,
533                                                 G_TYPE_STRING));
534   g_signal_connect (chooser_entry->completion_store, "finished-loading",
535 		    G_CALLBACK (finished_loading_cb), chooser_entry);
536 
537   _gtk_file_system_model_set_filter_folders (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
538                                              TRUE);
539   _gtk_file_system_model_set_show_files (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
540                                          chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
541                                          chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE);
542   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store),
543 					DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING);
544 
545   gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)),
546 				  chooser_entry->completion_store);
547 }
548 
549 /* Callback when the current folder finishes loading */
550 static void
finished_loading_cb(GtkFileSystemModel * model,GError * error,GtkFileChooserEntry * chooser_entry)551 finished_loading_cb (GtkFileSystemModel  *model,
552                      GError              *error,
553 		     GtkFileChooserEntry *chooser_entry)
554 {
555   GtkEntryCompletion *completion;
556 
557   chooser_entry->current_folder_loaded = TRUE;
558 
559   if (error)
560     {
561       discard_completion_store (chooser_entry);
562       set_complete_on_load (chooser_entry, FALSE);
563       return;
564     }
565 
566   if (chooser_entry->complete_on_load)
567     explicitly_complete (chooser_entry);
568 
569   gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL);
570 
571   completion = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
572   update_inline_completion (chooser_entry);
573 
574   if (gtk_widget_has_focus (GTK_WIDGET (chooser_entry)))
575     {
576       gtk_entry_completion_complete (completion);
577       gtk_entry_completion_insert_prefix (completion);
578     }
579 }
580 
581 static void
set_completion_folder(GtkFileChooserEntry * chooser_entry,GFile * folder_file,char * dir_part)582 set_completion_folder (GtkFileChooserEntry *chooser_entry,
583                        GFile               *folder_file,
584 		       char                *dir_part)
585 {
586   if (folder_file &&
587       chooser_entry->local_only
588       && !_gtk_file_has_native_path (folder_file))
589     folder_file = NULL;
590 
591   if (((chooser_entry->current_folder_file
592 	&& folder_file
593 	&& g_file_equal (folder_file, chooser_entry->current_folder_file))
594        || chooser_entry->current_folder_file == folder_file)
595       && g_strcmp0 (dir_part, chooser_entry->dir_part) == 0)
596     {
597       return;
598     }
599 
600   if (chooser_entry->current_folder_file)
601     {
602       g_object_unref (chooser_entry->current_folder_file);
603       chooser_entry->current_folder_file = NULL;
604     }
605 
606   g_free (chooser_entry->dir_part);
607   chooser_entry->dir_part = g_strdup (dir_part);
608 
609   chooser_entry->current_folder_loaded = FALSE;
610 
611   discard_completion_store (chooser_entry);
612 
613   if (folder_file)
614     {
615       chooser_entry->current_folder_file = g_object_ref (folder_file);
616       populate_completion_store (chooser_entry);
617     }
618 }
619 
620 static void
refresh_current_folder_and_file_part(GtkFileChooserEntry * chooser_entry)621 refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry)
622 {
623   GFile *folder_file;
624   char *text, *last_slash, *old_file_part;
625   char *dir_part;
626 
627   old_file_part = chooser_entry->file_part;
628 
629   text = gtk_file_chooser_entry_get_completion_text (chooser_entry);
630 
631   last_slash = strrchr (text, G_DIR_SEPARATOR);
632   if (last_slash)
633     {
634       dir_part = g_strndup (text, last_slash - text + 1);
635       chooser_entry->file_part = g_strdup (last_slash + 1);
636     }
637   else
638     {
639       dir_part = g_strdup ("");
640       chooser_entry->file_part = g_strdup (text);
641     }
642 
643   folder_file = gtk_file_chooser_get_directory_for_text (chooser_entry, text);
644 
645   set_completion_folder (chooser_entry, folder_file, dir_part);
646 
647   if (folder_file)
648     g_object_unref (folder_file);
649 
650   g_free (dir_part);
651 
652   if (chooser_entry->completion_store &&
653       (g_strcmp0 (old_file_part, chooser_entry->file_part) != 0))
654     {
655       GtkFileFilter *filter;
656       char *pattern;
657 
658       filter = gtk_file_filter_new ();
659       pattern = g_strconcat (chooser_entry->file_part, "*", NULL);
660       gtk_file_filter_add_pattern (filter, pattern);
661 
662       g_object_ref_sink (filter);
663 
664       _gtk_file_system_model_set_filter (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
665                                          filter);
666 
667       g_free (pattern);
668       g_object_unref (filter);
669     }
670 
671   g_free (text);
672   g_free (old_file_part);
673 }
674 
675 #ifdef G_OS_WIN32
676 static gint
insert_text_callback(GtkFileChooserEntry * chooser_entry,const gchar * new_text,gint new_text_length,gint * position,gpointer user_data)677 insert_text_callback (GtkFileChooserEntry *chooser_entry,
678 		      const gchar	  *new_text,
679 		      gint       	   new_text_length,
680 		      gint       	  *position,
681 		      gpointer   	   user_data)
682 {
683   const gchar *colon = memchr (new_text, ':', new_text_length);
684   gint i;
685 
686   /* Disallow these characters altogether */
687   for (i = 0; i < new_text_length; i++)
688     {
689       if (new_text[i] == '<' ||
690 	  new_text[i] == '>' ||
691 	  new_text[i] == '"' ||
692 	  new_text[i] == '|' ||
693 	  new_text[i] == '*' ||
694 	  new_text[i] == '?')
695 	break;
696     }
697 
698   if (i < new_text_length ||
699       /* Disallow entering text that would cause a colon to be anywhere except
700        * after a drive letter.
701        */
702       (colon != NULL &&
703        *position + (colon - new_text) != 1) ||
704       (new_text_length > 0 &&
705        *position <= 1 &&
706        gtk_entry_get_text_length (GTK_ENTRY (chooser_entry)) >= 2 &&
707        gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':'))
708     {
709       gtk_widget_error_bell (GTK_WIDGET (chooser_entry));
710       g_signal_stop_emission_by_name (chooser_entry, "insert_text");
711       return FALSE;
712     }
713 
714   return TRUE;
715 }
716 
717 static void
delete_text_callback(GtkFileChooserEntry * chooser_entry,gint start_pos,gint end_pos,gpointer user_data)718 delete_text_callback (GtkFileChooserEntry *chooser_entry,
719 		      gint                 start_pos,
720 		      gint                 end_pos,
721 		      gpointer             user_data)
722 {
723   /* If deleting a drive letter, delete the colon, too */
724   if (start_pos == 0 && end_pos == 1 &&
725       gtk_entry_get_text_length (GTK_ENTRY (chooser_entry)) >= 2 &&
726       gtk_entry_get_text (GTK_ENTRY (chooser_entry))[1] == ':')
727     {
728       g_signal_handlers_block_by_func (chooser_entry,
729 				       G_CALLBACK (delete_text_callback),
730 				       user_data);
731       gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), 0, 1);
732       g_signal_handlers_unblock_by_func (chooser_entry,
733 					 G_CALLBACK (delete_text_callback),
734 					 user_data);
735     }
736 }
737 #endif
738 
739 /**
740  * _gtk_file_chooser_entry_new:
741  * @eat_tabs: If %FALSE, allow focus navigation with the tab key.
742  *
743  * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
744  * is an internal implementation widget for the GTK+ file chooser
745  * which is an entry with completion with respect to a
746  * #GtkFileSystem object.
747  *
748  * Return value: the newly created #GtkFileChooserEntry
749  **/
750 GtkWidget *
_gtk_file_chooser_entry_new(gboolean eat_tabs)751 _gtk_file_chooser_entry_new (gboolean       eat_tabs)
752 {
753   GtkFileChooserEntry *chooser_entry;
754 
755   chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
756   chooser_entry->eat_tabs = (eat_tabs != FALSE);
757 
758   return GTK_WIDGET (chooser_entry);
759 }
760 
761 /**
762  * _gtk_file_chooser_entry_set_base_folder:
763  * @chooser_entry: a #GtkFileChooserEntry
764  * @file: file for a folder in the chooser entries current file system.
765  *
766  * Sets the folder with respect to which completions occur.
767  **/
768 void
_gtk_file_chooser_entry_set_base_folder(GtkFileChooserEntry * chooser_entry,GFile * file)769 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
770 					 GFile               *file)
771 {
772   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
773   g_return_if_fail (file == NULL || G_IS_FILE (file));
774 
775   if (chooser_entry->base_folder == file ||
776       (file != NULL && chooser_entry->base_folder != NULL
777        && g_file_equal (chooser_entry->base_folder, file)))
778     return;
779 
780   if (file)
781     g_object_ref (file);
782 
783   if (chooser_entry->base_folder)
784     g_object_unref (chooser_entry->base_folder);
785 
786   chooser_entry->base_folder = file;
787 
788   refresh_current_folder_and_file_part (chooser_entry);
789 }
790 
791 /**
792  * _gtk_file_chooser_entry_get_current_folder:
793  * @chooser_entry: a #GtkFileChooserEntry
794  *
795  * Gets the current folder for the #GtkFileChooserEntry. If the
796  * user has only entered a filename, this will be in the base folder
797  * (see _gtk_file_chooser_entry_set_base_folder()), but if the
798  * user has entered a relative or absolute path, then it will
799  * be different.  If the user has entered unparsable text, or text which
800  * the entry cannot handle, this will return %NULL.
801  *
802  * Return value: the file for the current folder - you must g_object_unref()
803  *   the value after use.
804  **/
805 GFile *
_gtk_file_chooser_entry_get_current_folder(GtkFileChooserEntry * chooser_entry)806 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
807 {
808   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry), NULL);
809 
810   return gtk_file_chooser_get_directory_for_text (chooser_entry,
811                                                   gtk_entry_get_text (GTK_ENTRY (chooser_entry)));
812 }
813 
814 /**
815  * _gtk_file_chooser_entry_get_file_part:
816  * @chooser_entry: a #GtkFileChooserEntry
817  *
818  * Gets the non-folder portion of whatever the user has entered
819  * into the file selector. What is returned is a UTF-8 string,
820  * and if a filename path is needed, g_file_get_child_for_display_name()
821  * must be used
822   *
823  * Return value: the entered filename - this value is owned by the
824  *  chooser entry and must not be modified or freed.
825  **/
826 const gchar *
_gtk_file_chooser_entry_get_file_part(GtkFileChooserEntry * chooser_entry)827 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
828 {
829   const char *last_slash, *text;
830 
831   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry), NULL);
832 
833   text = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
834   last_slash = strrchr (text, G_DIR_SEPARATOR);
835   if (last_slash)
836     return last_slash + 1;
837   else if (is_directory_shortcut (text))
838     return "";
839   else
840     return text;
841 }
842 
843 /**
844  * _gtk_file_chooser_entry_set_action:
845  * @chooser_entry: a #GtkFileChooserEntry
846  * @action: the action which is performed by the file selector using this entry
847  *
848  * Sets action which is performed by the file selector using this entry.
849  * The #GtkFileChooserEntry will use different completion strategies for
850  * different actions.
851  **/
852 void
_gtk_file_chooser_entry_set_action(GtkFileChooserEntry * chooser_entry,GtkFileChooserAction action)853 _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry,
854 				    GtkFileChooserAction action)
855 {
856   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
857 
858   if (chooser_entry->action != action)
859     {
860       GtkEntryCompletion *comp;
861 
862       chooser_entry->action = action;
863 
864       comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry));
865 
866       /* FIXME: do we need to actually set the following? */
867 
868       switch (action)
869 	{
870 	case GTK_FILE_CHOOSER_ACTION_OPEN:
871 	case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
872 	  gtk_entry_completion_set_popup_single_match (comp, FALSE);
873 	  break;
874 	case GTK_FILE_CHOOSER_ACTION_SAVE:
875 	case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER:
876 	  gtk_entry_completion_set_popup_single_match (comp, TRUE);
877 	  break;
878 	}
879 
880       if (chooser_entry->completion_store)
881         _gtk_file_system_model_set_show_files (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
882                                                action == GTK_FILE_CHOOSER_ACTION_OPEN ||
883                                                action == GTK_FILE_CHOOSER_ACTION_SAVE);
884 
885       update_inline_completion (chooser_entry);
886     }
887 }
888 
889 
890 /**
891  * _gtk_file_chooser_entry_get_action:
892  * @chooser_entry: a #GtkFileChooserEntry
893  *
894  * Gets the action for this entry.
895  *
896  * Returns: the action
897  **/
898 GtkFileChooserAction
_gtk_file_chooser_entry_get_action(GtkFileChooserEntry * chooser_entry)899 _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry)
900 {
901   g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry),
902 			GTK_FILE_CHOOSER_ACTION_OPEN);
903 
904   return chooser_entry->action;
905 }
906 
907 gboolean
_gtk_file_chooser_entry_get_is_folder(GtkFileChooserEntry * chooser_entry,GFile * file)908 _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry,
909 				       GFile               *file)
910 {
911   GtkTreeIter iter;
912   GFileInfo *info;
913 
914   if (chooser_entry->completion_store == NULL ||
915       !_gtk_file_system_model_get_iter_for_file (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
916                                                  &iter,
917                                                  file))
918     return FALSE;
919 
920   info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store),
921                                           &iter);
922 
923   return _gtk_file_info_consider_as_directory (info);
924 }
925 
926 
927 /*
928  * _gtk_file_chooser_entry_select_filename:
929  * @chooser_entry: a #GtkFileChooserEntry
930  *
931  * Selects the filename (without the extension) for user edition.
932  */
933 void
_gtk_file_chooser_entry_select_filename(GtkFileChooserEntry * chooser_entry)934 _gtk_file_chooser_entry_select_filename (GtkFileChooserEntry *chooser_entry)
935 {
936   const gchar *str, *ext;
937   glong len = -1;
938 
939   if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE)
940     {
941       str = gtk_entry_get_text (GTK_ENTRY (chooser_entry));
942       ext = g_strrstr (str, ".");
943 
944       if (ext)
945        len = g_utf8_pointer_to_offset (str, ext);
946     }
947 
948   gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, (gint) len);
949 }
950 
951 void
_gtk_file_chooser_entry_set_local_only(GtkFileChooserEntry * chooser_entry,gboolean local_only)952 _gtk_file_chooser_entry_set_local_only (GtkFileChooserEntry *chooser_entry,
953                                         gboolean             local_only)
954 {
955   chooser_entry->local_only = local_only;
956   refresh_current_folder_and_file_part (chooser_entry);
957 }
958 
959 gboolean
_gtk_file_chooser_entry_get_local_only(GtkFileChooserEntry * chooser_entry)960 _gtk_file_chooser_entry_get_local_only (GtkFileChooserEntry *chooser_entry)
961 {
962   return chooser_entry->local_only;
963 }
964