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