From 48966efcf4125fa1af72bdbb7ec8378a29494b90 Mon Sep 17 00:00:00 2001 From: Xavier Claessens <xavier.claessens@collabora.com> Date: Fri, 8 Sep 2023 16:06:18 -0400 Subject: [PATCH] NautilusWindowSlot: Connect signals on query editor only once It is wasteful to connect/disconnect signals each time the editor is shown/hidden. This also make possible to receive those signals when the query changed while being hidden for type ahead. --- src/nautilus-window-slot.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c index a251e0248a..4f588acfd8 100644 --- a/src/nautilus-window-slot.c +++ b/src/nautilus-window-slot.c @@ -431,8 +431,6 @@ hide_query_editor (NautilusWindowSlot *self) view = nautilus_window_slot_get_current_view (self); - g_signal_handlers_disconnect_by_data (self->query_editor, self); - nautilus_query_editor_set_query (self->query_editor, NULL); if (nautilus_view_is_searching (view)) @@ -496,15 +494,6 @@ show_query_editor (NautilusWindowSlot *self) } gtk_widget_grab_focus (GTK_WIDGET (self->query_editor)); - - 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); } static void @@ -912,6 +901,15 @@ nautilus_window_slot_constructed (GObject *object) gtk_box_append (GTK_BOX (self->vbox), 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); -- GitLab From 05f8e6e9bee25c894d2f3582649750737081253b Mon Sep 17 00:00:00 2001 From: Xavier Claessens <xavier.claessens@collabora.com> Date: Mon, 28 Nov 2022 15:18:15 -0500 Subject: [PATCH] Support type ahead for fast browsing Searching is too slow when fast browsing directory tree. Even when recursing searching is disabled, it still takes time to load the new view and it breaks the browsing work flow. The use-case is when user has a very well known tree and want to open a file, for example "sources/nautilus/src/meson.build", they would type *VERY* fast: "s" <enter> "n" <enter> "s" <enter> "m" <enter>. When going many times per day into the same location, it becomes muscle memory, just like tab in a terminal. This is similar to how at least Windows file browser works. --- data/org.gnome.nautilus.gschema.xml | 5 + src/nautilus-files-view.c | 16 +++ src/nautilus-files-view.h | 5 + src/nautilus-global-preferences.h | 1 + src/nautilus-preferences-dialog.c | 5 + src/nautilus-query-editor.c | 6 + src/nautilus-window-slot.c | 113 +++++++++++++++--- .../ui/nautilus-preferences-dialog.ui | 15 +++ 8 files changed, 151 insertions(+), 15 deletions(-) diff --git a/data/org.gnome.nautilus.gschema.xml b/data/org.gnome.nautilus.gschema.xml index 2c0f45638b..45d793ac37 100644 --- a/data/org.gnome.nautilus.gschema.xml +++ b/data/org.gnome.nautilus.gschema.xml @@ -82,6 +82,11 @@ <summary>Always use the location entry, instead of the pathbar</summary> <description>If set to true, Files will always use a textual input entry for the location toolbar, instead of the pathbar.</description> </key> + <key type="b" name="type-ahead-search"> + <default>true</default> + <summary>Start searching on type ahead</summary> + <description>If set to true, typing on the files viewer will start searching. Otherwise it select first matching file.</description> + </key> <key name="recursive-search" enum="org.gnome.nautilus.SpeedTradeoff"> <default>'local-only'</default> <summary>Where to perform recursive search</summary> diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c index f23380497f..66045a2bb4 100644 --- a/src/nautilus-files-view.c +++ b/src/nautilus-files-view.c @@ -10105,3 +10105,19 @@ nautilus_files_view_new (guint id, return view; } + +void +nautilus_files_view_get_sort_state(NautilusFilesView *view, + GQuark *sort_attribute, + gboolean *reversed, + gboolean *directories_first) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + + g_autoptr(GVariant) value = nautilus_list_base_get_sort_state(priv->list_base); + const gchar *target_name; + g_variant_get(value, "(&sb)", &target_name, reversed); + *sort_attribute = g_quark_from_string(target_name); + *directories_first = g_settings_get_boolean(gtk_filechooser_preferences, + NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); +} diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h index b149c3d4a7..30b8859995 100644 --- a/src/nautilus-files-view.h +++ b/src/nautilus-files-view.h @@ -161,4 +161,9 @@ void nautilus_files_view_update_context_menus (NautilusFilesV void nautilus_files_view_update_toolbar_menus (NautilusFilesView *view); void nautilus_files_view_update_actions_state (NautilusFilesView *view); +void nautilus_files_view_get_sort_state (NautilusFilesView *view, + GQuark *sort_attribute, + gboolean *reversed, + gboolean *directories_first); + G_END_DECLS diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h index 0c1bbdad26..2bce2c477d 100644 --- a/src/nautilus-global-preferences.h +++ b/src/nautilus-global-preferences.h @@ -118,6 +118,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-dialog.c b/src/nautilus-preferences-dialog.c index b399948d52..d7987720cb 100644 --- a/src/nautilus-preferences-dialog.c +++ b/src/nautilus-preferences-dialog.c @@ -42,6 +42,8 @@ "show_create_link_row" #define NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET \ "use_tree_view_row" +#define NAUTILUS_PREFERENCES_DIALOG_TYPE_AHEAD_WIDGET \ + "type_ahead_search" /* combo preferences */ #define NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO \ @@ -361,6 +363,9 @@ nautilus_preferences_dialog_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); setup_detailed_date (builder); diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c index 9284152656..e729098a9c 100644 --- a/src/nautilus-query-editor.c +++ b/src/nautilus-query-editor.c @@ -876,6 +876,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 4f588acfd8..821c140db7 100644 --- a/src/nautilus-window-slot.c +++ b/src/nautilus-window-slot.c @@ -79,6 +79,9 @@ enum static guint signals[LAST_SIGNAL]; +/* In type ahead mode, clear entry if it did not change for a while */ +#define CLEAR_QUERY_EDITOR_TIMEOUT 1000 + struct _NautilusWindowSlot { AdwBin parent_instance; @@ -124,6 +127,7 @@ struct _NautilusWindowSlot /* Query editor */ NautilusQueryEditor *query_editor; NautilusQuery *pending_search_query; + guint clear_query_editor_timeout_id; /* Banner */ AdwBanner *banner; @@ -409,6 +413,48 @@ query_editor_focus_view_callback (NautilusQueryEditor *editor, } } +static GFile * +nautilus_window_slot_get_current_location(NautilusWindowSlot *self) +{ + if (self->pending_location != NULL) + { + return self->pending_location; + } + + return self->location; +} + +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 sort_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->sort_attribute, + ctx->directories_first, + ctx->reversed); +} + static void query_editor_changed_callback (NautilusQueryEditor *editor, NautilusQuery *query, @@ -419,9 +465,54 @@ query_editor_changed_callback (NautilusQueryEditor *editor, view = nautilus_window_slot_get_current_view (self); - /* Setting search query may cause the view to load a new location. */ - 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)) + { + /* Setting search query may cause the view to load a new location. */ + 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); + const gchar *name = nautilus_file_get_display_name(file); + g_autofree gchar *name_casefold = g_utf8_casefold(name, text_len); + g_autofree 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 */ + if (matches != NULL) + { + FileCompareForTypeAheadContext ctx; + nautilus_files_view_get_sort_state (NAUTILUS_FILES_VIEW (view), &ctx.sort_attribute, &ctx.reversed, &ctx.directories_first); + matches = g_list_sort_with_data (matches, file_compare_for_type_ahead, &ctx); + g_autolist(NautilusFile) selection = g_list_prepend (NULL, g_object_ref (matches->data)); + 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 @@ -459,17 +550,6 @@ hide_query_editor (NautilusWindowSlot *self) } } -static GFile * -nautilus_window_slot_get_current_location (NautilusWindowSlot *self) -{ - if (self->pending_location != NULL) - { - return self->pending_location; - } - - return self->location; -} - static void show_query_editor (NautilusWindowSlot *self) { @@ -628,7 +708,7 @@ nautilus_window_slot_handle_event (NautilusWindowSlot *self, state); } - if (retval) + if (retval && type_ahead_search ()) { nautilus_window_slot_set_search_visible (self, TRUE); } @@ -2149,6 +2229,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/resources/ui/nautilus-preferences-dialog.ui b/src/resources/ui/nautilus-preferences-dialog.ui index fffaa6803f..4e0bb0f796 100644 --- a/src/resources/ui/nautilus-preferences-dialog.ui +++ b/src/resources/ui/nautilus-preferences-dialog.ui @@ -27,6 +27,21 @@ <property name="use_underline">True</property> </object> </child> + <child> + <object class="AdwActionRow"> + <property name="activatable_widget">type_ahead_search</property> + <property name="subtitle_lines">0</property> + <property name="title" translatable="yes">Search on type ahead</property> + <property name="title_lines">0</property> + <property name="use_underline">True</property> + <property name="visible">True</property> + <child> + <object class="GtkSwitch" id="type_ahead_search"> + <property name="valign">center</property> + </object> + </child> + </object> + </child> </object> </child> <child> -- GitLab