diff --git a/data/org.gnome.nautilus.gschema.xml b/data/org.gnome.nautilus.gschema.xml index 94b17208d..438008f15 100644 --- a/data/org.gnome.nautilus.gschema.xml +++ b/data/org.gnome.nautilus.gschema.xml @@ -77,6 +77,11 @@ Always use the location entry, instead of the pathbar If set to true, then Nautilus browser windows will always use a textual input entry for the location toolbar, instead of the pathbar. + + true + Start searching on type ahead + If set to true, typing on the files viewer will start searching. Otherwise it select first matching file. + 'local-only' Where to perform recursive search diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h index 9d39bbc03..27c2e54f1 100644 --- a/src/nautilus-global-preferences.h +++ b/src/nautilus-global-preferences.h @@ -116,6 +116,7 @@ typedef enum /* Search behaviour */ #define NAUTILUS_PREFERENCES_RECURSIVE_SEARCH "recursive-search" +#define NAUTILUS_PREFERENCES_TYPE_AHEAD_SEARCH "type-ahead-search" /* Context menu options */ #define NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY "show-delete-permanently" diff --git a/src/nautilus-preferences-window.c b/src/nautilus-preferences-window.c index a1cd95823..b5e167120 100644 --- a/src/nautilus-preferences-window.c +++ b/src/nautilus-preferences-window.c @@ -41,6 +41,8 @@ "show_create_link_switch" #define NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET \ "use_tree_view_switch" +#define NAUTILUS_PREFERENCES_DIALOG_TYPE_AHEAD_WIDGET \ + "type_ahead_search" /* combo preferences */ #define NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO \ @@ -334,6 +336,9 @@ nautilus_preferences_window_setup (GtkBuilder *builder) bind_builder_bool (builder, nautilus_preferences, NAUTILUS_PREFERENCES_DIALOG_DELETE_PERMANENTLY_WIDGET, NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY); + bind_builder_bool (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_TYPE_AHEAD_WIDGET, + NAUTILUS_PREFERENCES_TYPE_AHEAD_SEARCH); bind_builder_combo_row (builder, nautilus_preferences, NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO, diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c index 95b284b7d..4ca27b001 100644 --- a/src/nautilus-query-editor.c +++ b/src/nautilus-query-editor.c @@ -742,6 +742,12 @@ nautilus_query_editor_set_query (NautilusQueryEditor *self, g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (self)); + /* Setting query to NULL causes reentry to set it to an empty query */ + if (self->change_frozen) { + g_set_object (&self->query, query); + return; + } + if (query != NULL) { text = nautilus_query_get_text (query); diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c index ac367db12..567df1c88 100644 --- a/src/nautilus-window-slot.c +++ b/src/nautilus-window-slot.c @@ -67,6 +67,9 @@ enum #define FILE_SHARING_SCHEMA_ID "org.gnome.desktop.file-sharing" +/* In type ahead mode, clear entry if it did not change for a while */ +#define CLEAR_QUERY_EDITOR_TIMEOUT 1000 + struct _NautilusWindowSlot { GtkBox parent_instance; @@ -105,10 +108,7 @@ struct _NautilusWindowSlot /* Query editor */ NautilusQueryEditor *query_editor; NautilusQuery *pending_search_query; - gulong qe_changed_id; - gulong qe_cancel_id; - gulong qe_activated_id; - gulong qe_focus_view_id; + guint clear_query_editor_timeout_id; GtkLabel *search_info_label; GtkRevealer *search_info_label_revealer; @@ -176,6 +176,7 @@ static void real_set_templates_menu (NautilusWindowSlot *self, GMenuModel *menu); static GMenuModel *real_get_templates_menu (NautilusWindowSlot *self); static void nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self); +static GFile *nautilus_window_slot_get_current_location (NautilusWindowSlot *self); void free_navigation_state (gpointer data) @@ -426,6 +427,37 @@ query_editor_focus_view_callback (NautilusQueryEditor *editor, } } +static gboolean +type_ahead_search (void) +{ + return g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_TYPE_AHEAD_SEARCH); +} + +static gboolean +clear_query_editor_timeout_callback (NautilusWindowSlot *self) +{ + nautilus_query_editor_set_query (self->query_editor, NULL); + self->clear_query_editor_timeout_id = 0; + return G_SOURCE_REMOVE; +} + +typedef struct { + GQuark attribute; + gboolean directories_first; + gboolean reversed; +} FileCompareForTypeAheadContext; + +static int +file_compare_for_type_ahead (gconstpointer a, gconstpointer b, gpointer user_data) +{ + FileCompareForTypeAheadContext *ctx = user_data; + return nautilus_file_compare_for_sort_by_attribute_q (NAUTILUS_FILE(a), + NAUTILUS_FILE(b), + ctx->attribute, + ctx->directories_first, + ctx->reversed); +} + static void query_editor_changed_callback (NautilusQueryEditor *editor, NautilusQuery *query, @@ -436,8 +468,61 @@ query_editor_changed_callback (NautilusQueryEditor *editor, view = nautilus_window_slot_get_current_view (self); - nautilus_view_set_search_query (view, query); - nautilus_window_slot_set_location (self, nautilus_view_get_location (view)); + if (nautilus_window_slot_get_search_visible (self)) + { + nautilus_view_set_search_query (view, query); + nautilus_window_slot_set_location (self, nautilus_view_get_location (view)); + } + else + { + /* Find all files with a display name that starts with the query, case insensitive. */ + GFile *location = nautilus_window_slot_get_current_location (self); + g_autoptr (NautilusDirectory) directory = nautilus_directory_get (location); + const gchar *text = nautilus_query_get_text (query); + g_autofree gchar *text_casefold = g_utf8_casefold (text, -1); + g_autofree gchar *text_collate = g_utf8_collate_key_for_filename (text_casefold, -1); + gsize text_len = strlen (text); + g_autolist (NautilusFile) files = nautilus_directory_get_file_list (directory); + g_autolist (NautilusFile) matches = NULL; + GList *l; + + for (l = files; l; l = l->next) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + g_autofree const gchar *name = nautilus_file_get_display_name (file); + g_autofree const gchar *name_casefold = g_utf8_casefold (name, text_len); + g_autofree const gchar *name_collate = g_utf8_collate_key_for_filename (name_casefold, -1); + + if (g_str_equal (name_collate, text_collate)) + { + matches = g_list_prepend (matches, nautilus_file_ref (file)); + } + } + + /* Select the first match */ + { + FileCompareForTypeAheadContext ctx; + GActionGroup *action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (view)); + g_autoptr (GVariant) value = g_action_group_get_action_state (action_group, "sort"); + const gchar *attribute_name; + g_autolist (NautilusFile) selection; + + g_variant_get (value, "(&sb)", &attribute_name, &ctx.reversed); + ctx.attribute = g_quark_from_string (attribute_name); + ctx.directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (view)); + matches = g_list_sort_with_data (matches, file_compare_for_type_ahead, &ctx); + + selection = matches; + matches = g_list_remove_link (matches, selection); + nautilus_view_set_selection (self->content_view, selection); + } + + /* Reset timeout that clears type ahead query */ + g_clear_handle_id (&self->clear_query_editor_timeout_id, g_source_remove); + self->clear_query_editor_timeout_id = g_timeout_add (CLEAR_QUERY_EDITOR_TIMEOUT, + G_SOURCE_FUNC (clear_query_editor_timeout_callback), + self); + } } static void @@ -447,11 +532,6 @@ hide_query_editor (NautilusWindowSlot *self) view = nautilus_window_slot_get_current_view (self); - g_clear_signal_handler (&self->qe_changed_id, self->query_editor); - g_clear_signal_handler (&self->qe_cancel_id, self->query_editor); - g_clear_signal_handler (&self->qe_activated_id, self->query_editor); - g_clear_signal_handler (&self->qe_focus_view_id, self->query_editor); - nautilus_query_editor_set_query (self->query_editor, NULL); if (nautilus_view_is_searching (view)) @@ -515,31 +595,6 @@ show_query_editor (NautilusWindowSlot *self) } gtk_widget_grab_focus (GTK_WIDGET (self->query_editor)); - - if (self->qe_changed_id == 0) - { - self->qe_changed_id = - g_signal_connect (self->query_editor, "changed", - G_CALLBACK (query_editor_changed_callback), self); - } - if (self->qe_cancel_id == 0) - { - self->qe_cancel_id = - g_signal_connect (self->query_editor, "cancel", - G_CALLBACK (query_editor_cancel_callback), self); - } - if (self->qe_activated_id == 0) - { - self->qe_activated_id = - g_signal_connect (self->query_editor, "activated", - G_CALLBACK (query_editor_activated_callback), self); - } - if (self->qe_focus_view_id == 0) - { - self->qe_focus_view_id = - g_signal_connect (self->query_editor, "focus-view", - G_CALLBACK (query_editor_focus_view_callback), self); - } } static void @@ -634,7 +689,7 @@ nautilus_window_slot_handle_event (NautilusWindowSlot *self, state); } - if (retval) + if (retval && type_ahead_search ()) { nautilus_window_slot_set_search_visible (self, TRUE); } @@ -886,6 +941,15 @@ nautilus_window_slot_constructed (GObject *object) gtk_box_append (GTK_BOX (self), extras_vbox); self->query_editor = NAUTILUS_QUERY_EDITOR (nautilus_query_editor_new ()); + g_signal_connect (self->query_editor, "changed", + G_CALLBACK (query_editor_changed_callback), self); + g_signal_connect (self->query_editor, "cancel", + G_CALLBACK (query_editor_cancel_callback), self); + g_signal_connect (self->query_editor, "activated", + G_CALLBACK (query_editor_activated_callback), self); + g_signal_connect (self->query_editor, "focus-view", + G_CALLBACK (query_editor_focus_view_callback), self); + /* We want to keep alive the query editor betwen additions and removals on the * UI, specifically when the toolbar adds or removes it */ g_object_ref_sink (self->query_editor); @@ -1972,6 +2036,9 @@ setup_view (NautilusWindowSlot *self, nautilus_window_slot_disconnect_content_view (self); + nautilus_query_editor_set_query (self->query_editor, NULL); + g_clear_handle_id (&self->clear_query_editor_timeout_id, g_source_remove); + self->new_content_view = view; nautilus_window_slot_connect_new_content_view (self); diff --git a/src/nautilus-window.c b/src/nautilus-window.c index 9429500c0..d9f6003e2 100644 --- a/src/nautilus-window.c +++ b/src/nautilus-window.c @@ -1579,6 +1579,7 @@ const GActionEntry win_entries[] = { "forward", action_forward }, { "back-n", action_back_n, "u" }, { "forward-n", action_forward_n, "u" }, + { "backspaceup", action_up }, { "up", action_up }, { "current-location-menu", action_show_current_location_menu }, { "open-location", action_open_location, "s" }, @@ -1644,6 +1645,7 @@ nautilus_window_initialize_actions (NautilusWindow *window) /* Only accesible by shorcuts */ nautilus_application_set_accelerators (app, "win.bookmark-current-location", ACCELS ("d", "AddFavorite")); nautilus_application_set_accelerator (app, "win.up", "Up"); + nautilus_application_set_accelerator (app, "win.backspaceup", "BackSpace"); nautilus_application_set_accelerators (app, "win.go-home", ACCELS ("Home", "HomePage", "Start")); nautilus_application_set_accelerator (app, "win.go-starred", "Favorites"); nautilus_application_set_accelerator (app, "win.tab-move-left", "Page_Up"); diff --git a/src/resources/ui/nautilus-preferences-window.ui b/src/resources/ui/nautilus-preferences-window.ui index cff1c278e..8ad928908 100644 --- a/src/resources/ui/nautilus-preferences-window.ui +++ b/src/resources/ui/nautilus-preferences-window.ui @@ -49,6 +49,21 @@ True + + + type_ahead_search + 0 + Search on type ahead + 0 + True + True + + + center + + + +