GUI2: simplify various dialog's filter handling (#9650)

Closes #7868. This implements a farther-reaching cleanup without the regex, though I might revisit the regex method again later.
This commit is contained in:
Charles Dang 2024-12-18 02:01:05 -05:00 committed by GitHub
parent f113b2b7a7
commit e037694945
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 88 additions and 196 deletions

View file

@ -39,6 +39,7 @@
#include "serialization/string_utils.hpp"
#include "serialization/markup.hpp"
#include "utils/general.hpp"
#include "utils/ci_searcher.hpp"
#include "game_config_view.hpp"
#include <functional>
@ -86,7 +87,6 @@ game_load::game_load(const game_config_view& cache_config, savegame::load_game_m
, summary_(data.summary)
, games_()
, cache_config_(cache_config)
, last_words_()
{
}
@ -97,7 +97,7 @@ void game_load::pre_show()
text_box* filter = find_widget<text_box>("txtFilter", false, true);
filter->set_text_changed_callback(std::bind(&game_load::filter_text_changed, this, std::placeholders::_2));
filter->set_text_changed_callback(std::bind(&game_load::apply_filter_text, this, std::placeholders::_2));
listbox& list = find_widget<listbox>("savegame_list");
@ -317,41 +317,10 @@ void game_load::display_savegame()
get_window()->set_enter_disabled(!successfully_displayed_a_game);
}
void game_load::filter_text_changed(const std::string& text)
void game_load::apply_filter_text(const std::string& text)
{
apply_filter_text(text, false);
}
void game_load::apply_filter_text(const std::string& text, bool force)
{
listbox& list = find_widget<listbox>("savegame_list");
const std::vector<std::string> words = utils::split(text, ' ');
if(words == last_words_ && !force)
return;
last_words_ = words;
boost::dynamic_bitset<> show_items;
show_items.resize(list.get_item_count(), true);
if(!text.empty()) {
for(unsigned int i = 0; i < list.get_item_count() && i < games_.size(); i++) {
bool found = false;
for(const auto & word : words)
{
found = translation::ci_search(games_[i].name(), word);
if(!found) {
// one word doesn't match, we don't reach words.end()
break;
}
}
show_items[i] = found;
}
}
list.set_row_shown(show_items);
find_widget<listbox>("savegame_list").filter_rows_by(
[this, match = translation::make_ci_matcher(text)](std::size_t row) { return match(games_[row].name()); });
}
void game_load::evaluate_summary_string(std::stringstream& str, const config& cfg_summary)
@ -541,7 +510,7 @@ void game_load::handle_dir_select()
populate_game_list();
if(auto* filter = find_widget<text_box>("txtFilter", false, true)) {
apply_filter_text(filter->get_value(), true);
apply_filter_text(filter->get_value());
}
display_savegame();
}

View file

@ -46,18 +46,16 @@ private:
/** Update (both internally and visually) the list of games. */
void populate_game_list();
void filter_text_changed(const std::string& text);
void browse_button_callback();
void delete_button_callback();
void handle_dir_select();
/**
* Implementation detail of filter_text_changed and handle_dir_select
* Hides saves not matching the given filter.
*
* @param text Current contents of the textbox
* @param force If true, recalculate even if the text is the same as last time
*/
void apply_filter_text(const std::string& text, bool force);
void apply_filter_text(const std::string& text);
/** Part of display_savegame that might throw a config::error if the savegame data is corrupt. */
void display_savegame_internal(const savegame::save_info& game);
@ -77,8 +75,6 @@ private:
std::vector<savegame::save_info> games_;
const game_config_view& cache_config_;
std::vector<std::string> last_words_;
};
} // namespace dialogs
} // namespace gui2

View file

@ -22,6 +22,7 @@
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/toggle_button.hpp"
#include "gui/widgets/window.hpp"
#include "utils/ci_searcher.hpp"
#include "log.hpp"
@ -32,7 +33,6 @@ REGISTER_DIALOG(log_settings)
log_settings::log_settings()
: modal_dialog(window_id())
, last_words_()
{
//list of names must match those in logging.cfg
widget_id_.push_back("none");
@ -95,38 +95,8 @@ void log_settings::pre_show()
void log_settings::filter_text_changed(const std::string& text)
{
listbox& list = find_widget<listbox>("logger_listbox");
const std::vector<std::string> words = utils::split(text, ' ');
if(words == last_words_) {
return;
}
last_words_ = words;
boost::dynamic_bitset<> show_items;
show_items.resize(list.get_item_count(), true);
if(!text.empty()) {
for(unsigned int i = 0; i < list.get_item_count(); i++) {
assert(i < domain_list_.size());
bool found = false;
for(const auto& word : words)
{
found = translation::ci_search(domain_list_[i], word);
if(!found) {
break;
}
}
show_items[i] = found;
}
}
list.set_row_shown(show_items);
find_widget<listbox>("logger_listbox").filter_rows_by(
[this, match = translation::make_ci_matcher(text)](std::size_t row) { return match(domain_list_[row]); });
}
void log_settings::post_show()

View file

@ -50,9 +50,6 @@ private:
virtual void post_show() override;
void filter_text_changed(const std::string& text);
std::vector<std::string> last_words_;
};
} // namespace dialogs

View file

@ -28,6 +28,7 @@
#include "gui/widgets/window.hpp"
#include "gettext.hpp"
#include "units/types.hpp"
#include "utils/ci_searcher.hpp"
#include <functional>
@ -45,7 +46,6 @@ unit_create::unit_create()
, gender_(last_gender)
, choice_(last_chosen_type_id)
, variation_(last_variation)
, last_words_()
{
}
@ -230,46 +230,14 @@ void unit_create::list_item_clicked()
void unit_create::filter_text_changed(const std::string& text)
{
listbox& list = find_widget<listbox>("unit_type_list");
const std::vector<std::string> words = utils::split(text, ' ');
if(words == last_words_)
return;
last_words_ = words;
boost::dynamic_bitset<> show_items;
show_items.resize(list.get_item_count(), true);
if(!text.empty()) {
for(unsigned int i = 0; i < list.get_item_count(); i++) {
grid* row = list.get_row_grid(i);
// grid::iterator it = row->begin();
label& type_label = row->find_widget<label>("unit_type");
label& race_label = row->find_widget<label>("race");
assert(i < units_.size());
const std::string& unit_type_id = units_[i] ? units_[i]->id() : "";
bool found = false;
for(const auto & word : words)
{
found = translation::ci_search(type_label.get_label().str(), word) ||
translation::ci_search(race_label.get_label().str(), word) ||
translation::ci_search(unit_type_id, word);
if(!found) {
// one word doesn't match, we don't reach words.end()
break;
}
}
show_items[i] = found;
}
}
list.set_row_shown(show_items);
find_widget<listbox>("unit_type_list")
.filter_rows_by([this, match = translation::make_ci_matcher(text)](std::size_t row) {
return match(
units_[row]->type_name(),
units_[row]->race()->plural_name(),
units_[row]->id()
);
});
}
void unit_create::gender_toggle_callback(const unit_race::GENDER val)

View file

@ -69,8 +69,6 @@ private:
std::string variation_;
std::vector<std::string> last_words_;
virtual const std::string& window_id() const override;
virtual void pre_show() override;

View file

@ -37,6 +37,7 @@
#include "units/unit.hpp"
#include "units/ptr.hpp"
#include "units/types.hpp"
#include "utils/ci_searcher.hpp"
#include <functional>
#include "whiteboard/manager.hpp"
@ -441,35 +442,10 @@ void unit_recall::post_show()
void unit_recall::filter_text_changed(const std::string& text)
{
listbox& list = find_widget<listbox>("recall_list");
const std::vector<std::string> words = utils::split(text, ' ');
if(words == last_words_)
return;
last_words_ = words;
boost::dynamic_bitset<> show_items;
show_items.resize(list.get_item_count(), true);
if(!text.empty()) {
for(unsigned int i = 0; i < list.get_item_count(); i++) {
bool found = false;
for(const auto & word : words) {
found = translation::ci_search(filter_options_[i], word);
if(!found) {
// one word doesn't match, we don't reach words.end()
break;
}
}
show_items[i] = found;
}
}
list.set_row_shown(show_items);
auto& list = find_widget<listbox>("recall_list");
list.filter_rows_by([this, match = translation::make_ci_matcher(text)](std::size_t row) {
return match(filter_options_[row]);
});
// Disable rename and dismiss buttons if no units are shown
const bool any_shown = list.any_rows_shown();

View file

@ -26,6 +26,7 @@
#include "help/help.hpp"
#include "team.hpp"
#include "units/types.hpp"
#include "utils/ci_searcher.hpp"
#include <functional>
@ -61,30 +62,17 @@ static inline std::string gray_if_unrecruitable(const std::string& text, const b
// Compare unit_create::filter_text_change
void unit_recruit::filter_text_changed(const std::string& text)
{
listbox& list = find_widget<listbox>("recruit_list");
find_widget<listbox>("recruit_list")
.filter_rows_by([this, match = translation::make_ci_matcher(text)](std::size_t row) {
const unit_type* type = recruit_list_[row];
if(!type) return true;
const std::vector<std::string> words = utils::split(text, ' ');
const auto default_gender = !type->genders().empty() ? type->genders().front() : unit_race::MALE;
const auto race = type->race();
if(words == last_words_)
return;
last_words_ = words;
boost::dynamic_bitset<> show_items;
show_items.resize(list.get_item_count(), true);
if(!text.empty()) {
for(unsigned int i = 0; i < list.get_item_count(); i++) {
assert(i < recruit_list_.size());
const unit_type* type = recruit_list_[i];
if(!type) continue;
auto default_gender = !type->genders().empty()
? type->genders().front() : unit_race::MALE;
const auto* race = type->race();
// List of possible match criteria for this unit type. Empty values will
// never match.
auto criteria = std::make_tuple(
// List of possible match criteria for this unit type.
// Empty values will never match.
return match(
(game_config::debug ? type->id() : ""),
type->type_name(),
std::to_string(type->level()),
@ -92,27 +80,7 @@ void unit_recruit::filter_text_changed(const std::string& text)
(race ? race->name(default_gender) : ""),
(race ? race->plural_name() : "")
);
bool found = false;
for(const auto & word : words)
{
// Search for the name in the local language.
// In debug mode, also search for the type id.
std::apply([&](auto&&... criterion) {
found = (translation::ci_search(criterion, word) || ...);
}, criteria);
if(!found) {
// one word doesn't match, we don't reach words.end()
break;
}
}
show_items[i] = found;
}
}
list.set_row_shown(show_items);
});
}
void unit_recruit::pre_show()

View file

@ -54,8 +54,6 @@ private:
team& team_;
int selected_index_;
std::vector<std::string> last_words_;
};
} // namespace dialogs

View file

@ -246,6 +246,18 @@ void listbox::set_row_shown(const boost::dynamic_bitset<>& shown)
}
}
void listbox::filter_rows_by(const std::function<bool(std::size_t)>& filter)
{
boost::dynamic_bitset<> mask;
mask.resize(get_item_count(), true);
for(std::size_t i = 0; i < mask.size(); ++i) {
mask[i] = std::invoke(filter, i);
}
set_row_shown(mask);
}
boost::dynamic_bitset<> listbox::get_rows_shown() const
{
return generator_->get_items_shown();

View file

@ -132,6 +132,9 @@ public:
*/
void set_row_shown(const boost::dynamic_bitset<>& shown);
/** Hides all rows for which the given predicate returns false. */
void filter_rows_by(const std::function<bool(std::size_t)>& filter);
/**
* Returns a list of visible rows
*

37
src/utils/ci_searcher.hpp Normal file
View file

@ -0,0 +1,37 @@
/*
Copyright (C) 2024
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "gettext.hpp"
#include "serialization/string_utils.hpp"
namespace translation
{
/** Returns a function which performs locale-aware case-insensitive search. */
inline auto make_ci_matcher(std::string_view filter_text)
{
return [words = utils::split(filter_text, ' ')](auto&&... to_match) {
for(const auto& word : words) {
if(!(translation::ci_search(to_match, word) || ...)) {
return false;
}
}
return true;
};
}
} // namespace translation