1 /*
2  * Nautilus
3  *
4  * Copyright (C) 2000 Eazel, Inc.
5  *
6  * Nautilus 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 the
9  * License, or (at your option) any later version.
10  *
11  * Nautilus 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 GNU
14  * 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; see the file COPYING.  If not,
18  * see <http://www.gnu.org/licenses/>.
19  *
20  * Author: Maciej Stachowiak <mjs@eazel.com>
21  *         Ettore Perazzoli <ettore@gnu.org>
22  *         Michael Meeks <michael@nuclecu.unam.mx>
23  *     Andy Hertzfeld <andy@eazel.com>
24  *
25  */
26 
27 /* nautilus-location-bar.c - Location bar for Nautilus
28  */
29 
30 #include <config.h>
31 #include "nautilus-location-entry.h"
32 
33 #include "nautilus-application.h"
34 #include "nautilus-window.h"
35 #include <gtk/gtk.h>
36 #include <gdk/gdkkeysyms.h>
37 #include <glib/gi18n.h>
38 #include <gio/gio.h>
39 #include "nautilus-file-utilities.h"
40 #include "nautilus-clipboard.h"
41 #include <eel/eel-stock-dialogs.h>
42 #include <eel/eel-string.h>
43 #include <eel/eel-vfs-extensions.h>
44 #include <stdio.h>
45 #include <string.h>
46 
47 #define NAUTILUS_DND_URI_LIST_TYPE        "text/uri-list"
48 #define NAUTILUS_DND_TEXT_PLAIN_TYPE      "text/plain"
49 
50 enum
51 {
52     NAUTILUS_DND_URI_LIST,
53     NAUTILUS_DND_TEXT_PLAIN,
54     NAUTILUS_DND_NTARGETS
55 };
56 
57 static const GtkTargetEntry drop_types [] =
58 {
59     { NAUTILUS_DND_URI_LIST_TYPE, 0, NAUTILUS_DND_URI_LIST },
60     { NAUTILUS_DND_TEXT_PLAIN_TYPE, 0, NAUTILUS_DND_TEXT_PLAIN },
61 };
62 
63 typedef struct _NautilusLocationEntryPrivate
64 {
65     char *current_directory;
66     GFilenameCompleter *completer;
67 
68     guint idle_id;
69     gboolean idle_insert_completion;
70 
71     GFile *last_location;
72 
73     gboolean has_special_text;
74     gboolean setting_special_text;
75     gchar *special_text;
76     NautilusLocationEntryAction secondary_action;
77 
78     GtkEntryCompletion *completion;
79     GtkListStore *completions_store;
80     GtkCellRenderer *completion_cell;
81 } NautilusLocationEntryPrivate;
82 
83 enum
84 {
85     CANCEL,
86     LOCATION_CHANGED,
87     LAST_SIGNAL
88 };
89 
90 static guint signals[LAST_SIGNAL];
91 
92 G_DEFINE_TYPE_WITH_PRIVATE (NautilusLocationEntry, nautilus_location_entry, GTK_TYPE_ENTRY);
93 
94 static GFile *
nautilus_location_entry_get_location(NautilusLocationEntry * entry)95 nautilus_location_entry_get_location (NautilusLocationEntry *entry)
96 {
97     char *user_location;
98     GFile *location;
99 
100     user_location = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
101     location = g_file_parse_name (user_location);
102     g_free (user_location);
103 
104     return location;
105 }
106 
107 static void
emit_location_changed(NautilusLocationEntry * entry)108 emit_location_changed (NautilusLocationEntry *entry)
109 {
110     GFile *location;
111 
112     location = nautilus_location_entry_get_location (entry);
113     g_signal_emit (entry, signals[LOCATION_CHANGED], 0, location);
114     g_object_unref (location);
115 }
116 
117 static void
nautilus_location_entry_update_action(NautilusLocationEntry * entry)118 nautilus_location_entry_update_action (NautilusLocationEntry *entry)
119 {
120     NautilusLocationEntryPrivate *priv;
121     const char *current_text;
122     GFile *location;
123 
124     priv = nautilus_location_entry_get_instance_private (entry);
125 
126     if (priv->last_location == NULL)
127     {
128         nautilus_location_entry_set_secondary_action (entry,
129                                                       NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
130         return;
131     }
132 
133     current_text = gtk_entry_get_text (GTK_ENTRY (entry));
134     location = g_file_parse_name (current_text);
135 
136     if (g_file_equal (priv->last_location, location))
137     {
138         nautilus_location_entry_set_secondary_action (entry,
139                                                       NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
140     }
141     else
142     {
143         nautilus_location_entry_set_secondary_action (entry,
144                                                       NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
145     }
146 
147     g_object_unref (location);
148 }
149 
150 static int
get_editable_number_of_chars(GtkEditable * editable)151 get_editable_number_of_chars (GtkEditable *editable)
152 {
153     char *text;
154     int length;
155 
156     text = gtk_editable_get_chars (editable, 0, -1);
157     length = g_utf8_strlen (text, -1);
158     g_free (text);
159     return length;
160 }
161 
162 static void
set_position_and_selection_to_end(GtkEditable * editable)163 set_position_and_selection_to_end (GtkEditable *editable)
164 {
165     int end;
166 
167     end = get_editable_number_of_chars (editable);
168     gtk_editable_select_region (editable, end, end);
169     gtk_editable_set_position (editable, end);
170 }
171 
172 static void
nautilus_location_entry_update_current_uri(NautilusLocationEntry * entry,const char * uri)173 nautilus_location_entry_update_current_uri (NautilusLocationEntry *entry,
174                                             const char            *uri)
175 {
176     NautilusLocationEntryPrivate *priv;
177 
178     priv = nautilus_location_entry_get_instance_private (entry);
179 
180     g_free (priv->current_directory);
181     priv->current_directory = g_strdup (uri);
182 
183     gtk_entry_set_text (GTK_ENTRY (entry), uri);
184     set_position_and_selection_to_end (GTK_EDITABLE (entry));
185 }
186 
187 void
nautilus_location_entry_set_location(NautilusLocationEntry * entry,GFile * location)188 nautilus_location_entry_set_location (NautilusLocationEntry *entry,
189                                       GFile                 *location)
190 {
191     NautilusLocationEntryPrivate *priv;
192     gchar *uri, *formatted_uri;
193 
194     g_assert (location != NULL);
195 
196     priv = nautilus_location_entry_get_instance_private (entry);
197 
198     /* Note: This is called in reaction to external changes, and
199      * thus should not emit the LOCATION_CHANGED signal. */
200     uri = g_file_get_uri (location);
201     formatted_uri = g_file_get_parse_name (location);
202 
203     if (eel_uri_is_search (uri))
204     {
205         nautilus_location_entry_set_special_text (entry, "");
206     }
207     else
208     {
209         nautilus_location_entry_update_current_uri (entry, formatted_uri);
210     }
211 
212     /* remember the original location for later comparison */
213     if (!priv->last_location ||
214         !g_file_equal (priv->last_location, location))
215     {
216         g_clear_object (&priv->last_location);
217         priv->last_location = g_object_ref (location);
218     }
219 
220     nautilus_location_entry_update_action (entry);
221 
222     /* invalidate the completions list */
223     gtk_list_store_clear (priv->completions_store);
224 
225     g_free (uri);
226     g_free (formatted_uri);
227 }
228 
229 static void
drag_data_received_callback(GtkWidget * widget,GdkDragContext * context,int x,int y,GtkSelectionData * data,guint info,guint32 time,gpointer callback_data)230 drag_data_received_callback (GtkWidget        *widget,
231                              GdkDragContext   *context,
232                              int               x,
233                              int               y,
234                              GtkSelectionData *data,
235                              guint             info,
236                              guint32           time,
237                              gpointer          callback_data)
238 {
239     char **names;
240     int name_count;
241     GtkWidget *window;
242     gboolean new_windows_for_extras;
243     char *prompt;
244     char *detail;
245     GFile *location;
246     NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (widget);
247 
248     g_assert (data != NULL);
249     g_assert (callback_data == NULL);
250 
251     names = g_uri_list_extract_uris ((const gchar *) gtk_selection_data_get_data (data));
252 
253     if (names == NULL || *names == NULL)
254     {
255         g_warning ("No D&D URI's");
256         gtk_drag_finish (context, FALSE, FALSE, time);
257         return;
258     }
259 
260     window = gtk_widget_get_toplevel (widget);
261     new_windows_for_extras = FALSE;
262     /* Ask user if they really want to open multiple windows
263      * for multiple dropped URIs. This is likely to have been
264      * a mistake.
265      */
266     name_count = g_strv_length (names);
267     if (name_count > 1)
268     {
269         prompt = g_strdup_printf (ngettext ("Do you want to view %d location?",
270                                             "Do you want to view %d locations?",
271                                             name_count),
272                                   name_count);
273         detail = g_strdup_printf (ngettext ("This will open %d separate window.",
274                                             "This will open %d separate windows.",
275                                             name_count),
276                                   name_count);
277         /* eel_run_simple_dialog should really take in pairs
278          * like gtk_dialog_new_with_buttons() does. */
279         new_windows_for_extras = eel_run_simple_dialog (GTK_WIDGET (window),
280                                                         TRUE,
281                                                         GTK_MESSAGE_QUESTION,
282                                                         prompt,
283                                                         detail,
284                                                         _("_Cancel"), _("_OK"),
285                                                         NULL) != 0 /* GNOME_OK */;
286 
287         g_free (prompt);
288         g_free (detail);
289 
290         if (!new_windows_for_extras)
291         {
292             gtk_drag_finish (context, FALSE, FALSE, time);
293             return;
294         }
295     }
296 
297     location = g_file_new_for_uri (names[0]);
298     nautilus_location_entry_set_location (self, location);
299     emit_location_changed (self);
300     g_object_unref (location);
301 
302     if (new_windows_for_extras)
303     {
304         int i;
305 
306         for (i = 1; names[i] != NULL; ++i)
307         {
308             location = g_file_new_for_uri (names[i]);
309             nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()),
310                                                      location, NAUTILUS_WINDOW_OPEN_FLAG_NEW_WINDOW, NULL, NULL, NULL);
311             g_object_unref (location);
312         }
313     }
314 
315     g_strfreev (names);
316 
317     gtk_drag_finish (context, TRUE, FALSE, time);
318 }
319 
320 static void
drag_data_get_callback(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint32 time,gpointer callback_data)321 drag_data_get_callback (GtkWidget        *widget,
322                         GdkDragContext   *context,
323                         GtkSelectionData *selection_data,
324                         guint             info,
325                         guint32           time,
326                         gpointer          callback_data)
327 {
328     NautilusLocationEntry *self;
329     GFile *location;
330     gchar *uri;
331 
332     g_assert (selection_data != NULL);
333     self = callback_data;
334 
335     location = nautilus_location_entry_get_location (self);
336     uri = g_file_get_uri (location);
337 
338     switch (info)
339     {
340         case NAUTILUS_DND_URI_LIST:
341         case NAUTILUS_DND_TEXT_PLAIN:
342         {
343             gtk_selection_data_set (selection_data,
344                                     gtk_selection_data_get_target (selection_data),
345                                     8, (guchar *) uri,
346                                     strlen (uri));
347         }
348         break;
349 
350         default:
351         {
352             g_assert_not_reached ();
353         }
354     }
355     g_free (uri);
356     g_object_unref (location);
357 }
358 
359 
360 static void
set_prefix_dimming(GtkCellRenderer * completion_cell,char * user_location)361 set_prefix_dimming (GtkCellRenderer *completion_cell,
362                     char            *user_location)
363 {
364     g_autofree char *location_basename = NULL;
365     PangoAttrList *attrs;
366     PangoAttribute *attr;
367 
368     /* Dim the prefixes of the completion rows, leaving the basenames
369      * highlighted. This makes it easier to find what you're looking for.
370      *
371      * Perhaps a better solution would be to *only* show the basenames, but
372      * it would take a reimplementation of GtkEntryCompletion to align the
373      * popover. */
374 
375     location_basename = g_path_get_basename (user_location);
376 
377     attrs = pango_attr_list_new ();
378 
379     /* 55% opacity. This is the same as the dim-label style class in Adwaita. */
380     attr = pango_attr_foreground_alpha_new (36045);
381     attr->end_index = strlen (user_location) - strlen (location_basename);
382     pango_attr_list_insert (attrs, attr);
383 
384     g_object_set (completion_cell, "attributes", attrs, NULL);
385     pango_attr_list_unref (attrs);
386 }
387 
388 
389 /* Update the path completions list based on the current text of the entry. */
390 static gboolean
update_completions_store(gpointer callback_data)391 update_completions_store (gpointer callback_data)
392 {
393     NautilusLocationEntry *entry;
394     NautilusLocationEntryPrivate *priv;
395     GtkEditable *editable;
396     g_autofree char *absolute_location = NULL;
397     g_autofree char *user_location = NULL;
398     gboolean is_relative = FALSE;
399     int start_sel;
400     g_autofree char *uri_scheme = NULL;
401     g_auto (GStrv) completions = NULL;
402     char *completion;
403     int i;
404     GtkTreeIter iter;
405     int current_dir_strlen;
406 
407     entry = NAUTILUS_LOCATION_ENTRY (callback_data);
408     priv = nautilus_location_entry_get_instance_private (entry);
409     editable = GTK_EDITABLE (entry);
410 
411     if (gtk_editable_get_selection_bounds (editable, &start_sel, NULL))
412     {
413         user_location = gtk_editable_get_chars (editable, 0, start_sel);
414     }
415     else
416     {
417         user_location = gtk_editable_get_chars (editable, 0, -1);
418     }
419 
420     g_strstrip (user_location);
421     set_prefix_dimming (priv->completion_cell, user_location);
422 
423     priv->idle_id = 0;
424 
425     uri_scheme = g_uri_parse_scheme (user_location);
426 
427     if (!g_path_is_absolute (user_location) && uri_scheme == NULL && user_location[0] != '~')
428     {
429         is_relative = TRUE;
430         absolute_location = g_build_filename (priv->current_directory, user_location, NULL);
431     }
432     else
433     {
434         absolute_location = g_steal_pointer (&user_location);
435     }
436 
437     completions = g_filename_completer_get_completions (priv->completer, absolute_location);
438 
439     /* populate the completions model */
440     gtk_list_store_clear (priv->completions_store);
441 
442     current_dir_strlen = strlen (priv->current_directory);
443     for (i = 0; completions[i] != NULL; i++)
444     {
445         completion = completions[i];
446 
447         if (is_relative && strlen (completion) >= current_dir_strlen)
448         {
449             /* For relative paths, we need to strip the current directory
450              * (and the trailing slash) so the completions will match what's
451              * in the text entry */
452             completion += current_dir_strlen;
453             if (G_IS_DIR_SEPARATOR (completion[0]))
454             {
455                 completion++;
456             }
457         }
458 
459         gtk_list_store_append (priv->completions_store, &iter);
460         gtk_list_store_set (priv->completions_store, &iter, 0, completion, -1);
461     }
462 
463     /* refilter the completions dropdown */
464     gtk_entry_completion_complete (priv->completion);
465 
466     if (priv->idle_insert_completion)
467     {
468         /* insert the completion */
469         gtk_entry_completion_insert_prefix (priv->completion);
470     }
471 
472     return FALSE;
473 }
474 
475 /* Until we have a more elegant solution, this is how we figure out if
476  * the GtkEntry inserted characters, assuming that the return value is
477  * TRUE indicating that the GtkEntry consumed the key event for some
478  * reason. This is a clone of code from GtkEntry.
479  */
480 static gboolean
entry_would_have_inserted_characters(const GdkEvent * event)481 entry_would_have_inserted_characters (const GdkEvent *event)
482 {
483     guint keyval;
484     GdkModifierType state;
485 
486     if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
487     {
488         g_return_val_if_reached (GDK_EVENT_PROPAGATE);
489     }
490     if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
491     {
492         g_return_val_if_reached (GDK_EVENT_PROPAGATE);
493     }
494 
495     switch (keyval)
496     {
497         case GDK_KEY_BackSpace:
498         case GDK_KEY_Clear:
499         case GDK_KEY_Insert:
500         case GDK_KEY_Delete:
501         case GDK_KEY_Home:
502         case GDK_KEY_End:
503         case GDK_KEY_KP_Home:
504         case GDK_KEY_KP_End:
505         case GDK_KEY_Left:
506         case GDK_KEY_Right:
507         case GDK_KEY_KP_Left:
508         case GDK_KEY_KP_Right:
509         case GDK_KEY_Return:
510         /* For when the entry is set to be always visible.
511          */
512         case GDK_KEY_Escape:
513         {
514             return FALSE;
515         }
516 
517         default:
518         {
519             if (keyval >= 0x20 && keyval <= 0xFF)
520             {
521                 if ((state & GDK_CONTROL_MASK) != 0)
522                 {
523                     return FALSE;
524                 }
525                 if ((state & GDK_MOD1_MASK) != 0)
526                 {
527                     return FALSE;
528                 }
529             }
530         }
531     }
532 
533     /* GTK+ 4 TODO: gdk_event_get_string () and check if length > 0. */
534     return ((const GdkEventKey *) event)->length;
535 }
536 
537 static gboolean
position_and_selection_are_at_end(GtkEditable * editable)538 position_and_selection_are_at_end (GtkEditable *editable)
539 {
540     int end;
541     int start_sel, end_sel;
542 
543     end = get_editable_number_of_chars (editable);
544     if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel))
545     {
546         if (start_sel != end || end_sel != end)
547         {
548             return FALSE;
549         }
550     }
551     return gtk_editable_get_position (editable) == end;
552 }
553 
554 static void
got_completion_data_callback(GFilenameCompleter * completer,NautilusLocationEntry * entry)555 got_completion_data_callback (GFilenameCompleter    *completer,
556                               NautilusLocationEntry *entry)
557 {
558     NautilusLocationEntryPrivate *priv;
559 
560     priv = nautilus_location_entry_get_instance_private (entry);
561 
562     if (priv->idle_id)
563     {
564         g_source_remove (priv->idle_id);
565         priv->idle_id = 0;
566     }
567     update_completions_store (entry);
568 }
569 
570 static void
finalize(GObject * object)571 finalize (GObject *object)
572 {
573     NautilusLocationEntry *entry;
574     NautilusLocationEntryPrivate *priv;
575 
576     entry = NAUTILUS_LOCATION_ENTRY (object);
577     priv = nautilus_location_entry_get_instance_private (entry);
578 
579     g_object_unref (priv->completer);
580     g_free (priv->special_text);
581 
582     g_clear_object (&priv->last_location);
583     g_clear_object (&priv->completion);
584     g_clear_object (&priv->completions_store);
585 
586     G_OBJECT_CLASS (nautilus_location_entry_parent_class)->finalize (object);
587 }
588 
589 static void
destroy(GtkWidget * object)590 destroy (GtkWidget *object)
591 {
592     NautilusLocationEntry *entry;
593     NautilusLocationEntryPrivate *priv;
594 
595     entry = NAUTILUS_LOCATION_ENTRY (object);
596     priv = nautilus_location_entry_get_instance_private (entry);
597 
598     /* cancel the pending idle call, if any */
599     if (priv->idle_id != 0)
600     {
601         g_source_remove (priv->idle_id);
602         priv->idle_id = 0;
603     }
604 
605     g_free (priv->current_directory);
606     priv->current_directory = NULL;
607 
608     GTK_WIDGET_CLASS (nautilus_location_entry_parent_class)->destroy (object);
609 }
610 
611 static void
on_has_focus_changed(GObject * object,GParamSpec * pspec,gpointer user_data)612 on_has_focus_changed (GObject    *object,
613                       GParamSpec *pspec,
614                       gpointer    user_data)
615 {
616     NautilusLocationEntry *entry;
617     NautilusLocationEntryPrivate *priv;
618 
619     if (!gtk_widget_has_focus (GTK_WIDGET (object)))
620     {
621         return;
622     }
623 
624     entry = NAUTILUS_LOCATION_ENTRY (object);
625     priv = nautilus_location_entry_get_instance_private (entry);
626 
627     if (priv->has_special_text)
628     {
629         priv->setting_special_text = TRUE;
630         gtk_entry_set_text (GTK_ENTRY (entry), "");
631         priv->setting_special_text = FALSE;
632     }
633 }
634 
635 static void
nautilus_location_entry_text_changed(NautilusLocationEntry * entry,GParamSpec * pspec)636 nautilus_location_entry_text_changed (NautilusLocationEntry *entry,
637                                       GParamSpec            *pspec)
638 {
639     NautilusLocationEntryPrivate *priv;
640 
641     priv = nautilus_location_entry_get_instance_private (entry);
642 
643     if (priv->setting_special_text)
644     {
645         return;
646     }
647 
648     priv->has_special_text = FALSE;
649 }
650 
651 static void
nautilus_location_entry_icon_release(GtkEntry * gentry,GtkEntryIconPosition position,GdkEvent * event,gpointer unused)652 nautilus_location_entry_icon_release (GtkEntry             *gentry,
653                                       GtkEntryIconPosition  position,
654                                       GdkEvent             *event,
655                                       gpointer              unused)
656 {
657     NautilusLocationEntry *entry;
658     NautilusLocationEntryPrivate *priv;
659 
660     entry = NAUTILUS_LOCATION_ENTRY (gentry);
661     priv = nautilus_location_entry_get_instance_private (entry);
662 
663     switch (priv->secondary_action)
664     {
665         case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
666         {
667             g_signal_emit_by_name (gentry, "activate", gentry);
668         }
669         break;
670 
671         case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
672         {
673             gtk_entry_set_text (gentry, "");
674         }
675         break;
676 
677         default:
678         {
679             g_assert_not_reached ();
680         }
681     }
682 }
683 
684 static gboolean
nautilus_location_entry_on_event(GtkWidget * widget,GdkEvent * event)685 nautilus_location_entry_on_event (GtkWidget *widget,
686                                   GdkEvent  *event)
687 {
688     GtkWidgetClass *parent_widget_class;
689     NautilusLocationEntry *entry;
690     NautilusLocationEntryPrivate *priv;
691     GtkEditable *editable;
692     gboolean selected;
693     guint keyval;
694     GdkModifierType state;
695     gboolean handled;
696 
697     parent_widget_class = GTK_WIDGET_CLASS (nautilus_location_entry_parent_class);
698 
699     if (gdk_event_get_event_type (event) != GDK_KEY_PRESS)
700     {
701         return parent_widget_class->event (widget, event);
702     }
703 
704     entry = NAUTILUS_LOCATION_ENTRY (widget);
705     priv = nautilus_location_entry_get_instance_private (entry);
706     editable = GTK_EDITABLE (widget);
707     selected = gtk_editable_get_selection_bounds (editable, NULL, NULL);
708 
709     if (!gtk_editable_get_editable (editable))
710     {
711         return GDK_EVENT_PROPAGATE;
712     }
713 
714     if (G_UNLIKELY (!gdk_event_get_keyval (event, &keyval)))
715     {
716         g_return_val_if_reached (GDK_EVENT_PROPAGATE);
717     }
718     if (G_UNLIKELY (!gdk_event_get_state (event, &state)))
719     {
720         g_return_val_if_reached (GDK_EVENT_PROPAGATE);
721     }
722 
723     /* The location bar entry wants TAB to work kind of
724      * like it does in the shell for command completion,
725      * so if we get a tab and there's a selection, we
726      * should position the insertion point at the end of
727      * the selection.
728      */
729     if (keyval == GDK_KEY_Tab && !(state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
730     {
731         if (selected)
732         {
733             int position;
734 
735             position = strlen (gtk_entry_get_text (GTK_ENTRY (editable)));
736             gtk_editable_select_region (editable, position, position);
737         }
738         else
739         {
740             gtk_widget_error_bell (GTK_WIDGET (entry));
741         }
742 
743         return GDK_EVENT_STOP;
744     }
745 
746     if ((keyval == GDK_KEY_Right || keyval == GDK_KEY_End) &&
747         !(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && selected)
748     {
749         set_position_and_selection_to_end (editable);
750     }
751 
752     /* GTK+ 4 TODO: Calling the event vfunc is not enough, we need the entry
753      *              to handle the key press and insert the text first.
754      *
755      * Chaining up here is required either way, since the code below
756      * used to be in the handler for ::event-after, which is no longer a thing.
757      */
758     handled = parent_widget_class->key_press_event (widget, (GdkEventKey *) event);
759 
760 
761     if (keyval == GDK_KEY_Down || keyval == GDK_KEY_Up)
762     {
763         /* Ignore up/down arrow keys. These are used by the entry completion,
764          * and if we modify the completion store, navigation through the list
765          * will be interrupted. */
766         return GDK_EVENT_PROPAGATE;
767     }
768 
769     /* Only do completions when we are typing at the end of the
770      * text. Do the expand at idle time to avoid slowing down
771      * typing when the directory is large. Only insert an expansion
772      * when we type a key that would have inserted characters.
773      */
774     if (position_and_selection_are_at_end (editable))
775     {
776         /* Only insert a completion if a character was typed. Otherwise,
777          * update the completions store (i.e. in case backspace was pressed)
778          * but don't insert the completion into the entry. */
779         priv->idle_insert_completion = entry_would_have_inserted_characters (event);
780 
781         if (priv->idle_id == 0)
782         {
783             priv->idle_id = g_idle_add (update_completions_store, widget);
784         }
785     }
786     else
787     {
788         /* FIXME: Also might be good to do this when you click
789          * to change the position or selection.
790          */
791         if (priv->idle_id != 0)
792         {
793             g_source_remove (priv->idle_id);
794             priv->idle_id = 0;
795         }
796     }
797 
798     return handled;
799 }
800 
801 static void
nautilus_location_entry_activate(GtkEntry * entry)802 nautilus_location_entry_activate (GtkEntry *entry)
803 {
804     NautilusLocationEntry *loc_entry;
805     NautilusLocationEntryPrivate *priv;
806     const gchar *entry_text;
807     gchar *full_path, *uri_scheme = NULL;
808     g_autofree char *path = NULL;
809 
810     loc_entry = NAUTILUS_LOCATION_ENTRY (entry);
811     priv = nautilus_location_entry_get_instance_private (loc_entry);
812     entry_text = gtk_entry_get_text (entry);
813     path = g_strdup (entry_text);
814     path = g_strchug (path);
815     path = g_strchomp (path);
816 
817     if (path != NULL && *path != '\0')
818     {
819         uri_scheme = g_uri_parse_scheme (path);
820 
821         if (!g_path_is_absolute (path) && uri_scheme == NULL && path[0] != '~')
822         {
823             /* Fix non absolute paths */
824             full_path = g_build_filename (priv->current_directory, path, NULL);
825             gtk_entry_set_text (entry, full_path);
826             g_free (full_path);
827         }
828 
829         g_free (uri_scheme);
830     }
831 
832     GTK_ENTRY_CLASS (nautilus_location_entry_parent_class)->activate (entry);
833 }
834 
835 static void
nautilus_location_entry_cancel(NautilusLocationEntry * entry)836 nautilus_location_entry_cancel (NautilusLocationEntry *entry)
837 {
838     NautilusLocationEntryPrivate *priv;
839 
840     priv = nautilus_location_entry_get_instance_private (entry);
841 
842     nautilus_location_entry_set_location (entry, priv->last_location);
843 }
844 
845 static void
nautilus_location_entry_class_init(NautilusLocationEntryClass * class)846 nautilus_location_entry_class_init (NautilusLocationEntryClass *class)
847 {
848     GtkWidgetClass *widget_class;
849     GObjectClass *gobject_class;
850     GtkEntryClass *entry_class;
851     GtkBindingSet *binding_set;
852 
853     widget_class = GTK_WIDGET_CLASS (class);
854     widget_class->destroy = destroy;
855     widget_class->event = nautilus_location_entry_on_event;
856 
857     gobject_class = G_OBJECT_CLASS (class);
858     gobject_class->finalize = finalize;
859 
860     entry_class = GTK_ENTRY_CLASS (class);
861     entry_class->activate = nautilus_location_entry_activate;
862 
863     class->cancel = nautilus_location_entry_cancel;
864 
865     signals[CANCEL] = g_signal_new
866                           ("cancel",
867                           G_TYPE_FROM_CLASS (class),
868                           G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
869                           G_STRUCT_OFFSET (NautilusLocationEntryClass,
870                                            cancel),
871                           NULL, NULL,
872                           g_cclosure_marshal_VOID__VOID,
873                           G_TYPE_NONE, 0);
874 
875     signals[LOCATION_CHANGED] = g_signal_new
876                                     ("location-changed",
877                                     G_TYPE_FROM_CLASS (class),
878                                     G_SIGNAL_RUN_LAST, 0,
879                                     NULL, NULL,
880                                     g_cclosure_marshal_generic,
881                                     G_TYPE_NONE, 1, G_TYPE_OBJECT);
882 
883     binding_set = gtk_binding_set_by_class (class);
884     gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "cancel", 0);
885 }
886 
887 void
nautilus_location_entry_set_secondary_action(NautilusLocationEntry * entry,NautilusLocationEntryAction secondary_action)888 nautilus_location_entry_set_secondary_action (NautilusLocationEntry       *entry,
889                                               NautilusLocationEntryAction  secondary_action)
890 {
891     NautilusLocationEntryPrivate *priv;
892 
893     priv = nautilus_location_entry_get_instance_private (entry);
894 
895     if (priv->secondary_action == secondary_action)
896     {
897         return;
898     }
899 
900     switch (secondary_action)
901     {
902         case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
903         {
904             gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
905                                                GTK_ENTRY_ICON_SECONDARY,
906                                                "edit-clear-symbolic");
907         }
908         break;
909 
910         case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
911         {
912             gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
913                                                GTK_ENTRY_ICON_SECONDARY,
914                                                "go-next-symbolic");
915         }
916         break;
917 
918         default:
919         {
920             g_assert_not_reached ();
921         }
922     }
923     priv->secondary_action = secondary_action;
924 }
925 
926 static void
editable_activate_callback(GtkEntry * entry,gpointer user_data)927 editable_activate_callback (GtkEntry *entry,
928                             gpointer  user_data)
929 {
930     NautilusLocationEntry *self = user_data;
931     const char *entry_text;
932     g_autofree gchar *path = NULL;
933 
934     entry_text = gtk_entry_get_text (entry);
935     path = g_strdup (entry_text);
936     path = g_strchug (path);
937     path = g_strchomp (path);
938 
939     if (path != NULL && *path != '\0')
940     {
941         gtk_entry_set_text (entry, path);
942         emit_location_changed (self);
943     }
944 }
945 
946 static void
editable_changed_callback(GtkEntry * entry,gpointer user_data)947 editable_changed_callback (GtkEntry *entry,
948                            gpointer  user_data)
949 {
950     nautilus_location_entry_update_action (NAUTILUS_LOCATION_ENTRY (entry));
951 }
952 
953 static void
nautilus_location_entry_init(NautilusLocationEntry * entry)954 nautilus_location_entry_init (NautilusLocationEntry *entry)
955 {
956     NautilusLocationEntryPrivate *priv;
957 
958     priv = nautilus_location_entry_get_instance_private (entry);
959 
960     priv->completer = g_filename_completer_new ();
961     g_filename_completer_set_dirs_only (priv->completer, TRUE);
962 
963     nautilus_location_entry_set_secondary_action (entry,
964                                                   NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
965 
966     g_signal_connect (entry, "notify::has-focus",
967                       G_CALLBACK (on_has_focus_changed), NULL);
968 
969     g_signal_connect (entry, "notify::text",
970                       G_CALLBACK (nautilus_location_entry_text_changed), NULL);
971 
972     g_signal_connect (entry, "icon-release",
973                       G_CALLBACK (nautilus_location_entry_icon_release), NULL);
974 
975     g_signal_connect (priv->completer, "got-completion-data",
976                       G_CALLBACK (got_completion_data_callback), entry);
977 
978     /* Drag source */
979     g_signal_connect_object (entry, "drag-data-get",
980                              G_CALLBACK (drag_data_get_callback), entry, 0);
981 
982     /* Drag dest. */
983     gtk_drag_dest_set (GTK_WIDGET (entry),
984                        GTK_DEST_DEFAULT_ALL,
985                        drop_types, G_N_ELEMENTS (drop_types),
986                        GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
987     g_signal_connect (entry, "drag-data-received",
988                       G_CALLBACK (drag_data_received_callback), NULL);
989 
990     g_signal_connect_object (entry, "activate",
991                              G_CALLBACK (editable_activate_callback), entry, G_CONNECT_AFTER);
992     g_signal_connect_object (entry, "changed",
993                              G_CALLBACK (editable_changed_callback), entry, 0);
994 
995     priv->completion = gtk_entry_completion_new ();
996     priv->completions_store = gtk_list_store_new (1, G_TYPE_STRING);
997     gtk_entry_completion_set_model (priv->completion, GTK_TREE_MODEL (priv->completions_store));
998 
999     g_object_set (priv->completion,
1000                   "text-column", 0,
1001                   "inline-completion", TRUE,
1002                   "inline-selection", TRUE,
1003                   "popup-single-match", TRUE,
1004                   NULL);
1005 
1006     priv->completion_cell = gtk_cell_renderer_text_new ();
1007     g_object_set (priv->completion_cell, "xpad", 6, NULL);
1008 
1009     gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, FALSE);
1010     gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, "text", 0);
1011 
1012     gtk_entry_set_completion (GTK_ENTRY (entry), priv->completion);
1013 }
1014 
1015 GtkWidget *
nautilus_location_entry_new(void)1016 nautilus_location_entry_new (void)
1017 {
1018     GtkWidget *entry;
1019 
1020     entry = gtk_widget_new (NAUTILUS_TYPE_LOCATION_ENTRY, "max-width-chars", 350, NULL);
1021 
1022     return entry;
1023 }
1024 
1025 void
nautilus_location_entry_set_special_text(NautilusLocationEntry * entry,const char * special_text)1026 nautilus_location_entry_set_special_text (NautilusLocationEntry *entry,
1027                                           const char            *special_text)
1028 {
1029     NautilusLocationEntryPrivate *priv;
1030 
1031     priv = nautilus_location_entry_get_instance_private (entry);
1032 
1033     priv->has_special_text = TRUE;
1034 
1035     g_free (priv->special_text);
1036     priv->special_text = g_strdup (special_text);
1037 
1038     priv->setting_special_text = TRUE;
1039     gtk_entry_set_text (GTK_ENTRY (entry), special_text);
1040     priv->setting_special_text = FALSE;
1041 }
1042