mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
Add a C++ Sort Model class (#1688)
This allows automatically sorting a model.
This commit is contained in:
parent
cfbdce735f
commit
6df60fa723
2 changed files with 293 additions and 2 deletions
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue