Add a C++ Sort Model class (#1688)

This allows automatically sorting a model.
This commit is contained in:
Simon Hausmann 2022-09-28 10:03:05 +02:00 committed by GitHub
parent cfbdce735f
commit 6df60fa723
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 293 additions and 2 deletions

View file

@ -877,7 +877,7 @@ struct FilterModelInner : private_api::ModelChangeListener
void row_changed(int index) override
{
auto existing_row = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index);
auto existing_row_index = existing_row - accepted_rows.begin();
auto existing_row_index = std::distance(accepted_rows.begin(), existing_row);
bool is_contained = existing_row != accepted_rows.end() && *existing_row == index;
auto accepted_updated_row = filter_fn(*source_model->row_data(index));
@ -897,7 +897,7 @@ struct FilterModelInner : private_api::ModelChangeListener
auto mapped_row_end =
std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index + count);
auto mapped_removed_len = mapped_row_end - mapped_row_start;
auto mapped_removed_len = std::distance(mapped_row_start, mapped_row_end);
auto mapped_removed_index =
(mapped_row_start != accepted_rows.end() && *mapped_row_start == index)
@ -1049,6 +1049,182 @@ private:
std::function<MappedModelData(const SourceModelData &)> map_fn;
};
template<typename ModelData>
class SortModel;
namespace private_api {
template<typename ModelData>
struct SortModelInner : private_api::ModelChangeListener
{
SortModelInner(std::shared_ptr<slint::Model<ModelData>> source_model,
std::function<bool(const ModelData &, const ModelData &)> sort_fn,
slint::SortModel<ModelData> &target_model)
: source_model(source_model), sort_fn(sort_fn), target_model(target_model)
{
}
void row_added(int first_inserted_row, int count) override
{
if (sorted_rows_dirty) {
reset();
return;
}
// Adjust the existing sorted row indices to match the updated source model
for (auto &row : sorted_rows) {
if (row >= first_inserted_row)
row += count;
}
for (int row = first_inserted_row; row < first_inserted_row + count; ++row) {
ModelData inserted_value = *source_model->row_data(row);
auto insertion_point =
std::lower_bound(sorted_rows.begin(), sorted_rows.end(), inserted_value,
[this](int sorted_row, const ModelData &inserted_value) {
auto sorted_elem = source_model->row_data(sorted_row);
return sort_fn(*sorted_elem, inserted_value);
});
insertion_point = sorted_rows.insert(insertion_point, row);
target_model.row_added(std::distance(sorted_rows.begin(), insertion_point), 1);
}
}
void row_changed(int changed_row) override
{
if (sorted_rows_dirty) {
reset();
return;
}
auto removed_row_it =
sorted_rows.erase(std::find(sorted_rows.begin(), sorted_rows.end(), changed_row));
auto removed_row = std::distance(sorted_rows.begin(), removed_row_it);
ModelData changed_value = *source_model->row_data(changed_row);
auto insertion_point =
std::lower_bound(sorted_rows.begin(), sorted_rows.end(), changed_value,
[this](int sorted_row, const ModelData &changed_value) {
auto sorted_elem = source_model->row_data(sorted_row);
return sort_fn(*sorted_elem, changed_value);
});
insertion_point = sorted_rows.insert(insertion_point, changed_row);
auto inserted_row = std::distance(sorted_rows.begin(), insertion_point);
if (inserted_row == removed_row) {
target_model.row_changed(removed_row);
} else {
target_model.row_removed(removed_row, 1);
target_model.row_added(inserted_row, 1);
}
}
void row_removed(int first_removed_row, int count) override
{
if (sorted_rows_dirty) {
reset();
return;
}
std::vector<int> removed_rows;
removed_rows.reserve(count);
for (auto it = sorted_rows.begin(); it != sorted_rows.end();) {
if (*it >= first_removed_row) {
if (*it < first_removed_row + count) {
removed_rows.push_back(std::distance(sorted_rows.begin(), it));
it = sorted_rows.erase(it);
continue;
} else {
*it -= count;
}
}
++it;
}
for (int removed_row : removed_rows) {
target_model.row_removed(removed_row, 1);
}
}
void reset() override
{
sorted_rows_dirty = true;
target_model.reset();
}
void ensure_sorted()
{
if (!sorted_rows_dirty) {
return;
}
sorted_rows.resize(source_model->row_count());
for (size_t i = 0; i < sorted_rows.size(); ++i)
sorted_rows[i] = i;
std::sort(sorted_rows.begin(), sorted_rows.end(), [this](int lhs_index, int rhs_index) {
auto lhs_elem = source_model->row_data(lhs_index);
auto rhs_elem = source_model->row_data(rhs_index);
return sort_fn(*lhs_elem, *rhs_elem);
});
sorted_rows_dirty = false;
}
std::shared_ptr<slint::Model<ModelData>> source_model;
std::function<bool(const ModelData &, const ModelData &)> sort_fn;
slint::SortModel<ModelData> &target_model;
std::vector<int> sorted_rows;
bool sorted_rows_dirty = true;
};
}
/// The SortModel acts as an adapter model for a given source model by sorting all rows
/// with by order provided by the given sorting function. The sorting function is called for
/// pairs of elements of the source model.
template<typename ModelData>
class SortModel : public Model<ModelData>
{
friend struct private_api::SortModelInner<ModelData>;
public:
/// Constructs a new SortModel that provides a sorted view on the \a source_model by applying
/// the order given by the specified \a sort_fn.
SortModel(std::shared_ptr<Model<ModelData>> source_model,
std::function<bool(const ModelData &, const ModelData &)> sort_fn)
: inner(std::make_shared<private_api::SortModelInner<ModelData>>(std::move(source_model),
std::move(sort_fn), *this))
{
inner->source_model->attach_peer(inner);
}
int row_count() const override { return inner->source_model->row_count(); }
std::optional<ModelData> row_data(int i) const override
{
inner->ensure_sorted();
return inner->source_model->row_data(inner->sorted_rows[i]);
}
/// Re-applies the model's sort function on each row of the source model. Use this if state
/// external to the sort function has changed.
void sort() { inner->reset(); }
/// Given the \a sorted_row_index, this function returns the corresponding row index in the
/// source model.
int unsorted_row(int sorted_row_index) const
{
inner->ensure_sorted();
return inner->sorted_rows[sorted_row_index];
}
/// Returns the source model of this filter model.
std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; }
private:
std::shared_ptr<private_api::SortModelInner<ModelData>> inner;
};
namespace private_api {
template<typename C, typename ModelData>

View file

@ -281,3 +281,118 @@ SCENARIO("Mapped Model")
REQUIRE(plus_one_model->row_data(2) == 3);
REQUIRE(plus_one_model->row_data(3) == 5);
}
SCENARIO("Sorted Model Insert")
{
auto vec_model = std::make_shared<slint::VectorModel<int>>(std::vector<int> { 3, 4, 1, 2 });
auto sorted_model = std::make_shared<slint::SortModel<int>>(
vec_model, [](auto lhs, auto rhs) { return lhs < rhs; });
auto observer = std::make_shared<ModelObserver>();
sorted_model->attach_peer(observer);
REQUIRE(sorted_model->row_count() == 4);
REQUIRE(sorted_model->row_data(0) == 1);
REQUIRE(sorted_model->row_data(1) == 2);
REQUIRE(sorted_model->row_data(2) == 3);
REQUIRE(sorted_model->row_data(3) == 4);
vec_model->insert(0, 10);
REQUIRE(observer->added_rows.size() == 1);
REQUIRE(observer->added_rows[0] == ModelObserver::Range { 4, 1 });
REQUIRE(observer->changed_rows.empty());
REQUIRE(observer->removed_rows.empty());
REQUIRE(!observer->model_reset);
observer->clear();
REQUIRE(sorted_model->row_count() == 5);
REQUIRE(sorted_model->row_data(0) == 1);
REQUIRE(sorted_model->row_data(1) == 2);
REQUIRE(sorted_model->row_data(2) == 3);
REQUIRE(sorted_model->row_data(3) == 4);
REQUIRE(sorted_model->row_data(4) == 10);
}
SCENARIO("Sorted Model Remove")
{
auto vec_model = std::make_shared<slint::VectorModel<int>>(std::vector<int> { 3, 4, 1, 2 });
auto sorted_model = std::make_shared<slint::SortModel<int>>(
vec_model, [](auto lhs, auto rhs) { return lhs < rhs; });
auto observer = std::make_shared<ModelObserver>();
sorted_model->attach_peer(observer);
REQUIRE(sorted_model->row_count() == 4);
REQUIRE(sorted_model->row_data(0) == 1);
REQUIRE(sorted_model->row_data(1) == 2);
REQUIRE(sorted_model->row_data(2) == 3);
REQUIRE(sorted_model->row_data(3) == 4);
/// Remove the entry with the value 4
vec_model->erase(1);
REQUIRE(observer->added_rows.empty());
REQUIRE(observer->changed_rows.empty());
REQUIRE(observer->removed_rows.size() == 1);
REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 3, 1 });
REQUIRE(!observer->model_reset);
observer->clear();
REQUIRE(sorted_model->row_count() == 3);
REQUIRE(sorted_model->row_data(0) == 1);
REQUIRE(sorted_model->row_data(1) == 2);
REQUIRE(sorted_model->row_data(2) == 3);
}
SCENARIO("Sorted Model Change")
{
auto vec_model = std::make_shared<slint::VectorModel<int>>(std::vector<int> { 3, 4, 1, 2 });
auto sorted_model = std::make_shared<slint::SortModel<int>>(
vec_model, [](auto lhs, auto rhs) { return lhs < rhs; });
auto observer = std::make_shared<ModelObserver>();
sorted_model->attach_peer(observer);
REQUIRE(sorted_model->row_count() == 4);
REQUIRE(sorted_model->row_data(0) == 1);
REQUIRE(sorted_model->row_data(1) == 2);
REQUIRE(sorted_model->row_data(2) == 3);
REQUIRE(sorted_model->row_data(3) == 4);
/// Change the entry with the value 4 to 10 -> maintain order
vec_model->set_row_data(1, 10);
REQUIRE(observer->added_rows.empty());
REQUIRE(observer->changed_rows.size() == 1);
REQUIRE(observer->changed_rows[0] == 3);
REQUIRE(observer->removed_rows.empty());
REQUIRE(!observer->model_reset);
observer->clear();
REQUIRE(sorted_model->row_count() == 4);
REQUIRE(sorted_model->row_data(0) == 1);
REQUIRE(sorted_model->row_data(1) == 2);
REQUIRE(sorted_model->row_data(2) == 3);
REQUIRE(sorted_model->row_data(3) == 10);
/// Change the entry with the value 10 to 0 -> new order with remove and insert
vec_model->set_row_data(1, 0);
REQUIRE(observer->added_rows.size() == 1);
REQUIRE(observer->added_rows[0] == ModelObserver::Range { 0, 1 });
REQUIRE(observer->changed_rows.empty());
REQUIRE(observer->removed_rows.size() == 1);
REQUIRE(observer->removed_rows[0] == ModelObserver::Range { 3, 1 });
REQUIRE(!observer->model_reset);
observer->clear();
REQUIRE(sorted_model->row_count() == 4);
REQUIRE(sorted_model->row_data(0) == 0);
REQUIRE(sorted_model->row_data(1) == 1);
REQUIRE(sorted_model->row_data(2) == 2);
REQUIRE(sorted_model->row_data(3) == 3);
}