Serenity Operating System
at master 104 lines 3.8 kB view raw
1/* 2 * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "CharacterSearchWidget.h" 9#include "SearchCharacters.h" 10#include <Applications/CharacterMap/CharacterSearchWindowGML.h> 11#include <LibCore/Debounce.h> 12 13struct SearchResult { 14 u32 code_point; 15 DeprecatedString code_point_string; 16 DeprecatedString display_text; 17}; 18 19class CharacterSearchModel final : public GUI::Model { 20public: 21 CharacterSearchModel() = default; 22 23 int row_count(GUI::ModelIndex const&) const override { return m_data.size(); } 24 int column_count(GUI::ModelIndex const&) const override { return 2; } 25 26 GUI::Variant data(GUI::ModelIndex const& index, GUI::ModelRole role) const override 27 { 28 auto& result = m_data.at(index.row()); 29 if (role == GUI::ModelRole::Display) { 30 if (index.column() == 0) 31 return result.code_point_string; 32 return result.display_text; 33 } 34 if (role == GUI::ModelRole::Custom) 35 return result.code_point; 36 return {}; 37 } 38 39 void clear() 40 { 41 m_data.clear(); 42 invalidate(); 43 } 44 45 void add_result(SearchResult result) 46 { 47 m_data.append(move(result)); 48 invalidate(); 49 } 50 51private: 52 Vector<SearchResult> m_data; 53}; 54 55CharacterSearchWidget::CharacterSearchWidget() 56{ 57 load_from_gml(character_search_window_gml).release_value_but_fixme_should_propagate_errors(); 58 59 m_search_input = find_descendant_of_type_named<GUI::TextBox>("search_input"); 60 m_results_table = find_descendant_of_type_named<GUI::TableView>("results_table"); 61 62 m_search_input->on_up_pressed = [this] { m_results_table->move_cursor(GUI::AbstractView::CursorMovement::Up, GUI::AbstractView::SelectionUpdate::Set); }; 63 m_search_input->on_down_pressed = [this] { m_results_table->move_cursor(GUI::AbstractView::CursorMovement::Down, GUI::AbstractView::SelectionUpdate::Set); }; 64 65 m_search_input->on_change = Core::debounce([this] { search(); }, 100); 66 67 m_results_table->horizontal_scrollbar().set_visible(false); 68 m_results_table->set_column_headers_visible(false); 69 m_results_table->set_model(adopt_ref(*new CharacterSearchModel())); 70 m_results_table->on_selection_change = [&] { 71 auto& model = static_cast<CharacterSearchModel&>(*m_results_table->model()); 72 auto index = m_results_table->selection().first(); 73 auto code_point = model.data(index, GUI::ModelRole::Custom).as_u32(); 74 if (on_character_selected) 75 on_character_selected(code_point); 76 }; 77} 78 79void CharacterSearchWidget::search() 80{ 81 ScopeGuard guard { [&] { m_results_table->set_updates_enabled(true); } }; 82 m_results_table->set_updates_enabled(false); 83 84 // TODO: Sort the results nicely. They're sorted by code-point for now, which is easy, but not the most useful. 85 // Sorting intelligently in a style similar to Assistant would be nicer. 86 // Note that this will mean limiting the number of results some other way. 87 auto& model = static_cast<CharacterSearchModel&>(*m_results_table->model()); 88 model.clear(); 89 auto query = m_search_input->text(); 90 if (query.is_empty()) 91 return; 92 for_each_character_containing(query, [&](auto code_point, auto display_name) { 93 StringBuilder builder; 94 builder.append_code_point(code_point); 95 96 model.add_result({ code_point, builder.to_deprecated_string(), move(display_name) }); 97 98 // Stop when we reach 250 results. 99 // This is already too many for the search to be useful, and means we don't spend forever recalculating the column size. 100 if (model.row_count({}) >= 250) 101 return IterationDecision::Break; 102 return IterationDecision::Continue; 103 }); 104}