CharacterSearchWidget.cpp 3.8 KB

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