From 520c4bbd555f0513a1e0db9c1c5d0d7f1310b142 Mon Sep 17 00:00:00 2001 From: Gleb Smirnov Date: Mon, 24 Oct 2022 07:45:40 +0300 Subject: [PATCH] feat: Save selected tool between sessions Fixes #96 --- ...m.github.liferooter.textpieces.gschema.xml | 6 +- po/POTFILES | 15 +- resources/meson.build | 1 + resources/textpieces.gresource.xml | 1 + resources/ui/Preferences.blp | 2 +- resources/ui/Search.blp | 60 ++++ resources/ui/Window.blp | 94 +----- src/Search.vala | 151 --------- src/meson.build | 32 +- src/{ => tools}/Tool.vala | 0 src/{ => tools}/ToolsController.vala | 28 -- src/{ => utils}/Recoloring.vala | 0 src/{ => utils}/Utils.vala | 0 src/{ => widgets}/Editor.vala | 0 src/widgets/Search.vala | 314 ++++++++++++++++++ src/{ => widgets}/SearchBar.vala | 0 src/{ => widgets}/SearchEntry.vala | 0 src/{ => widgets}/Window.vala | 156 ++------- .../preferences}/Preferences.vala | 0 .../preferences}/ToolSettings.vala | 0 .../preferences/pages}/CustomToolPage.vala | 0 .../preferences/pages}/NewToolPage.vala | 0 22 files changed, 444 insertions(+), 416 deletions(-) create mode 100644 resources/ui/Search.blp delete mode 100644 src/Search.vala rename src/{ => tools}/Tool.vala (100%) rename src/{ => tools}/ToolsController.vala (92%) rename src/{ => utils}/Recoloring.vala (100%) rename src/{ => utils}/Utils.vala (100%) rename src/{ => widgets}/Editor.vala (100%) create mode 100644 src/widgets/Search.vala rename src/{ => widgets}/SearchBar.vala (100%) rename src/{ => widgets}/SearchEntry.vala (100%) rename src/{ => widgets}/Window.vala (72%) rename src/{ => widgets/preferences}/Preferences.vala (100%) rename src/{ => widgets/preferences}/ToolSettings.vala (100%) rename src/{ => widgets/preferences/pages}/CustomToolPage.vala (100%) rename src/{ => widgets/preferences/pages}/NewToolPage.vala (100%) diff --git a/data/com.github.liferooter.textpieces.gschema.xml b/data/com.github.liferooter.textpieces.gschema.xml index 3164a75..6366ed9 100644 --- a/data/com.github.liferooter.textpieces.gschema.xml +++ b/data/com.github.liferooter.textpieces.gschema.xml @@ -48,10 +48,10 @@ SPDX-License-Identifier: GPL-3.0-or-later Is maximized Whether the application window is maximized - + '' - Default tool - Tool to select on start. Its value must be a name of tool's script. + Selected tool + Used to restore tool choice on restart. Its value must be a name of tool's script. diff --git a/po/POTFILES b/po/POTFILES index 8b0e60d..6c07333 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -5,13 +5,16 @@ data/com.github.liferooter.textpieces.desktop.in data/com.github.liferooter.textpieces.appdata.xml.in data/com.github.liferooter.textpieces.gschema.xml -src/Editor.vala -src/NewToolPage.vala -src/Preferences.vala -src/SearchEntry.vala -src/ToolSettings.vala -src/Window.vala + +src/widgets/Editor.vala +src/widgets/Window.vala +src/widgets/SearchEntry.vala +src/widgets/preferences/Preferences.vala +src/widgets/preferences/ToolSettings.vala +src/widgets/preferences/pages/NewToolPage.vala + resources/ui/CustomToolPage.blp +resources/ui/Search.blp resources/ui/Editor.blp resources/ui/NewToolPage.blp resources/ui/Preferences.blp diff --git a/resources/meson.build b/resources/meson.build index c19e129..da887bf 100644 --- a/resources/meson.build +++ b/resources/meson.build @@ -13,6 +13,7 @@ blueprints = custom_target('blueprints', 'ui/ShortcutsWindow.blp', 'ui/ToolSettings.blp', 'ui/Window.blp', + 'ui/Search.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], diff --git a/resources/textpieces.gresource.xml b/resources/textpieces.gresource.xml index 97da73a..d252023 100644 --- a/resources/textpieces.gresource.xml +++ b/resources/textpieces.gresource.xml @@ -17,6 +17,7 @@ SPDX-License-Identifier: GPL-3.0-or-later ui/ToolSettings.ui ui/Window.ui ui/Editor.ui + ui/Search.ui ui/ShortcutsWindow.ui diff --git a/resources/ui/Preferences.blp b/resources/ui/Preferences.blp index 424a1d5..d462db0 100644 --- a/resources/ui/Preferences.blp +++ b/resources/ui/Preferences.blp @@ -66,7 +66,7 @@ template TextPiecesPreferences : Adw.PreferencesWindow { SpinButton spaces_in_tab { valign: center; climb-rate: 1; - adjustment: + adjustment: Adjustment { lower: 1; upper: 21; diff --git a/resources/ui/Search.blp b/resources/ui/Search.blp new file mode 100644 index 0000000..e9c808f --- /dev/null +++ b/resources/ui/Search.blp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Gleb Smirnov +// +// SPDX-License-Identifier: GPL-3.0-or-later + +using Gtk 4.0; +using Adw 1; + +template TextPiecesSearch : Adw.Bin { + Stack search_stack { + transition-type: crossfade; + + StackPage { + name: "search"; + child: ScrolledWindow { + child: Viewport results_viewport { + scroll-to-focus: true; + + Adw.Clamp { + maximum-size: 600; + + ListBox results_listbox { + styles [ + "boxed-list", + ] + + row-activated => on_row_activated(); + margin-top: 24; + margin-bottom: 24; + valign: start; + selection-mode: browse; + } + } + }; + }; + } + + StackPage { + name: "placeholder"; + child: Adw.StatusPage { + icon-name: "applications-utilities-symbolic"; + title: _("No Tools Found"); + + Button { + styles [ + "flat", + ] + + halign: center; + action-name: "win.tools-settings"; + + Adw.ButtonContent { + icon-name: "list-add-symbolic"; + label: _("_Add Custom Tool"); + use-underline: true; + } + } + }; + } + } +} diff --git a/resources/ui/Window.blp b/resources/ui/Window.blp index 86530bb..e8bc7ee 100644 --- a/resources/ui/Window.blp +++ b/resources/ui/Window.blp @@ -89,17 +89,7 @@ template TextPiecesWindow : Adw.ApplicationWindow { SearchBar search_bar { search-mode-enabled: bind tool_button.active bidirectional; - child: - SearchEntry search_entry { - changed => update_search_results(); - activate => on_search_activated(); - - EventControllerKey search_event_controller { - key-pressed => on_search_entry_key(); - } - } - - ; + child: SearchEntry search_entry {}; } Stack content_stack { @@ -118,84 +108,10 @@ template TextPiecesWindow : Adw.ApplicationWindow { StackPage { name: "search"; - child: - Stack search_stack { - transition-type: crossfade; - - StackPage { - name: "search"; - child: - ScrolledWindow { - child: Viewport search_viewport { - scroll-to-focus: true; - - Adw.Clamp { - maximum-size: 600; - - ListBox search_listbox { - styles [ - "boxed-list", - ] - - row-activated => on_row_activated(); - margin-top: 24; - margin-bottom: 24; - valign: start; - selection-mode: browse; - } - } - } - - ; - } - - ; - } - - StackPage { - name: "placeholder"; - child: - Adw.StatusPage { - icon-name: "applications-utilities-symbolic"; - title: _("No Tools Found"); - - Button { - styles [ - "flat", - ] - - halign: center; - action-name: "win.tools-settings"; - child: - Box { - spacing: 8; - - Image { - styles [ - "dim-label", - ] - - icon-name: "list-add-symbolic"; - } - - Label { - styles [ - "dim-label", - ] - - label: _("Add Custom Tool"); - } - } - - ; - } - } - - ; - } - } - - ; + child: .TextPiecesSearch search { + search-entry: search_entry; + tool-selected => on_tool_selected(); + }; } } } diff --git a/src/Search.vala b/src/Search.vala deleted file mode 100644 index d9348da..0000000 --- a/src/Search.vala +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Gleb Smirnov -// -// SPDX-License-Identifier: GPL-3.0-or-later - -namespace TextPieces.Search { - /** - * Compare tools by search irrelevance - * - * Less relevant tool should be - * larger than more relevant - * to be below more relevant tool - * in search results. If tools are - * equal, compare tools' names. - * - * @param a one tool - * @param b another tool - * @param query search query - * - * @return zero if tools are equal, positive if `a` is larger, negative otherwise - */ - public int tool_sort_func (Tool a, Tool b, string query) { - var res = calculate_irrelevance (a, query) - - calculate_irrelevance (b, query); - if (res == 0) - return strcmp (a.translated_name, b.translated_name); - return res; - } - - /** - * Functions used to filter tools by search relevance - * - * Don't show tool if its irrelevance is infinite - * - * @param tool the tool - * @param query search query - * - * @return whether to show tool in search results - */ - public bool tool_filter_func (Tool tool, string query) { - return calculate_irrelevance (tool, query) != int.MAX; - } - - /** - * Calculate tool irrelevance - * - * This method is used to filter - * and sort search results - * - * @param tool the tool - * @param query search query - * - * @return tool's search irrelevance - */ - int calculate_irrelevance (Tool tool, string query) { - /* Get case-independent form of search query */ - var casefolded_query = query.casefold (); - - return int.min ( - /* Calculate non-translated tool's irrelevance */ - calculate_irrelevance_for_fields ({ - tool.name .casefold (), - tool.description.casefold () - }, casefolded_query), - /* Calculate translated tool's irrelevance */ - calculate_irrelevance_for_fields ({ - tool.translated_name .casefold (), - tool.translated_description.casefold () - }, casefolded_query) - ); - } - - /** - * Calculate fields' irrelevance - * - * This method gets list of fields - * and search query and returns fields' - * search irrelevance. The alghorythm is as follows: - * - * 1. If query is empty string, algorythm finishes, - * irrelevance is zero. It's used to fallback - * to alphabetical sort. - * 2. At the start of the alghorythm irrelevance - * is equals to zero - * 3. Query is divided into words, called terms - * 4. Algorythm iterates over terms - * 5. I search for term in fields, from the first - * field to the last field - * 6. If term is not found, algorythm finishes, - * irrelevance is infinite - * 7. If term is found in the field, increase - * irrelevance by the difference from the index of - * the match start and then cut out part of field - * from the start of field to the end of match - * 8. At the end of the algorythm, increase - * irrelevance by sum of lengths of first fields - * which don't contain any terms - * - * @param fields list of the fields - * @param query search query - * - * @return tool's search irrelevance - */ - int calculate_irrelevance_for_fields (string[] fields, string query) { - /* If query is empty, return zero */ - if (query == "") - return 0; - - /* Split query to terms */ - var terms = query.split (" "); - - /* Create array of field begginings. - It's used to easily cut fields as said - in the algorythm */ - var field_beginning = new int[fields.length]; - - /* Initial irrelevance is zero */ - var irrelevance = 0; - - /* Iterate over terms */ - foreach (var term in terms) { - /* Find first field containing the term */ - var matching_field = 0; - var match = 0; - - while (matching_field < fields.length - && (match = fields[matching_field].index_of (term, field_beginning[matching_field])) == -1) { - matching_field++; - } - - /* If there are no such field, - return infinity */ - if (match == -1) - return int.MAX; - - /* Increase irrelevance by - match beginning index */ - irrelevance += match - field_beginning[matching_field]; - - /* Cut the field */ - field_beginning[matching_field] = match + term.length; - } - - /* Increase irrelevance by sum of - lengths of first non-matched fields */ - for (var i = 0; i < field_beginning.length && field_beginning[i] == 0; i++) - irrelevance += fields[i].length; - - /* Return the result */ - return irrelevance; - } -} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 74442b0..d1b4043 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,19 +4,25 @@ textpieces_sources += [ 'Application.vala', - 'CustomToolPage.vala', - 'Editor.vala', - 'NewToolPage.vala', - 'Preferences.vala', - 'Recoloring.vala', - 'Search.vala', - 'SearchBar.vala', - 'SearchEntry.vala', - 'Tool.vala', - 'ToolSettings.vala', - 'ToolsController.vala', - 'Utils.vala', - 'Window.vala', + + 'tools/Tool.vala', + 'tools/ToolsController.vala', + + 'widgets/Window.vala', + 'widgets/Editor.vala', + 'widgets/Search.vala', + 'widgets/SearchBar.vala', + 'widgets/SearchEntry.vala', + + 'widgets/preferences/Preferences.vala', + 'widgets/preferences/ToolSettings.vala', + + 'widgets/preferences/pages/CustomToolPage.vala', + 'widgets/preferences/pages/NewToolPage.vala', + + 'utils/Utils.vala', + 'utils/Recoloring.vala', + ] executable('textpieces', [blueprint_hack, textpieces_sources], diff --git a/src/Tool.vala b/src/tools/Tool.vala similarity index 100% rename from src/Tool.vala rename to src/tools/Tool.vala diff --git a/src/ToolsController.vala b/src/tools/ToolsController.vala similarity index 92% rename from src/ToolsController.vala rename to src/tools/ToolsController.vala index 31d3cb8..cae1e49 100644 --- a/src/ToolsController.vala +++ b/src/tools/ToolsController.vala @@ -56,34 +56,6 @@ namespace TextPieces { get; private set; default = new ListStore (typeof (Tool)); } - /** - * Default tool - */ - public Tool? default_tool { - owned get { - var default_tool_script = Application.settings.get_string ("default-tool"); - - if (default_tool_script != "") { - for (uint i = 0; i < all_tools.get_n_items (); i++) { - var tool = (Tool) all_tools.get_item (i); - if (tool.script == default_tool_script) { - return tool; - } - } - - message ("Default tool is not found, so will be reset"); - default_tool = null; - } - - return null; - } set { - Application.settings.set_string ( - "default-tool", - value?.script ?? "" - ); - } - } - /** * Queue of deleted tools * pending script removal diff --git a/src/Recoloring.vala b/src/utils/Recoloring.vala similarity index 100% rename from src/Recoloring.vala rename to src/utils/Recoloring.vala diff --git a/src/Utils.vala b/src/utils/Utils.vala similarity index 100% rename from src/Utils.vala rename to src/utils/Utils.vala diff --git a/src/Editor.vala b/src/widgets/Editor.vala similarity index 100% rename from src/Editor.vala rename to src/widgets/Editor.vala diff --git a/src/widgets/Search.vala b/src/widgets/Search.vala new file mode 100644 index 0000000..697792e --- /dev/null +++ b/src/widgets/Search.vala @@ -0,0 +1,314 @@ +// SPDX-FileCopyrightText: 2022 Gleb Smirnov +// +// SPDX-License-Identifier: GPL-3.0-or-later + +using Gtk; +using Gdk; +using Adw; + +[GtkTemplate(ui = "/com/github/liferooter/textpieces/ui/Search.ui")] +class TextPieces.Search : Bin { + [GtkChild] unowned Viewport results_viewport; + [GtkChild] unowned ListBox results_listbox; + [GtkChild] unowned Stack search_stack; + + /** + * Search entry to use + */ + public Gtk.SearchEntry search_entry { get; construct; default = null; } + + /** + * Tool list model + */ + GLib.ListModel results_model; + + /** + * Sorter for search results + */ + Sorter sorter; + + /** + * Filter for search results + */ + Filter filter; + + /** + * Search entry key event controller + */ + EventControllerKey search_event_controller; + + construct { + /* Create model for search results */ + sorter = new CustomSorter ((a, b) => tool_sort_func ((Tool) a, (Tool) b)); + filter = new CustomFilter (tool_filter_func); + results_model = new SortListModel ( + new FilterListModel ( + Application.tools.all_tools, + filter + ), + sorter + ); + + /* Bind model to list box */ + results_listbox.bind_model ( + results_model, + build_list_row + ); + + /* Update search results on query changes */ + search_entry.changed.connect (update_search_results); + + /* Activate current row on activate */ + search_entry.activate.connect (activate_selected_result); + + /* Pass arrow keys events from + search entry to list box */ + search_event_controller = new EventControllerKey (); + search_event_controller.key_pressed.connect (on_search_entry_key); + search_entry.add_controller (search_event_controller); + } + + /** + * Emitted when a tool is selected + * + * @param tool selected tool + */ + public signal void tool_selected (Tool tool); + + /** + * Reset search + * + * Clear the search entry, scroll to the top, reset selection + */ + public void reset () { + /* Clear the search entry */ + search_entry.text = ""; + /* Scroll to the top */ + results_viewport.vadjustment.value = 0; + /* Select the first row */ + var row = results_listbox.get_row_at_index (0); + results_listbox.select_row (row); + } + + /** + * Choose selected result + */ + void activate_selected_result () { + var row = results_listbox.get_selected_row () + ?? results_listbox.get_row_at_index (0); + if (row != null) + results_listbox.row_activated (row); + } + + /** + * Process key pressed in search entry + * + * Move focus to the first search result + * if `Gdk.Key.Down` is pressed + * + * @param keyval pressed key value + * @param keycode pressed key code + * @param modifiers pressed modifiers + * + * @returnif whether the key press was handled + */ + bool on_search_entry_key (uint keyval, + uint keycode, + ModifierType modifiers) { + switch (keyval) { + case (Key.Down): + case (Key.Up): + /* Move focus to listbox */ + results_listbox.get_selected_row()?.grab_focus (); + /* Forward event to listbox */ + search_event_controller.forward (results_listbox); + /* Grab focus back when done */ + search_entry.grab_focus (); + + return true; + default: + return false; + } + } + + /** + * Emit `tool-selected` signal when tool is selected + */ + [GtkCallback] + void on_row_activated (ListBoxRow row) { + var index = row.get_index (); + var tool = (Tool) results_model.get_item (index); + tool_selected (tool); + } + + /** + * Update list of search results + */ + void update_search_results () { + /* Invalidate search sorter and filter */ + sorter.changed (DIFFERENT); + filter.changed (DIFFERENT); + + /* Select the first row if no row is selected */ + if (results_listbox.get_selected_row () == null) { + results_listbox.select_row ( + results_listbox.get_row_at_index (0) + ); + } + + /* Show placeholder if + there are no tools found */ + search_stack.set_visible_child_name ( + results_model.get_n_items () == 0 + ? "placeholder" + : "search" + ); + } + + /** + * Compare tools by search irrelevance + * + * Less relevant tool should be + * larger than more relevant + * to be below more relevant tool + * in search results. If tools are + * equal, compare tools' names. + * + * @param a one tool + * @param b another tool + * + * @return zero if tools are equal, positive if `a` is larger, negative otherwise + */ + public int tool_sort_func (Tool a, Tool b) { + var query = search_entry.text; + + var res = calculate_irrelevance (a, query) + - calculate_irrelevance (b, query); + if (res == 0) + return strcmp (a.translated_name, b.translated_name); + return res; + } + + /** + * Functions used to filter tools by search relevance + * + * Don't show tool if its irrelevance is infinite + * + * @param tool the tool + * @param query search query + * + * @return whether to show tool in search results + */ + public bool tool_filter_func (Object tool) { + return calculate_irrelevance ((Tool) tool, search_entry.text) != int.MAX; + } + + /** + * Calculate tool irrelevance + * + * This method is used to filter + * and sort search results + * + * @param tool the tool + * @param query search query + * + * @return tool's search irrelevance + */ + static int calculate_irrelevance (Tool tool, string query) { + /* Get case-independent form of search query */ + var casefolded_query = query.casefold (); + + return int.min ( + /* Calculate non-translated tool's irrelevance */ + calculate_irrelevance_for_fields ({ + tool.name .casefold (), + tool.description.casefold () + }, casefolded_query), + /* Calculate translated tool's irrelevance */ + calculate_irrelevance_for_fields ({ + tool.translated_name .casefold (), + tool.translated_description.casefold () + }, casefolded_query) + ); + } + + /** + * Calculate fields' irrelevance + * + * This method gets list of fields + * and search query and returns fields' + * search irrelevance. The alghorythm is as follows: + * + * 1. If query is empty string, algorythm finishes, + * irrelevance is zero. It's used to fallback + * to alphabetical sort. + * 2. At the start of the alghorythm irrelevance + * is equals to zero + * 3. Query is divided into words, called terms + * 4. Algorythm iterates over terms + * 5. I search for term in fields, from the first + * field to the last field + * 6. If term is not found, algorythm finishes, + * irrelevance is infinite + * 7. If term is found in the field, increase + * irrelevance by the difference from the index of + * the match start and then cut out part of field + * from the start of field to the end of match + * 8. At the end of the algorythm, increase + * irrelevance by sum of lengths of first fields + * which don't contain any terms + * + * @param fields list of the fields + * @param query search query + * + * @return tool's search irrelevance + */ + static int calculate_irrelevance_for_fields (string[] fields, string query) { + /* If query is empty, return zero */ + if (query == "") + return 0; + + /* Split query to terms */ + var terms = query.split (" "); + + /* Create array of field begginings. + It's used to easily cut fields as said + in the algorythm */ + var field_beginning = new int[fields.length]; + + /* Initial irrelevance is zero */ + var irrelevance = 0; + + /* Iterate over terms */ + foreach (var term in terms) { + /* Find first field containing the term */ + var matching_field = 0; + var match = 0; + + while (matching_field < fields.length + && (match = fields[matching_field].index_of (term, field_beginning[matching_field])) == -1) { + matching_field++; + } + + /* If there are no such field, + return infinity */ + if (match == -1) + return int.MAX; + + /* Increase irrelevance by + match beginning index */ + irrelevance += match - field_beginning[matching_field]; + + /* Cut the field */ + field_beginning[matching_field] = match + term.length; + } + + /* Increase irrelevance by sum of + lengths of first non-matched fields */ + for (var i = 0; i < field_beginning.length && field_beginning[i] == 0; i++) + irrelevance += fields[i].length; + + /* Return the result */ + return irrelevance; + } +} diff --git a/src/SearchBar.vala b/src/widgets/SearchBar.vala similarity index 100% rename from src/SearchBar.vala rename to src/widgets/SearchBar.vala diff --git a/src/SearchEntry.vala b/src/widgets/SearchEntry.vala similarity index 100% rename from src/SearchEntry.vala rename to src/widgets/SearchEntry.vala diff --git a/src/Window.vala b/src/widgets/Window.vala similarity index 72% rename from src/Window.vala rename to src/widgets/Window.vala index ff043e3..a90eb7e 100644 --- a/src/Window.vala +++ b/src/widgets/Window.vala @@ -9,14 +9,10 @@ namespace TextPieces { [GtkTemplate (ui = "/com/github/liferooter/textpieces/ui/Window.ui")] class Window : Adw.ApplicationWindow { [GtkChild] unowned Adw.ButtonContent tool_button_content; - [GtkChild] unowned Gtk.EventControllerKey search_event_controller; - [GtkChild] unowned Gtk.ListBox search_listbox; - [GtkChild] unowned Gtk.SearchEntry search_entry; [GtkChild] unowned TextPieces.Editor editor; [GtkChild] unowned Gtk.Stack content_stack; - [GtkChild] unowned Gtk.Stack search_stack; [GtkChild] unowned Gtk.ToggleButton tool_button; - [GtkChild] unowned Gtk.Viewport search_viewport; + [GtkChild] unowned Search search; /** * Text used instead of tool name @@ -31,21 +27,6 @@ namespace TextPieces { */ const string NO_TOOL_ICON = "applications-utilities-symbolic"; - /** - * Tool search model - */ - Gtk.SortListModel search_model; - - /** - * Sorter for search results - */ - Gtk.Sorter search_sorter; - - /** - * Filter for search results - */ - Gtk.Filter search_filter; - /** * Selected tool */ @@ -67,6 +48,12 @@ namespace TextPieces { _selected_tool.notify .connect (tool_changed); + /* Save selected tool in GSettings */ + Application.settings.set_string ( + "selected-tool", + value?.script ?? "" + ); + /* Trigger tool change callback */ tool_changed (); } @@ -105,31 +92,6 @@ namespace TextPieces { /* Load actions */ add_action_entries (ACTION_ENTRIES, this); - /* Create sorter for search results */ - search_sorter = new Gtk.CustomSorter ( - (a, b) => Search.tool_sort_func ((Tool) a, (Tool) b, search_entry.text) - ); - - /* Create filter for search results */ - search_filter = new Gtk.CustomFilter ( - (tool) => Search.tool_filter_func ((Tool) tool, search_entry.text) - ); - - /* Create model for search results */ - search_model = new Gtk.SortListModel ( - new Gtk.FilterListModel ( - Application.tools.all_tools, - search_filter - ), - search_sorter - ); - - /* Bind the model to list box */ - search_listbox.bind_model ( - search_model, - build_list_row - ); - /* Unselect tool if it's deleted */ Application.tools.delete_tool.connect ((tool) => { if (tool == selected_tool) @@ -139,7 +101,26 @@ namespace TextPieces { /* Initialize selected tool property and run its callback */ - selected_tool = Application.tools.default_tool; + var selected_tool_script = Application.settings.get_string ("selected-tool"); + var tool_found = false; + + if (selected_tool_script != "") { + for (uint i = 0; i < Application.tools.all_tools.get_n_items (); i++) { + var tool = (Tool) Application.tools.all_tools.get_item (i); + if (tool.script == selected_tool_script) { + selected_tool = tool; + tool_found = true; + break; + } + } + + if (!tool_found) { + message ("Previously selected tool is not found, so will be reset"); + selected_tool = null; + } + } else { + selected_tool = null; + } } /** @@ -392,83 +373,12 @@ namespace TextPieces { } /** - * Update search state - */ - [GtkCallback] - void update_search_results () { - /* Invalidate search sorter and filter */ - search_sorter.changed (DIFFERENT); - search_filter.changed (DIFFERENT); - - /* Select the first row if no row is selected */ - if (search_listbox.get_selected_row () == null) { - search_listbox.select_row ( - search_listbox.get_row_at_index (0) - ); - } - - /* Show placeholder if - there are no tools found */ - search_stack.set_visible_child_name ( - search_model.get_n_items () == 0 - ? "placeholder" - : "search" - ); - } - - /** - * Process key pressed in search entry - * - * Move focus to the first search result - * if `Gdk.Key.Down` is pressed - * - * @param keyval pressed key value - * @param keycode pressed key code - * @param modifiers pressed modifiers - * - * @returnif whether the key press was handled - */ - [GtkCallback] - bool on_search_entry_key (uint keyval, - uint keycode, - Gdk.ModifierType modifiers) { - switch (keyval) { - case (Gdk.Key.Down): - case (Gdk.Key.Up): - /* Move focus to listbox */ - search_entry.move_focus (TAB_FORWARD); - /* Forward event to listbox */ - search_event_controller.forward (search_listbox); - /* Grab focus back when done */ - search_entry.grab_focus (); - - return true; - default: - return false; - } - } - - /** - * Activate selected tool row - */ - [GtkCallback] - void on_search_activated () { - var row = search_listbox.get_selected_row () - ?? search_listbox.get_row_at_index (0); - if (row != null) - search_listbox.row_activated (row); - } - - /** - * Select tool from row and stop search - * - * @param row activated row + * Select tool and close the search */ [GtkCallback] - void on_row_activated (Gtk.ListBoxRow row) { - var tool = (Tool) search_model.get_item (row.get_index ()); + void on_tool_selected (Tool tool) { selected_tool = tool; - search_entry.stop_search (); + tool_button.active = false; } /** @@ -480,14 +390,10 @@ namespace TextPieces { if (tool_button.active) { /* Show search */ content_stack.visible_child_name = "search"; - /* Scroll search to the top */ - search_viewport.vadjustment.set_value (0); - /* Select the first row */ - var row = search_listbox.get_row_at_index (0); - search_listbox.select_row (row); } else { /* Show editor */ content_stack.visible_child_name = "editor"; + search.reset (); } } } diff --git a/src/Preferences.vala b/src/widgets/preferences/Preferences.vala similarity index 100% rename from src/Preferences.vala rename to src/widgets/preferences/Preferences.vala diff --git a/src/ToolSettings.vala b/src/widgets/preferences/ToolSettings.vala similarity index 100% rename from src/ToolSettings.vala rename to src/widgets/preferences/ToolSettings.vala diff --git a/src/CustomToolPage.vala b/src/widgets/preferences/pages/CustomToolPage.vala similarity index 100% rename from src/CustomToolPage.vala rename to src/widgets/preferences/pages/CustomToolPage.vala diff --git a/src/NewToolPage.vala b/src/widgets/preferences/pages/NewToolPage.vala similarity index 100% rename from src/NewToolPage.vala rename to src/widgets/preferences/pages/NewToolPage.vala