1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*-
3  * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
4  * Copyright (c) 2009-2010 Jannis Pohlmann <jannis@xfce.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  * The icon code is based on ideas from SexyIconEntry, which was written by
22  * Christian Hammond <chipx86@chipx86.com>.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 #ifdef HAVE_ERRNO_H
30 #include <errno.h>
31 #endif
32 #ifdef HAVE_MEMORY_H
33 #include <memory.h>
34 #endif
35 #ifdef HAVE_STRING_H
36 #include <string.h>
37 #endif
38 
39 #include <gdk/gdkkeysyms.h>
40 
41 #include <thunar/thunar-gobject-extensions.h>
42 #include <thunar/thunar-icon-factory.h>
43 #include <thunar/thunar-icon-renderer.h>
44 #include <thunar/thunar-list-model.h>
45 #include <thunar/thunar-path-entry.h>
46 #include <thunar/thunar-private.h>
47 #include <thunar/thunar-util.h>
48 
49 
50 
51 #define ICON_MARGIN (2)
52 
53 
54 
55 enum
56 {
57   PROP_0,
58   PROP_CURRENT_FILE,
59 };
60 
61 
62 
63 static void     thunar_path_entry_editable_init                 (GtkEditableInterface *iface);
64 static void     thunar_path_entry_finalize                      (GObject              *object);
65 static void     thunar_path_entry_get_property                  (GObject              *object,
66                                                                  guint                 prop_id,
67                                                                  GValue               *value,
68                                                                  GParamSpec           *pspec);
69 static void     thunar_path_entry_set_property                  (GObject              *object,
70                                                                  guint                 prop_id,
71                                                                  const GValue         *value,
72                                                                  GParamSpec           *pspec);
73 static gboolean thunar_path_entry_focus                         (GtkWidget            *widget,
74                                                                  GtkDirectionType      direction);
75 static void     thunar_path_entry_icon_press_event              (GtkEntry            *entry,
76                                                                  GtkEntryIconPosition icon_pos,
77                                                                  GdkEventButton      *event,
78                                                                  gpointer             userdata);
79 static void     thunar_path_entry_icon_release_event            (GtkEntry            *entry,
80                                                                  GtkEntryIconPosition icon_pos,
81                                                                  GdkEventButton      *event,
82                                                                  gpointer             user_data);
83 static gboolean thunar_path_entry_motion_notify_event           (GtkWidget            *widget,
84                                                                  GdkEventMotion       *event);
85 static gboolean thunar_path_entry_key_press_event               (GtkWidget            *widget,
86                                                                  GdkEventKey          *event);
87 static void     thunar_path_entry_drag_data_get                 (GtkWidget            *widget,
88                                                                  GdkDragContext       *context,
89                                                                  GtkSelectionData     *selection_data,
90                                                                  guint                 info,
91                                                                  guint                 timestamp);
92 static void     thunar_path_entry_activate                      (GtkEntry             *entry);
93 static void     thunar_path_entry_changed                       (GtkEditable          *editable);
94 static void     thunar_path_entry_update_icon                   (ThunarPathEntry      *path_entry);
95 static void     thunar_path_entry_do_insert_text                (GtkEditable          *editable,
96                                                                  const gchar          *new_text,
97                                                                  gint                  new_text_length,
98                                                                  gint                 *position);
99 static void     thunar_path_entry_clear_completion              (ThunarPathEntry      *path_entry);
100 static void     thunar_path_entry_common_prefix_append          (ThunarPathEntry      *path_entry,
101                                                                  gboolean              highlight);
102 static void     thunar_path_entry_common_prefix_lookup          (ThunarPathEntry      *path_entry,
103                                                                  gchar               **prefix_return,
104                                                                  ThunarFile          **file_return);
105 static gboolean thunar_path_entry_match_func                    (GtkEntryCompletion   *completion,
106                                                                  const gchar          *key,
107                                                                  GtkTreeIter          *iter,
108                                                                  gpointer              user_data);
109 static gboolean thunar_path_entry_match_selected                (GtkEntryCompletion   *completion,
110                                                                  GtkTreeModel         *model,
111                                                                  GtkTreeIter          *iter,
112                                                                  gpointer              user_data);
113 static gboolean thunar_path_entry_parse                         (ThunarPathEntry      *path_entry,
114                                                                  gchar               **folder_part,
115                                                                  gchar               **file_part,
116                                                                  GError              **error);
117 static void     thunar_path_entry_queue_check_completion        (ThunarPathEntry      *path_entry);
118 static gboolean thunar_path_entry_check_completion_idle         (gpointer              user_data);
119 static void     thunar_path_entry_check_completion_idle_destroy (gpointer              user_data);
120 
121 
122 
123 struct _ThunarPathEntryClass
124 {
125   GtkEntryClass __parent__;
126 };
127 
128 struct _ThunarPathEntry
129 {
130   GtkEntry __parent__;
131 
132   ThunarIconFactory *icon_factory;
133   ThunarFile        *current_folder;
134   ThunarFile        *current_file;
135   GFile             *working_directory;
136 
137   guint              drag_button;
138   gint               drag_x;
139   gint               drag_y;
140 
141   /* auto completion support */
142   guint              in_change : 1;
143   guint              has_completion : 1;
144   guint              check_completion_idle_id;
145 };
146 
147 
148 
149 static const GtkTargetEntry drag_targets[] =
150 {
151   { "text/uri-list", 0, 0, },
152 };
153 
154 
155 
156 static GtkEditableInterface *thunar_path_entry_editable_parent_iface;
157 
158 
159 
G_DEFINE_TYPE_WITH_CODE(ThunarPathEntry,thunar_path_entry,GTK_TYPE_ENTRY,G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,thunar_path_entry_editable_init))160 G_DEFINE_TYPE_WITH_CODE (ThunarPathEntry, thunar_path_entry, GTK_TYPE_ENTRY,
161     G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, thunar_path_entry_editable_init))
162 
163 
164 
165 static void
166 thunar_path_entry_class_init (ThunarPathEntryClass *klass)
167 {
168   GtkWidgetClass *gtkwidget_class;
169   GtkEntryClass  *gtkentry_class;
170   GObjectClass   *gobject_class;
171 
172   gobject_class = G_OBJECT_CLASS (klass);
173   gobject_class->finalize = thunar_path_entry_finalize;
174   gobject_class->get_property = thunar_path_entry_get_property;
175   gobject_class->set_property = thunar_path_entry_set_property;
176 
177   gtkwidget_class = GTK_WIDGET_CLASS (klass);
178   gtkwidget_class->focus = thunar_path_entry_focus;
179   gtkwidget_class->motion_notify_event = thunar_path_entry_motion_notify_event;
180   gtkwidget_class->drag_data_get = thunar_path_entry_drag_data_get;
181 
182   gtkentry_class = GTK_ENTRY_CLASS (klass);
183   gtkentry_class->activate = thunar_path_entry_activate;
184 
185   /**
186    * ThunarPathEntry:current-file:
187    *
188    * The #ThunarFile currently displayed by the path entry or %NULL.
189    **/
190   g_object_class_install_property (gobject_class,
191                                    PROP_CURRENT_FILE,
192                                    g_param_spec_object ("current-file",
193                                                         "current-file",
194                                                         "current-file",
195                                                         THUNAR_TYPE_FILE,
196                                                         EXO_PARAM_READWRITE));
197 
198   /**
199    * ThunarPathEntry:icon-size:
200    *
201    * The preferred size of the icon displayed in the path entry.
202    **/
203   gtk_widget_class_install_style_property (gtkwidget_class,
204                                            g_param_spec_int ("icon-size",
205                                                              _("Icon size"),
206                                                              _("The icon size for the path entry"),
207                                                              1, G_MAXINT, 16, EXO_PARAM_READABLE));
208 }
209 
210 
211 
212 static void
thunar_path_entry_editable_init(GtkEditableInterface * iface)213 thunar_path_entry_editable_init (GtkEditableInterface *iface)
214 {
215   thunar_path_entry_editable_parent_iface = g_type_interface_peek_parent (iface);
216 
217   iface->changed = thunar_path_entry_changed;
218   iface->do_insert_text = thunar_path_entry_do_insert_text;
219 }
220 
221 
222 
223 static void
thunar_path_entry_init(ThunarPathEntry * path_entry)224 thunar_path_entry_init (ThunarPathEntry *path_entry)
225 {
226   GtkEntryCompletion *completion;
227   GtkCellRenderer    *renderer;
228   ThunarListModel    *store;
229 
230   path_entry->check_completion_idle_id = 0;
231   path_entry->working_directory = NULL;
232 
233   /* allocate a new entry completion for the given model */
234   completion = gtk_entry_completion_new ();
235   gtk_entry_completion_set_popup_single_match (completion, FALSE);
236   gtk_entry_completion_set_match_func (completion, thunar_path_entry_match_func, path_entry, NULL);
237   g_signal_connect (G_OBJECT (completion), "match-selected", G_CALLBACK (thunar_path_entry_match_selected), path_entry);
238 
239   /* add the icon renderer to the entry completion */
240   renderer = g_object_new (THUNAR_TYPE_ICON_RENDERER, "size", 16, NULL);
241   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, FALSE);
242   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), renderer, "file", THUNAR_COLUMN_FILE);
243 
244   /* add the text renderer to the entry completion */
245   renderer = gtk_cell_renderer_text_new ();
246   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
247   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), renderer, "text", THUNAR_COLUMN_NAME);
248 
249   /* allocate a new list mode for the completion */
250   store = thunar_list_model_new ();
251   thunar_list_model_set_show_hidden (store, TRUE);
252   thunar_list_model_set_folders_first (store, TRUE);
253   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), THUNAR_COLUMN_FILE_NAME, GTK_SORT_ASCENDING);
254   gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (store));
255   g_object_unref (G_OBJECT (store));
256 
257   /* need to connect the "key-press-event" before the GtkEntry class connects the completion signals, so
258    * we get the Tab key before its handled as part of the completion stuff.
259    */
260   g_signal_connect (G_OBJECT (path_entry), "key-press-event", G_CALLBACK (thunar_path_entry_key_press_event), NULL);
261 
262   /* setup the new completion */
263   gtk_entry_set_completion (GTK_ENTRY (path_entry), completion);
264 
265   /* cleanup */
266   g_object_unref (G_OBJECT (completion));
267 
268   /* clear the auto completion whenever the cursor is moved manually or the selection is changed manually */
269   g_signal_connect (G_OBJECT (path_entry), "notify::cursor-position", G_CALLBACK (thunar_path_entry_clear_completion), NULL);
270   g_signal_connect (G_OBJECT (path_entry), "notify::selection-bound", G_CALLBACK (thunar_path_entry_clear_completion), NULL);
271 
272   /* connect the icon signals */
273   g_signal_connect (G_OBJECT (path_entry), "icon-press", G_CALLBACK (thunar_path_entry_icon_press_event), NULL);
274   g_signal_connect (G_OBJECT (path_entry), "icon-release", G_CALLBACK (thunar_path_entry_icon_release_event), NULL);
275 }
276 
277 
278 
279 static void
thunar_path_entry_finalize(GObject * object)280 thunar_path_entry_finalize (GObject *object)
281 {
282   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (object);
283 
284   /* release factory */
285   if (path_entry->icon_factory != NULL)
286     g_object_unref (path_entry->icon_factory);
287 
288   /* release the current-folder reference */
289   if (G_LIKELY (path_entry->current_folder != NULL))
290     g_object_unref (G_OBJECT (path_entry->current_folder));
291 
292   /* release the current-file reference */
293   if (G_LIKELY (path_entry->current_file != NULL))
294     {
295       g_signal_handlers_disconnect_by_func (G_OBJECT (path_entry->current_file), thunar_path_entry_set_current_file, path_entry);
296       g_object_unref (G_OBJECT (path_entry->current_file));
297     }
298 
299   /* release the working directory */
300   if (G_LIKELY (path_entry->working_directory != NULL))
301     g_object_unref (G_OBJECT (path_entry->working_directory));
302 
303   /* drop the check_completion_idle source */
304   if (G_UNLIKELY (path_entry->check_completion_idle_id != 0))
305     g_source_remove (path_entry->check_completion_idle_id);
306 
307   (*G_OBJECT_CLASS (thunar_path_entry_parent_class)->finalize) (object);
308 }
309 
310 
311 
312 static void
thunar_path_entry_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)313 thunar_path_entry_get_property (GObject    *object,
314                                 guint       prop_id,
315                                 GValue     *value,
316                                 GParamSpec *pspec)
317 {
318   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (object);
319 
320   switch (prop_id)
321     {
322     case PROP_CURRENT_FILE:
323       g_value_set_object (value, thunar_path_entry_get_current_file (path_entry));
324       break;
325 
326     default:
327       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
328       break;
329     }
330 }
331 
332 
333 
334 static void
thunar_path_entry_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)335 thunar_path_entry_set_property (GObject      *object,
336                                 guint         prop_id,
337                                 const GValue *value,
338                                 GParamSpec   *pspec)
339 {
340   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (object);
341 
342   switch (prop_id)
343     {
344     case PROP_CURRENT_FILE:
345       thunar_path_entry_set_current_file (path_entry, g_value_get_object (value));
346       break;
347 
348     default:
349       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
350       break;
351     }
352 }
353 
354 
355 
356 static gboolean
thunar_path_entry_focus(GtkWidget * widget,GtkDirectionType direction)357 thunar_path_entry_focus (GtkWidget       *widget,
358                          GtkDirectionType direction)
359 {
360   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (widget);
361   GdkModifierType  state;
362   gboolean         control_pressed;
363 
364   /* determine whether control is pressed */
365   control_pressed = (gtk_get_current_event_state (&state) && (state & GDK_CONTROL_MASK) != 0);
366 
367   /* evil hack, but works for GtkFileChooserEntry, so who cares :-) */
368   if ((direction == GTK_DIR_TAB_FORWARD) && (gtk_widget_has_focus (widget)) && !control_pressed)
369     {
370       /* if we don't have a completion and the cursor is at the end of the line, we just insert the common prefix */
371       if (!path_entry->has_completion && gtk_editable_get_position (GTK_EDITABLE (path_entry)) == gtk_entry_get_text_length (GTK_ENTRY (path_entry)))
372         thunar_path_entry_common_prefix_append (path_entry, FALSE);
373 
374       /* place the cursor at the end */
375       gtk_editable_set_position (GTK_EDITABLE (path_entry), gtk_entry_get_text_length (GTK_ENTRY (path_entry)));
376 
377       return TRUE;
378     }
379   else
380     return (*GTK_WIDGET_CLASS (thunar_path_entry_parent_class)->focus) (widget, direction);
381 }
382 
383 
384 
385 static void
thunar_path_entry_icon_press_event(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEventButton * event,gpointer userdata)386 thunar_path_entry_icon_press_event (GtkEntry            *entry,
387                                     GtkEntryIconPosition icon_pos,
388                                     GdkEventButton      *event,
389                                     gpointer             userdata)
390 {
391   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (entry);
392 
393   if (event->button == 1 && icon_pos == GTK_ENTRY_ICON_PRIMARY)
394     {
395       /* consume the event */
396       path_entry->drag_button = event->button;
397       path_entry->drag_x = event->x;
398       path_entry->drag_y = event->y;
399     }
400 }
401 
402 
403 
404 static void
thunar_path_entry_icon_release_event(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEventButton * event,gpointer user_data)405 thunar_path_entry_icon_release_event (GtkEntry            *entry,
406                                       GtkEntryIconPosition icon_pos,
407                                       GdkEventButton      *event,
408                                       gpointer             user_data)
409 {
410   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (entry);
411 
412   if (event->button == path_entry->drag_button && icon_pos == GTK_ENTRY_ICON_PRIMARY)
413     {
414       /* reset the drag button state */
415       path_entry->drag_button = 0;
416     }
417 }
418 
419 
420 
421 static gboolean
thunar_path_entry_motion_notify_event(GtkWidget * widget,GdkEventMotion * event)422 thunar_path_entry_motion_notify_event (GtkWidget      *widget,
423                                        GdkEventMotion *event)
424 {
425   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (widget);
426   GdkDragContext  *context;
427   GtkTargetList   *target_list;
428   GdkPixbuf       *icon;
429   gint             size;
430 
431   if (path_entry->drag_button > 0
432       && path_entry->current_file != NULL
433       /*FIXME && event->window == gtk_entry_get_icon_window (GTK_ENTRY (widget), GTK_ENTRY_ICON_PRIMARY)*/
434       && gtk_drag_check_threshold (widget, path_entry->drag_x, path_entry->drag_y, event->x, event->y))
435     {
436       /* create the drag context */
437       target_list = gtk_target_list_new (drag_targets, G_N_ELEMENTS (drag_targets));
438       context = gtk_drag_begin_with_coordinates (widget, target_list,
439                                                  GDK_ACTION_COPY |
440                                                  GDK_ACTION_LINK,
441                                                  path_entry->drag_button,
442                                                  (GdkEvent *) event, -1, -1);
443       gtk_target_list_unref (target_list);
444 
445       /* setup the drag icon (atleast 24px) */
446       gtk_widget_style_get (widget, "icon-size", &size, NULL);
447       icon = thunar_icon_factory_load_file_icon (path_entry->icon_factory,
448                                                  path_entry->current_file,
449                                                  THUNAR_FILE_ICON_STATE_DEFAULT,
450                                                  MAX (size, 16));
451       if (G_LIKELY (icon != NULL))
452         {
453           gtk_drag_set_icon_pixbuf (context, icon, 0, 0);
454           g_object_unref (G_OBJECT (icon));
455         }
456 
457       /* reset the drag button state */
458       path_entry->drag_button = 0;
459 
460       return TRUE;
461     }
462 
463   return (*GTK_WIDGET_CLASS (thunar_path_entry_parent_class)->motion_notify_event) (widget, event);
464 }
465 
466 
467 
468 static gboolean
thunar_path_entry_key_press_event(GtkWidget * widget,GdkEventKey * event)469 thunar_path_entry_key_press_event (GtkWidget   *widget,
470                                    GdkEventKey *event)
471 {
472   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (widget);
473 
474   /* check if we have a tab key press here and control is not pressed */
475   if (G_UNLIKELY (event->keyval == GDK_KEY_Tab && (event->state & GDK_CONTROL_MASK) == 0))
476     {
477       /* if we don't have a completion and the cursor is at the end of the line, we just insert the common prefix */
478       if (!path_entry->has_completion && gtk_editable_get_position (GTK_EDITABLE (path_entry)) == gtk_entry_get_text_length (GTK_ENTRY (path_entry)))
479         thunar_path_entry_common_prefix_append (path_entry, FALSE);
480 
481       /* place the cursor at the end */
482       gtk_editable_set_position (GTK_EDITABLE (path_entry), gtk_entry_get_text_length (GTK_ENTRY (path_entry)));
483 
484       /* emit "changed", so the completion window is popped up */
485       g_signal_emit_by_name (G_OBJECT (path_entry), "changed", 0);
486 
487       /* we handled the event */
488       return TRUE;
489     }
490 
491   return FALSE;
492 }
493 
494 
495 
496 static void
thunar_path_entry_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint timestamp)497 thunar_path_entry_drag_data_get (GtkWidget        *widget,
498                                  GdkDragContext   *context,
499                                  GtkSelectionData *selection_data,
500                                  guint             info,
501                                  guint             timestamp)
502 {
503   ThunarPathEntry  *path_entry = THUNAR_PATH_ENTRY (widget);
504   GList             file_list;
505   gchar           **uris;
506 
507   /* verify that we actually display a path */
508   if (G_LIKELY (path_entry->current_file != NULL))
509     {
510       /* transform the path for the current file into an uri string list */
511       file_list.next = file_list.prev = NULL;
512       file_list.data = thunar_file_get_file (path_entry->current_file);
513 
514       /* setup the uri list for the drag selection */
515       uris = thunar_g_file_list_to_stringv (&file_list);
516       gtk_selection_data_set_uris (selection_data, uris);
517       g_strfreev (uris);
518     }
519 }
520 
521 
522 
523 static void
thunar_path_entry_activate(GtkEntry * entry)524 thunar_path_entry_activate (GtkEntry *entry)
525 {
526   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (entry);
527 
528   if (G_LIKELY (path_entry->has_completion))
529     {
530       /* place cursor at the end of the text if we have completion set */
531       gtk_editable_set_position (GTK_EDITABLE (path_entry), -1);
532     }
533 
534   /* emit the "activate" signal */
535   (*GTK_ENTRY_CLASS (thunar_path_entry_parent_class)->activate) (entry);
536 }
537 
538 
539 
540 static void
thunar_path_entry_changed(GtkEditable * editable)541 thunar_path_entry_changed (GtkEditable *editable)
542 {
543   GtkEntryCompletion *completion;
544   ThunarPathEntry    *path_entry = THUNAR_PATH_ENTRY (editable);
545   ThunarFolder       *folder;
546   GtkTreeModel       *model;
547   const gchar        *text;
548   gchar              *escaped_text;
549   ThunarFile         *current_folder;
550   ThunarFile         *current_file;
551   GFile              *folder_path = NULL;
552   GFile              *file_path = NULL;
553   gchar              *folder_part = NULL;
554   gchar              *file_part = NULL;
555   gboolean            update_icon = FALSE;
556 
557   /* check if we should ignore this event */
558   if (G_UNLIKELY (path_entry->in_change))
559     return;
560 
561   /* parse the entered string (handling URIs properly) */
562   text = gtk_entry_get_text (GTK_ENTRY (path_entry));
563   if (G_UNLIKELY (exo_str_looks_like_an_uri (text)))
564     {
565       /* try to parse the URI text */
566       escaped_text = g_uri_escape_string (text, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
567       file_path = g_file_new_for_uri (escaped_text);
568       g_free (escaped_text);
569 
570       /* use the same file if the text assumes we're in a directory */
571       if (g_str_has_suffix (text, "/"))
572         folder_path = G_FILE (g_object_ref (G_OBJECT (file_path)));
573       else
574         folder_path = g_file_get_parent (file_path);
575     }
576   else if (thunar_path_entry_parse (path_entry, &folder_part, &file_part, NULL))
577     {
578       /* determine the folder path */
579       folder_path = g_file_new_for_path (folder_part);
580 
581       /* determine the relative file path */
582       if (G_LIKELY (*file_part != '\0'))
583         file_path = g_file_resolve_relative_path (folder_path, file_part);
584       else
585         file_path = g_object_ref (folder_path);
586 
587       /* cleanup the part strings */
588       g_free (folder_part);
589       g_free (file_part);
590     }
591 
592   /* determine new current file/folder from the paths */
593   current_folder = (folder_path != NULL) ? thunar_file_get (folder_path, NULL) : NULL;
594   current_file = (file_path != NULL) ? thunar_file_get (file_path, NULL) : NULL;
595 
596   /* determine the entry completion */
597   completion = gtk_entry_get_completion (GTK_ENTRY (path_entry));
598 
599   /* update the current folder if required */
600   if (current_folder != path_entry->current_folder)
601     {
602       /* take a reference on the current folder */
603       if (G_LIKELY (path_entry->current_folder != NULL))
604         g_object_unref (G_OBJECT (path_entry->current_folder));
605       path_entry->current_folder = current_folder;
606       if (G_LIKELY (current_folder != NULL))
607         g_object_ref (G_OBJECT (current_folder));
608 
609       /* try to open the current-folder file as folder */
610       if (current_folder != NULL && thunar_file_is_directory (current_folder))
611         folder = thunar_folder_get_for_file (current_folder);
612       else
613         folder = NULL;
614 
615       /* set the new folder for the completion model, but disconnect the model from the
616        * completion first, because GtkEntryCompletion has become very slow recently when
617        * updating the model being used (https://bugzilla.xfce.org/show_bug.cgi?id=1681).
618        */
619       model = gtk_entry_completion_get_model (completion);
620       g_object_ref (G_OBJECT (model));
621       gtk_entry_completion_set_model (completion, NULL);
622       thunar_list_model_set_folder (THUNAR_LIST_MODEL (model), folder);
623       gtk_entry_completion_set_model (completion, model);
624       g_object_unref (G_OBJECT (model));
625 
626       /* cleanup */
627       if (G_LIKELY (folder != NULL))
628         g_object_unref (G_OBJECT (folder));
629 
630       /* we most likely need a new icon */
631       update_icon = TRUE;
632     }
633 
634   /* update the current file if required */
635   if (current_file != path_entry->current_file)
636     {
637       if (G_UNLIKELY (path_entry->current_file != NULL))
638         {
639           g_signal_handlers_disconnect_by_func (G_OBJECT (path_entry->current_file), thunar_path_entry_set_current_file, path_entry);
640           g_object_unref (G_OBJECT (path_entry->current_file));
641         }
642       path_entry->current_file = current_file;
643       if (G_UNLIKELY (current_file != NULL))
644         {
645           g_object_ref (G_OBJECT (current_file));
646           g_signal_connect_swapped (G_OBJECT (current_file), "changed", G_CALLBACK (thunar_path_entry_set_current_file), path_entry);
647         }
648       g_object_notify (G_OBJECT (path_entry), "current-file");
649 
650       /* we most likely need a new icon */
651       update_icon = TRUE;
652     }
653 
654   if (update_icon)
655     thunar_path_entry_update_icon (path_entry);
656 
657   /* cleanup */
658   if (G_LIKELY (current_folder != NULL))
659     g_object_unref (G_OBJECT (current_folder));
660   if (G_LIKELY (current_file != NULL))
661     g_object_unref (G_OBJECT (current_file));
662   if (G_LIKELY (folder_path != NULL))
663     g_object_unref (folder_path);
664   if (G_LIKELY (file_path != NULL))
665     g_object_unref (file_path);
666 }
667 
668 
669 static void
thunar_path_entry_update_icon(ThunarPathEntry * path_entry)670 thunar_path_entry_update_icon (ThunarPathEntry *path_entry)
671 {
672   GdkPixbuf          *icon = NULL;
673   GtkIconTheme       *icon_theme;
674   gint                icon_size;
675 
676   if (path_entry->icon_factory == NULL)
677     {
678       icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_entry)));
679       path_entry->icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme);
680     }
681 
682   gtk_widget_style_get (GTK_WIDGET (path_entry), "icon-size", &icon_size, NULL);
683 
684   if (G_UNLIKELY (path_entry->current_file != NULL))
685     {
686       icon = thunar_icon_factory_load_file_icon (path_entry->icon_factory,
687                                                  path_entry->current_file,
688                                                  THUNAR_FILE_ICON_STATE_DEFAULT,
689                                                  icon_size);
690     }
691   else if (G_LIKELY (path_entry->current_folder != NULL))
692     {
693       icon = thunar_icon_factory_load_file_icon (path_entry->icon_factory,
694                                                  path_entry->current_folder,
695                                                  THUNAR_FILE_ICON_STATE_DEFAULT,
696                                                  icon_size);
697     }
698 
699   if (icon != NULL)
700     {
701       gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (path_entry),
702                                       GTK_ENTRY_ICON_PRIMARY,
703                                       icon);
704       g_object_unref (icon);
705     }
706   else
707     {
708       gtk_entry_set_icon_from_icon_name (GTK_ENTRY (path_entry),
709                                          GTK_ENTRY_ICON_PRIMARY,
710                                          "dialog-error-symbolic");
711     }
712 }
713 
714 
715 
716 static void
thunar_path_entry_do_insert_text(GtkEditable * editable,const gchar * new_text,gint new_text_length,gint * position)717 thunar_path_entry_do_insert_text (GtkEditable *editable,
718                                   const gchar *new_text,
719                                   gint         new_text_length,
720                                   gint        *position)
721 {
722   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (editable);
723 
724   /* let the GtkEntry class handle the insert */
725   (*thunar_path_entry_editable_parent_iface->do_insert_text) (editable, new_text, new_text_length, position);
726 
727   /* queue a completion check if this insert operation was triggered by the user */
728   if (G_LIKELY (!path_entry->in_change))
729     thunar_path_entry_queue_check_completion (path_entry);
730 }
731 
732 
733 
734 static void
thunar_path_entry_clear_completion(ThunarPathEntry * path_entry)735 thunar_path_entry_clear_completion (ThunarPathEntry *path_entry)
736 {
737   /* reset the completion and apply the new text */
738   if (G_UNLIKELY (path_entry->has_completion))
739     {
740       path_entry->has_completion = FALSE;
741       thunar_path_entry_changed (GTK_EDITABLE (path_entry));
742     }
743 }
744 
745 
746 
747 static void
thunar_path_entry_common_prefix_append(ThunarPathEntry * path_entry,gboolean highlight)748 thunar_path_entry_common_prefix_append (ThunarPathEntry *path_entry,
749                                         gboolean         highlight)
750 {
751   const gchar *last_slash;
752   const gchar *text;
753   ThunarFile  *file;
754   gchar       *prefix;
755   gchar       *tmp;
756   gint         prefix_length;
757   gint         text_length;
758   gint         offset;
759   gint         base;
760 
761   /* determine the common prefix */
762   thunar_path_entry_common_prefix_lookup (path_entry, &prefix, &file);
763 
764   /* check if we should append a slash to the prefix */
765   if (G_LIKELY (file != NULL))
766     {
767       /* we only append slashes for directories */
768       if (thunar_file_is_directory (file) && file != path_entry->current_file)
769         {
770           tmp = g_strconcat (prefix, G_DIR_SEPARATOR_S, NULL);
771           g_free (prefix);
772           prefix = tmp;
773         }
774 
775       /* release the file */
776       g_object_unref (G_OBJECT (file));
777     }
778 
779   /* check if we have a common prefix */
780   if (G_LIKELY (prefix != NULL))
781     {
782       /* determine the UTF-8 length of the entry text */
783       text = gtk_entry_get_text (GTK_ENTRY (path_entry));
784       last_slash = g_utf8_strrchr (text, -1, G_DIR_SEPARATOR);
785       if (G_LIKELY (last_slash != NULL))
786         offset = g_utf8_strlen (text, last_slash - text) + 1;
787       else
788         offset = 0;
789       text_length = g_utf8_strlen (text, -1) - offset;
790 
791       /* determine the UTF-8 length of the prefix */
792       prefix_length = g_utf8_strlen (prefix, -1);
793 
794       /* append only if the prefix is longer than the already entered text */
795       if (G_LIKELY (prefix_length > text_length))
796         {
797           /* remember the base offset */
798           base = offset;
799 
800           /* insert the prefix */
801           path_entry->in_change = TRUE;
802           gtk_editable_delete_text (GTK_EDITABLE (path_entry), offset, -1);
803           gtk_editable_insert_text (GTK_EDITABLE (path_entry), prefix, -1, &offset);
804           path_entry->in_change = FALSE;
805 
806           /* highlight the prefix if requested */
807           if (G_LIKELY (highlight))
808             {
809               gtk_editable_select_region (GTK_EDITABLE (path_entry), base + text_length, base + prefix_length);
810               path_entry->has_completion = TRUE;
811             }
812         }
813 
814       /* cleanup */
815       g_free (prefix);
816     }
817 }
818 
819 
820 
821 static gboolean
thunar_path_entry_has_prefix_casefolded(const gchar * string,const gchar * prefix)822 thunar_path_entry_has_prefix_casefolded (const gchar *string,
823                                          const gchar *prefix)
824 {
825   gchar *string_casefolded;
826   gchar *prefix_casefolded;
827   gboolean has_prefix;
828 
829   if (string == NULL || prefix == NULL)
830     return FALSE;
831 
832   string_casefolded = g_utf8_casefold (string, -1);
833   prefix_casefolded = g_utf8_casefold (prefix, -1);
834 
835   has_prefix = g_str_has_prefix (string_casefolded , prefix_casefolded);
836 
837   g_free (string_casefolded);
838   g_free (prefix_casefolded);
839 
840   return has_prefix;
841 }
842 
843 
844 
845 static void
thunar_path_entry_common_prefix_lookup(ThunarPathEntry * path_entry,gchar ** prefix_return,ThunarFile ** file_return)846 thunar_path_entry_common_prefix_lookup (ThunarPathEntry *path_entry,
847                                         gchar          **prefix_return,
848                                         ThunarFile     **file_return)
849 {
850   GtkTreeModel *model;
851   GtkTreeIter   iter;
852   const gchar  *text;
853   const gchar  *s;
854   gchar        *name;
855   gchar        *t;
856 
857   *prefix_return = NULL;
858   *file_return = NULL;
859 
860   /* lookup the last slash character in the entry text */
861   text = gtk_entry_get_text (GTK_ENTRY (path_entry));
862   s = strrchr (text, G_DIR_SEPARATOR);
863   if (G_UNLIKELY (s != NULL && s[1] == '\0'))
864     return;
865   else if (G_LIKELY (s != NULL))
866     text = s + 1;
867 
868   /* check all items in the model */
869   model = gtk_entry_completion_get_model (gtk_entry_get_completion (GTK_ENTRY (path_entry)));
870   if (gtk_tree_model_get_iter_first (model, &iter))
871     {
872       do
873         {
874           /* determine the real file name for the iter */
875           gtk_tree_model_get (model, &iter, THUNAR_COLUMN_FILE_NAME, &name, -1);
876 
877           /* check if we have a valid prefix here */
878           if (thunar_path_entry_has_prefix_casefolded (name, text))
879             {
880               /* check if we're the first to match */
881               if (*prefix_return == NULL)
882                 {
883                   /* remember the prefix */
884                   *prefix_return = g_strdup (name);
885 
886                   /* determine the file for the iter */
887                   gtk_tree_model_get (model, &iter, THUNAR_COLUMN_FILE, file_return, -1);
888                 }
889               else
890                 {
891                   /* we already have another prefix, so determine the common part */
892                   for (s = name, t = *prefix_return; *s != '\0' && *s == *t; ++s, ++t)
893                     ;
894                   *t = '\0';
895 
896                   /* release the file, since it's not a unique match */
897                   if (G_LIKELY (*file_return != NULL))
898                     {
899                       g_object_unref (G_OBJECT (*file_return));
900                       *file_return = NULL;
901                     }
902                 }
903             }
904 
905           /* cleanup */
906           g_free (name);
907         }
908       while (gtk_tree_model_iter_next (model, &iter));
909     }
910 }
911 
912 
913 
914 static gboolean
thunar_path_entry_match_func(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * iter,gpointer user_data)915 thunar_path_entry_match_func (GtkEntryCompletion *completion,
916                               const gchar        *key,
917                               GtkTreeIter        *iter,
918                               gpointer            user_data)
919 {
920   GtkTreeModel    *model;
921   ThunarPathEntry *path_entry;
922   const gchar     *last_slash;
923   ThunarFile      *file;
924   gboolean         matched;
925   gchar           *text_normalized;
926   gchar           *name_normalized;
927   gchar           *name;
928 
929   /* determine the model from the completion */
930   model = gtk_entry_completion_get_model (completion);
931 
932   /* leave if the model is null, we do this in thunar_path_entry_changed() to speed
933    * things up, but that causes https://bugzilla.xfce.org/show_bug.cgi?id=4847. */
934   if (G_UNLIKELY (model == NULL))
935     return FALSE;
936 
937   /* leave if the auto completion highlight was not cleared yet, to prevent
938    * https://bugzilla.xfce.org/show_bug.cgi?id=16267. */
939   path_entry = THUNAR_PATH_ENTRY (user_data);
940   if (G_UNLIKELY (path_entry->has_completion))
941     return FALSE;
942 
943   /* determine the current text (UTF-8 normalized) */
944   text_normalized = g_utf8_normalize (gtk_entry_get_text (GTK_ENTRY (user_data)), -1, G_NORMALIZE_ALL);
945 
946   /* lookup the last slash character in the key */
947   last_slash = strrchr (text_normalized, G_DIR_SEPARATOR);
948   if (G_UNLIKELY (last_slash != NULL && last_slash[1] == '\0'))
949     {
950       /* check if the file is hidden */
951       gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE, &file, -1);
952       matched = !thunar_file_is_hidden (file);
953       g_object_unref (G_OBJECT (file));
954     }
955   else
956     {
957       if (G_UNLIKELY (last_slash == NULL))
958         last_slash = text_normalized;
959       else
960         last_slash += 1;
961 
962       /* determine the real file name for the iter */
963       gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE_NAME, &name, -1);
964       name_normalized = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
965       if (G_LIKELY (name_normalized != NULL))
966         g_free (name);
967       else
968         name_normalized = name;
969 
970       /* check if we have a match here */
971       if (name_normalized != NULL)
972         matched = thunar_path_entry_has_prefix_casefolded (name_normalized, last_slash);
973       else
974         matched = FALSE;
975 
976       /* cleanup */
977       g_free (name_normalized);
978     }
979 
980   /* cleanup */
981   g_free (text_normalized);
982 
983   return matched;
984 }
985 
986 
987 
988 static gboolean
thunar_path_entry_match_selected(GtkEntryCompletion * completion,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)989 thunar_path_entry_match_selected (GtkEntryCompletion *completion,
990                                   GtkTreeModel       *model,
991                                   GtkTreeIter        *iter,
992                                   gpointer            user_data)
993 {
994   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (user_data);
995   const gchar     *last_slash;
996   const gchar     *text;
997   ThunarFile      *file;
998   gchar           *real_name;
999   gchar           *tmp;
1000   gint             offset;
1001 
1002   /* determine the file for the iterator */
1003   gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE, &file, -1);
1004 
1005   /* determine the real name for the file */
1006   gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE_NAME, &real_name, -1);
1007 
1008   /* append a slash if we have a folder here */
1009   if (G_LIKELY (thunar_file_is_directory (file)))
1010     {
1011       tmp = g_strconcat (real_name, G_DIR_SEPARATOR_S, NULL);
1012       g_free (real_name);
1013       real_name = tmp;
1014     }
1015 
1016   /* determine the UTF-8 offset of the last slash on the entry text */
1017   text = gtk_entry_get_text (GTK_ENTRY (path_entry));
1018   last_slash = g_utf8_strrchr (text, -1, G_DIR_SEPARATOR);
1019   if (G_LIKELY (last_slash != NULL))
1020     offset = g_utf8_strlen (text, last_slash - text) + 1;
1021   else
1022     offset = 0;
1023 
1024   /* delete the previous text at the specified offset */
1025   gtk_editable_delete_text (GTK_EDITABLE (path_entry), offset, -1);
1026 
1027   /* insert the new file/folder name */
1028   gtk_editable_insert_text (GTK_EDITABLE (path_entry), real_name, -1, &offset);
1029 
1030   /* move the cursor to the end of the text entry */
1031   gtk_editable_set_position (GTK_EDITABLE (path_entry), -1);
1032 
1033   /* cleanup */
1034   g_object_unref (G_OBJECT (file));
1035   g_free (real_name);
1036 
1037   return TRUE;
1038 }
1039 
1040 
1041 
1042 static gboolean
thunar_path_entry_parse(ThunarPathEntry * path_entry,gchar ** folder_part,gchar ** file_part,GError ** error)1043 thunar_path_entry_parse (ThunarPathEntry *path_entry,
1044                          gchar          **folder_part,
1045                          gchar          **file_part,
1046                          GError         **error)
1047 {
1048   const gchar *last_slash;
1049   gchar       *filename;
1050   gchar       *path;
1051 
1052   _thunar_return_val_if_fail (THUNAR_IS_PATH_ENTRY (path_entry), FALSE);
1053   _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1054   _thunar_return_val_if_fail (folder_part != NULL, FALSE);
1055   _thunar_return_val_if_fail (file_part != NULL, FALSE);
1056 
1057   /* expand the filename */
1058   filename = thunar_util_expand_filename (gtk_entry_get_text (GTK_ENTRY (path_entry)),
1059                                           path_entry->working_directory,
1060                                           error);
1061   if (G_UNLIKELY (filename == NULL))
1062     return FALSE;
1063 
1064   /* lookup the last slash character in the filename */
1065   last_slash = strrchr (filename, G_DIR_SEPARATOR);
1066   if (G_UNLIKELY (last_slash == NULL))
1067     {
1068       /* no slash character, it's relative to the home dir */
1069       *file_part = g_filename_from_utf8 (filename, -1, NULL, NULL, error);
1070       if (G_LIKELY (*file_part != NULL))
1071         *folder_part = g_strdup (xfce_get_homedir ());
1072     }
1073   else
1074     {
1075       if (G_LIKELY (last_slash != filename))
1076         *folder_part = g_filename_from_utf8 (filename, last_slash - filename, NULL, NULL, error);
1077       else
1078         *folder_part = g_strdup ("/");
1079 
1080       if (G_LIKELY (*folder_part != NULL))
1081         {
1082           /* if folder_part doesn't start with '/', it's relative to the home dir */
1083           if (G_UNLIKELY (**folder_part != G_DIR_SEPARATOR))
1084             {
1085               path = xfce_get_homefile (*folder_part, NULL);
1086               g_free (*folder_part);
1087               *folder_part = path;
1088             }
1089 
1090           /* determine the file part */
1091           *file_part = g_filename_from_utf8 (last_slash + 1, -1, NULL, NULL, error);
1092           if (G_UNLIKELY (*file_part == NULL))
1093             {
1094               g_free (*folder_part);
1095               *folder_part = NULL;
1096             }
1097         }
1098       else
1099         {
1100           g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s", g_strerror (EINVAL));
1101         }
1102     }
1103 
1104   /* release the filename */
1105   g_free (filename);
1106 
1107   return (*folder_part != NULL);
1108 }
1109 
1110 
1111 
1112 static void
thunar_path_entry_queue_check_completion(ThunarPathEntry * path_entry)1113 thunar_path_entry_queue_check_completion (ThunarPathEntry *path_entry)
1114 {
1115   if (G_LIKELY (path_entry->check_completion_idle_id == 0))
1116     {
1117       path_entry->check_completion_idle_id = g_idle_add_full (G_PRIORITY_HIGH, thunar_path_entry_check_completion_idle,
1118                                                               path_entry, thunar_path_entry_check_completion_idle_destroy);
1119     }
1120 }
1121 
1122 
1123 
1124 static gboolean
thunar_path_entry_check_completion_idle(gpointer user_data)1125 thunar_path_entry_check_completion_idle (gpointer user_data)
1126 {
1127   ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (user_data);
1128   const gchar     *text;
1129 
1130 THUNAR_THREADS_ENTER
1131 
1132   /* check if the user entered at least part of a filename */
1133   text = gtk_entry_get_text (GTK_ENTRY (path_entry));
1134   if (*text != '\0' && text[strlen (text) - 1] != '/')
1135     {
1136       /* automatically insert the common prefix */
1137       thunar_path_entry_common_prefix_append (path_entry, TRUE);
1138     }
1139 
1140 THUNAR_THREADS_LEAVE
1141 
1142   return FALSE;
1143 }
1144 
1145 
1146 
1147 static void
thunar_path_entry_check_completion_idle_destroy(gpointer user_data)1148 thunar_path_entry_check_completion_idle_destroy (gpointer user_data)
1149 {
1150   THUNAR_PATH_ENTRY (user_data)->check_completion_idle_id = 0;
1151 }
1152 
1153 
1154 
1155 /**
1156  * thunar_path_entry_new:
1157  *
1158  * Allocates a new #ThunarPathEntry instance.
1159  *
1160  * Return value: the newly allocated #ThunarPathEntry.
1161  **/
1162 GtkWidget*
thunar_path_entry_new(void)1163 thunar_path_entry_new (void)
1164 {
1165   return g_object_new (THUNAR_TYPE_PATH_ENTRY, NULL);
1166 }
1167 
1168 
1169 
1170 /**
1171  * thunar_path_entry_get_current_file:
1172  * @path_entry : a #ThunarPathEntry.
1173  *
1174  * Returns the #ThunarFile currently being displayed by
1175  * @path_entry or %NULL if @path_entry doesn't contain
1176  * a valid #ThunarFile.
1177  *
1178  * Return value: the #ThunarFile for @path_entry or %NULL.
1179  **/
1180 ThunarFile*
thunar_path_entry_get_current_file(ThunarPathEntry * path_entry)1181 thunar_path_entry_get_current_file (ThunarPathEntry *path_entry)
1182 {
1183   _thunar_return_val_if_fail (THUNAR_IS_PATH_ENTRY (path_entry), NULL);
1184   return path_entry->current_file;
1185 }
1186 
1187 
1188 
1189 /**
1190  * thunar_path_entry_set_current_file:
1191  * @path_entry   : a #ThunarPathEntry.
1192  * @current_file : a #ThunarFile or %NULL.
1193  *
1194  * Sets the #ThunarFile that should be displayed by
1195  * @path_entry to @current_file.
1196  **/
1197 void
thunar_path_entry_set_current_file(ThunarPathEntry * path_entry,ThunarFile * current_file)1198 thunar_path_entry_set_current_file (ThunarPathEntry *path_entry,
1199                                     ThunarFile      *current_file)
1200 {
1201   GFile    *file;
1202   gchar    *text;
1203   gchar    *unescaped;
1204   gchar    *tmp;
1205   gboolean  is_uri = FALSE;
1206 
1207   _thunar_return_if_fail (THUNAR_IS_PATH_ENTRY (path_entry));
1208   _thunar_return_if_fail (current_file == NULL || THUNAR_IS_FILE (current_file));
1209 
1210   file = (current_file != NULL) ? thunar_file_get_file (current_file) : NULL;
1211   if (G_UNLIKELY (file == NULL))
1212     {
1213       /* invalid file */
1214       text = g_strdup ("");
1215     }
1216   else
1217     {
1218       /* check if the file is native to the platform */
1219       if (g_file_is_native (file))
1220         {
1221           /* it is, try the local path first */
1222           text = g_file_get_path (file);
1223 
1224           /* if there is no local path, use the URI (which always works) */
1225           if (text == NULL)
1226             {
1227               text = g_file_get_uri (file);
1228               is_uri = TRUE;
1229             }
1230         }
1231       else
1232         {
1233           /* not a native file, use the URI */
1234           text = g_file_get_uri (file);
1235           is_uri = TRUE;
1236         }
1237 
1238       /* if the file is a directory, end with a / to avoid loading the parent
1239        * directory which is probably not something the user wants */
1240       if (thunar_file_is_directory (current_file)
1241           && !g_str_has_suffix (text, "/"))
1242         {
1243           tmp = g_strconcat (text, "/", NULL);
1244           g_free (text);
1245           text = tmp;
1246         }
1247 
1248       // convert filename into valid UTF-8 string for display
1249       tmp = text;
1250       text = g_filename_display_name(tmp);
1251       g_free (tmp);
1252     }
1253 
1254   if (is_uri)
1255     unescaped = g_uri_unescape_string (text, NULL);
1256   else
1257     unescaped = g_strdup (text);
1258   g_free (text);
1259 
1260   /* setup the entry text */
1261   gtk_entry_set_text (GTK_ENTRY (path_entry), unescaped);
1262   g_free (unescaped);
1263 
1264   /* update the icon */
1265   thunar_path_entry_update_icon (path_entry);
1266 
1267   gtk_editable_set_position (GTK_EDITABLE (path_entry), -1);
1268 
1269   gtk_widget_queue_draw (GTK_WIDGET (path_entry));
1270 }
1271 
1272 
1273 
1274 /**
1275  * thunar_path_entry_set_working_directory:
1276  * @path_entry        : a #ThunarPathEntry.
1277  * @working_directory : a #ThunarFile or %NULL.
1278  *
1279  * Sets the #ThunarFile that should be used as the
1280  * working directory for @path_entry.
1281  **/
1282 void
thunar_path_entry_set_working_directory(ThunarPathEntry * path_entry,ThunarFile * working_directory)1283 thunar_path_entry_set_working_directory (ThunarPathEntry *path_entry,
1284                                          ThunarFile      *working_directory)
1285 {
1286   _thunar_return_if_fail (THUNAR_IS_PATH_ENTRY (path_entry));
1287   _thunar_return_if_fail (working_directory == NULL || THUNAR_IS_FILE (working_directory));
1288 
1289   if (G_LIKELY (path_entry->working_directory != NULL))
1290     g_object_unref (path_entry->working_directory);
1291 
1292   path_entry->working_directory = NULL;
1293 
1294   if (THUNAR_IS_FILE (working_directory))
1295     path_entry->working_directory = g_object_ref (thunar_file_get_file (working_directory));
1296 }
1297