summaryrefslogtreecommitdiffstats
path: root/src/nautilus-location-entry.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nautilus-location-entry.c')
-rw-r--r--src/nautilus-location-entry.c845
1 files changed, 845 insertions, 0 deletions
diff --git a/src/nautilus-location-entry.c b/src/nautilus-location-entry.c
new file mode 100644
index 0000000..4ae027c
--- /dev/null
+++ b/src/nautilus-location-entry.c
@@ -0,0 +1,845 @@
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2000 Eazel, Inc.
+ *
+ * Nautilus is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Maciej Stachowiak <mjs@eazel.com>
+ * Ettore Perazzoli <ettore@gnu.org>
+ * Michael Meeks <michael@nuclecu.unam.mx>
+ * Andy Hertzfeld <andy@eazel.com>
+ *
+ */
+
+/* nautilus-location-bar.c - Location bar for Nautilus
+ */
+
+#include <config.h>
+#include "nautilus-location-entry.h"
+
+#include "nautilus-application.h"
+#include "nautilus-window.h"
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "nautilus-file-utilities.h"
+#include "nautilus-clipboard.h"
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <stdio.h>
+#include <string.h>
+
+
+typedef struct _NautilusLocationEntryPrivate
+{
+ char *current_directory;
+ GFilenameCompleter *completer;
+
+ guint idle_id;
+ gboolean idle_insert_completion;
+
+ GFile *last_location;
+
+ gboolean has_special_text;
+ NautilusLocationEntryAction secondary_action;
+
+ GtkEventController *controller;
+
+ GtkEntryCompletion *completion;
+ GtkListStore *completions_store;
+ GtkCellRenderer *completion_cell;
+} NautilusLocationEntryPrivate;
+
+enum
+{
+ CANCEL,
+ LOCATION_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_PRIVATE (NautilusLocationEntry, nautilus_location_entry, GTK_TYPE_ENTRY);
+
+static void on_after_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ gpointer data);
+
+static void on_after_delete_text (GtkEditable *editable,
+ gint start_pos,
+ gint end_pos,
+ gpointer data);
+
+static GFile *
+nautilus_location_entry_get_location (NautilusLocationEntry *entry)
+{
+ char *user_location;
+ GFile *location;
+
+ user_location = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
+ location = g_file_parse_name (user_location);
+ g_free (user_location);
+
+ return location;
+}
+
+static void
+nautilus_location_entry_set_text (NautilusLocationEntry *entry,
+ const char *new_text)
+{
+ GtkEditable *delegate;
+
+ delegate = gtk_editable_get_delegate (GTK_EDITABLE (entry));
+ g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+ g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_delete_text), entry);
+
+ gtk_editable_set_text (GTK_EDITABLE (entry), new_text);
+
+ g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+ g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_delete_text), entry);
+}
+
+static void
+nautilus_location_entry_insert_prefix (NautilusLocationEntry *entry,
+ GtkEntryCompletion *completion)
+{
+ GtkEditable *delegate;
+
+ delegate = gtk_editable_get_delegate (GTK_EDITABLE (entry));
+ g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+
+ gtk_entry_completion_insert_prefix (completion);
+
+ g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_insert_text), entry);
+}
+
+static void
+emit_location_changed (NautilusLocationEntry *entry)
+{
+ GFile *location;
+
+ location = nautilus_location_entry_get_location (entry);
+ g_signal_emit (entry, signals[LOCATION_CHANGED], 0, location);
+ g_object_unref (location);
+}
+
+static void
+nautilus_location_entry_update_action (NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+ const char *current_text;
+ GFile *location;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ if (priv->last_location == NULL)
+ {
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
+ return;
+ }
+
+ current_text = gtk_editable_get_text (GTK_EDITABLE (entry));
+ location = g_file_parse_name (current_text);
+
+ if (g_file_equal (priv->last_location, location))
+ {
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
+ }
+ else
+ {
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_GOTO);
+ }
+
+ g_object_unref (location);
+}
+
+static int
+get_editable_number_of_chars (GtkEditable *editable)
+{
+ char *text;
+ int length;
+
+ text = gtk_editable_get_chars (editable, 0, -1);
+ length = g_utf8_strlen (text, -1);
+ g_free (text);
+ return length;
+}
+
+static void
+set_position_and_selection_to_end (GtkEditable *editable)
+{
+ int end;
+
+ end = get_editable_number_of_chars (editable);
+ gtk_editable_select_region (editable, end, end);
+ gtk_editable_set_position (editable, end);
+}
+
+static void
+nautilus_location_entry_update_current_uri (NautilusLocationEntry *entry,
+ const char *uri)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ g_free (priv->current_directory);
+ priv->current_directory = g_strdup (uri);
+
+ nautilus_location_entry_set_text (entry, uri);
+ set_position_and_selection_to_end (GTK_EDITABLE (entry));
+}
+
+void
+nautilus_location_entry_set_location (NautilusLocationEntry *entry,
+ GFile *location)
+{
+ NautilusLocationEntryPrivate *priv;
+ gchar *uri, *formatted_uri;
+
+ g_assert (location != NULL);
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ /* Note: This is called in reaction to external changes, and
+ * thus should not emit the LOCATION_CHANGED signal. */
+ uri = g_file_get_uri (location);
+ formatted_uri = g_file_get_parse_name (location);
+
+ if (eel_uri_is_search (uri))
+ {
+ nautilus_location_entry_set_special_text (entry, "");
+ }
+ else
+ {
+ nautilus_location_entry_update_current_uri (entry, formatted_uri);
+ }
+
+ /* remember the original location for later comparison */
+ if (!priv->last_location ||
+ !g_file_equal (priv->last_location, location))
+ {
+ g_clear_object (&priv->last_location);
+ priv->last_location = g_object_ref (location);
+ }
+
+ nautilus_location_entry_update_action (entry);
+
+ /* invalidate the completions list */
+ gtk_list_store_clear (priv->completions_store);
+
+ g_free (uri);
+ g_free (formatted_uri);
+}
+
+static void
+set_prefix_dimming (GtkCellRenderer *completion_cell,
+ char *user_location)
+{
+ g_autofree char *location_basename = NULL;
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+
+ /* Dim the prefixes of the completion rows, leaving the basenames
+ * highlighted. This makes it easier to find what you're looking for.
+ *
+ * Perhaps a better solution would be to *only* show the basenames, but
+ * it would take a reimplementation of GtkEntryCompletion to align the
+ * popover. */
+
+ location_basename = g_path_get_basename (user_location);
+
+ attrs = pango_attr_list_new ();
+
+ /* 55% opacity. This is the same as the dim-label style class in Adwaita. */
+ attr = pango_attr_foreground_alpha_new (36045);
+ attr->end_index = strlen (user_location) - strlen (location_basename);
+ pango_attr_list_insert (attrs, attr);
+
+ g_object_set (completion_cell, "attributes", attrs, NULL);
+ pango_attr_list_unref (attrs);
+}
+
+static gboolean
+position_and_selection_are_at_end (GtkEditable *editable)
+{
+ int end;
+ int start_sel, end_sel;
+
+ end = get_editable_number_of_chars (editable);
+ if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel))
+ {
+ if (start_sel != end || end_sel != end)
+ {
+ return FALSE;
+ }
+ }
+ return gtk_editable_get_position (editable) == end;
+}
+
+/* Update the path completions list based on the current text of the entry. */
+static gboolean
+update_completions_store (gpointer callback_data)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+ GtkEditable *editable;
+ g_autofree char *absolute_location = NULL;
+ g_autofree char *user_location = NULL;
+ gboolean is_relative = FALSE;
+ int start_sel;
+ g_autofree char *uri_scheme = NULL;
+ g_auto (GStrv) completions = NULL;
+ char *completion;
+ int i;
+ GtkTreeIter iter;
+ int current_dir_strlen;
+
+ entry = NAUTILUS_LOCATION_ENTRY (callback_data);
+ priv = nautilus_location_entry_get_instance_private (entry);
+ editable = GTK_EDITABLE (entry);
+
+ priv->idle_id = 0;
+
+ /* Only do completions when we are typing at the end of the
+ * text. */
+ if (!position_and_selection_are_at_end (editable))
+ {
+ return FALSE;
+ }
+
+ if (gtk_editable_get_selection_bounds (editable, &start_sel, NULL))
+ {
+ user_location = gtk_editable_get_chars (editable, 0, start_sel);
+ }
+ else
+ {
+ user_location = gtk_editable_get_chars (editable, 0, -1);
+ }
+
+ g_strstrip (user_location);
+ set_prefix_dimming (priv->completion_cell, user_location);
+
+ uri_scheme = g_uri_parse_scheme (user_location);
+
+ if (!g_path_is_absolute (user_location) && uri_scheme == NULL && user_location[0] != '~')
+ {
+ is_relative = TRUE;
+ absolute_location = g_build_filename (priv->current_directory, user_location, NULL);
+ }
+ else
+ {
+ absolute_location = g_steal_pointer (&user_location);
+ }
+
+ completions = g_filename_completer_get_completions (priv->completer, absolute_location);
+
+ /* populate the completions model */
+ gtk_list_store_clear (priv->completions_store);
+
+ current_dir_strlen = strlen (priv->current_directory);
+ for (i = 0; completions[i] != NULL; i++)
+ {
+ completion = completions[i];
+
+ if (is_relative && strlen (completion) >= current_dir_strlen)
+ {
+ /* For relative paths, we need to strip the current directory
+ * (and the trailing slash) so the completions will match what's
+ * in the text entry */
+ completion += current_dir_strlen;
+ if (G_IS_DIR_SEPARATOR (completion[0]))
+ {
+ completion++;
+ }
+ }
+
+ gtk_list_store_append (priv->completions_store, &iter);
+ gtk_list_store_set (priv->completions_store, &iter, 0, completion, -1);
+ }
+
+ /* refilter the completions dropdown */
+ gtk_entry_completion_complete (priv->completion);
+
+ if (priv->idle_insert_completion)
+ {
+ /* insert the completion */
+ nautilus_location_entry_insert_prefix (entry, priv->completion);
+ }
+
+ return FALSE;
+}
+
+static void
+got_completion_data_callback (GFilenameCompleter *completer,
+ NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ if (priv->idle_id)
+ {
+ g_source_remove (priv->idle_id);
+ priv->idle_id = 0;
+ }
+ update_completions_store (entry);
+}
+
+static void
+finalize (GObject *object)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ entry = NAUTILUS_LOCATION_ENTRY (object);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ g_object_unref (priv->completer);
+
+ g_clear_object (&priv->last_location);
+ g_clear_object (&priv->completion);
+ g_clear_object (&priv->completions_store);
+ g_free (priv->current_directory);
+
+ G_OBJECT_CLASS (nautilus_location_entry_parent_class)->finalize (object);
+}
+
+static void
+nautilus_location_entry_dispose (GObject *object)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ entry = NAUTILUS_LOCATION_ENTRY (object);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ /* cancel the pending idle call, if any */
+ if (priv->idle_id != 0)
+ {
+ g_source_remove (priv->idle_id);
+ priv->idle_id = 0;
+ }
+
+
+ G_OBJECT_CLASS (nautilus_location_entry_parent_class)->dispose (object);
+}
+
+static void
+on_has_focus_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (object)))
+ {
+ return;
+ }
+
+ entry = NAUTILUS_LOCATION_ENTRY (object);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ /* The entry has text which is not worth preserving on focus-in. */
+ if (priv->has_special_text)
+ {
+ nautilus_location_entry_set_text (entry, "");
+ }
+}
+
+static void
+nautilus_location_entry_text_changed (NautilusLocationEntry *entry,
+ GParamSpec *pspec)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ priv->has_special_text = FALSE;
+}
+
+static void
+nautilus_location_entry_icon_release (GtkEntry *gentry,
+ GtkEntryIconPosition position,
+ gpointer unused)
+{
+ NautilusLocationEntry *entry;
+ NautilusLocationEntryPrivate *priv;
+
+ entry = NAUTILUS_LOCATION_ENTRY (gentry);
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ switch (priv->secondary_action)
+ {
+ case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
+ {
+ g_signal_emit_by_name (gentry, "activate", gentry);
+ }
+ break;
+
+ case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
+ {
+ nautilus_location_entry_set_text (entry, "");
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+}
+
+static gboolean
+nautilus_location_entry_key_pressed (GtkEventControllerKey *controller,
+ unsigned int keyval,
+ unsigned int keycode,
+ GdkModifierType state,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ GtkEditable *editable;
+ gboolean selected;
+
+
+ widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
+ editable = GTK_EDITABLE (widget);
+ selected = gtk_editable_get_selection_bounds (editable, NULL, NULL);
+
+ if (!gtk_editable_get_editable (editable))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ /* The location bar entry wants TAB to work kind of
+ * like it does in the shell for command completion,
+ * so if we get a tab and there's a selection, we
+ * should position the insertion point at the end of
+ * the selection.
+ */
+ if (keyval == GDK_KEY_Tab && !(state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ {
+ if (selected)
+ {
+ int position;
+
+ position = strlen (gtk_editable_get_text (GTK_EDITABLE (editable)));
+ gtk_editable_select_region (editable, position, position);
+ }
+ else
+ {
+ gtk_widget_error_bell (widget);
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ if ((keyval == GDK_KEY_Right || keyval == GDK_KEY_End) &&
+ !(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && selected)
+ {
+ set_position_and_selection_to_end (editable);
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+after_text_change (NautilusLocationEntry *self,
+ gboolean insert)
+{
+ NautilusLocationEntryPrivate *priv = nautilus_location_entry_get_instance_private (self);
+
+ /* Only insert a completion if a character was typed. Otherwise,
+ * update the completions store (i.e. in case backspace was pressed)
+ * but don't insert the completion into the entry. */
+ priv->idle_insert_completion = insert;
+
+ /* Do the expand at idle time to avoid slowing down typing when the
+ * directory is large. */
+ if (priv->idle_id == 0)
+ {
+ priv->idle_id = g_idle_add (update_completions_store, self);
+ }
+}
+
+static void
+on_after_insert_text (GtkEditable *editable,
+ const gchar *text,
+ gint length,
+ gint *position,
+ gpointer data)
+{
+ NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (data);
+
+ after_text_change (self, TRUE);
+}
+
+static void
+on_after_delete_text (GtkEditable *editable,
+ gint start_pos,
+ gint end_pos,
+ gpointer data)
+{
+ NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (data);
+
+ after_text_change (self, FALSE);
+}
+
+static void
+nautilus_location_entry_activate (GtkEntry *entry)
+{
+ NautilusLocationEntry *loc_entry;
+ NautilusLocationEntryPrivate *priv;
+ const gchar *entry_text;
+ gchar *full_path, *uri_scheme = NULL;
+ g_autofree char *path = NULL;
+
+ loc_entry = NAUTILUS_LOCATION_ENTRY (entry);
+ priv = nautilus_location_entry_get_instance_private (loc_entry);
+ entry_text = gtk_editable_get_text (GTK_EDITABLE (entry));
+ path = g_strdup (entry_text);
+ path = g_strchug (path);
+ path = g_strchomp (path);
+
+ if (path != NULL && *path != '\0')
+ {
+ uri_scheme = g_uri_parse_scheme (path);
+
+ if (!g_path_is_absolute (path) && uri_scheme == NULL && path[0] != '~')
+ {
+ /* Fix non absolute paths */
+ full_path = g_build_filename (priv->current_directory, path, NULL);
+ nautilus_location_entry_set_text (loc_entry, full_path);
+ g_free (full_path);
+ }
+
+ g_free (uri_scheme);
+ }
+}
+
+static void
+nautilus_location_entry_cancel (NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ nautilus_location_entry_set_location (entry, priv->last_location);
+}
+
+static void
+nautilus_location_entry_class_init (NautilusLocationEntryClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkEntryClass *entry_class;
+ g_autoptr (GtkShortcut) shortcut = NULL;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->dispose = nautilus_location_entry_dispose;
+ gobject_class->finalize = finalize;
+
+ entry_class = GTK_ENTRY_CLASS (class);
+ entry_class->activate = nautilus_location_entry_activate;
+
+ class->cancel = nautilus_location_entry_cancel;
+
+ signals[CANCEL] = g_signal_new
+ ("cancel",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (NautilusLocationEntryClass,
+ cancel),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[LOCATION_CHANGED] = g_signal_new
+ ("location-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST, 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+ shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (GDK_KEY_Escape, 0),
+ gtk_signal_action_new ("cancel"));
+ gtk_widget_class_add_shortcut (GTK_WIDGET_CLASS (class), shortcut);
+}
+
+void
+nautilus_location_entry_set_secondary_action (NautilusLocationEntry *entry,
+ NautilusLocationEntryAction secondary_action)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ if (priv->secondary_action == secondary_action)
+ {
+ return;
+ }
+
+ switch (secondary_action)
+ {
+ case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR:
+ {
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ "edit-clear-symbolic");
+ }
+ break;
+
+ case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO:
+ {
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ "go-next-symbolic");
+ }
+ break;
+
+ default:
+ {
+ g_assert_not_reached ();
+ }
+ }
+ priv->secondary_action = secondary_action;
+}
+
+static void
+editable_activate_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ NautilusLocationEntry *self = user_data;
+ const char *entry_text;
+ g_autofree gchar *path = NULL;
+
+ entry_text = gtk_editable_get_text (GTK_EDITABLE (entry));
+ path = g_strdup (entry_text);
+ path = g_strchug (path);
+ path = g_strchomp (path);
+
+ if (path != NULL && *path != '\0')
+ {
+ nautilus_location_entry_set_text (self, path);
+ emit_location_changed (self);
+ }
+}
+
+static void
+editable_changed_callback (GtkEntry *entry,
+ gpointer user_data)
+{
+ nautilus_location_entry_update_action (NAUTILUS_LOCATION_ENTRY (entry));
+}
+
+static void
+nautilus_location_entry_init (NautilusLocationEntry *entry)
+{
+ NautilusLocationEntryPrivate *priv;
+ GtkEventController *controller;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ priv->completer = g_filename_completer_new ();
+ g_filename_completer_set_dirs_only (priv->completer, TRUE);
+
+ nautilus_location_entry_set_secondary_action (entry,
+ NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR);
+
+ g_signal_connect (entry, "notify::has-focus",
+ G_CALLBACK (on_has_focus_changed), NULL);
+
+ g_signal_connect (entry, "notify::text",
+ G_CALLBACK (nautilus_location_entry_text_changed), NULL);
+
+ g_signal_connect (entry, "icon-release",
+ G_CALLBACK (nautilus_location_entry_icon_release), NULL);
+
+ g_signal_connect (priv->completer, "got-completion-data",
+ G_CALLBACK (got_completion_data_callback), entry);
+
+ g_signal_connect_object (entry, "activate",
+ G_CALLBACK (editable_activate_callback), entry, G_CONNECT_AFTER);
+ g_signal_connect_object (entry, "changed",
+ G_CALLBACK (editable_changed_callback), entry, 0);
+
+ controller = gtk_event_controller_key_new ();
+ gtk_widget_add_controller (GTK_WIDGET (entry), controller);
+ /* In GTK3, the Tab key binding (for focus change) happens in the bubble
+ * phase, and we want to stop that from happening. After porting to GTK4
+ * we need to check whether this is still correct. */
+ gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
+ g_signal_connect (controller, "key-pressed",
+ G_CALLBACK (nautilus_location_entry_key_pressed), NULL);
+
+ g_signal_connect_after (gtk_editable_get_delegate (GTK_EDITABLE (entry)),
+ "insert-text",
+ G_CALLBACK (on_after_insert_text),
+ entry);
+ g_signal_connect_after (gtk_editable_get_delegate (GTK_EDITABLE (entry)),
+ "delete-text",
+ G_CALLBACK (on_after_delete_text),
+ entry);
+
+ priv->completion = gtk_entry_completion_new ();
+ priv->completions_store = gtk_list_store_new (1, G_TYPE_STRING);
+ gtk_entry_completion_set_model (priv->completion, GTK_TREE_MODEL (priv->completions_store));
+
+ g_object_set (priv->completion,
+ "text-column", 0,
+ "inline-completion", FALSE,
+ "inline-selection", TRUE,
+ "popup-single-match", TRUE,
+ NULL);
+
+ priv->completion_cell = gtk_cell_renderer_text_new ();
+ g_object_set (priv->completion_cell, "xpad", 6, NULL);
+
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, FALSE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, "text", 0);
+
+ gtk_entry_set_completion (GTK_ENTRY (entry), priv->completion);
+}
+
+GtkWidget *
+nautilus_location_entry_new (void)
+{
+ GtkWidget *entry;
+
+ entry = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_LOCATION_ENTRY, NULL));
+
+ return entry;
+}
+
+void
+nautilus_location_entry_set_special_text (NautilusLocationEntry *entry,
+ const char *special_text)
+{
+ NautilusLocationEntryPrivate *priv;
+
+ priv = nautilus_location_entry_get_instance_private (entry);
+
+ nautilus_location_entry_set_text (entry, special_text);
+ priv->has_special_text = TRUE;
+}