morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
selection.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5/// @file table/selection.hpp
6/// @brief A table's selection and activation, kept by row key.
7///
8/// Selection is a set of `RowId`s plus an active key (the row with the
9/// cursor). Because it is kept by key, it survives sort, filter and refetch:
10/// a row that leaves the view stays selected unless the table prunes on
11/// filter. Activation (double-click, Enter) reports a row and does not change
12/// the selection.
13///
14/// In `server` mode a selected row may be outside the fetched pages, so
15/// "select all" and pruning are not available; the selection is the set of
16/// keys the user chose.
17///
18/// See `docs/spec/table/engine.md`.
19
20#include <cstddef>
21#include <cstdint>
22#include <expected>
23#include <functional>
26#include <optional>
27#include <unordered_set>
28#include <utility>
29#include <vector>
30
31namespace morph::table {
32
33// NOLINTBEGIN(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access) -- row and column indices are bounded by the snapshot's own counts, and the sort and filter loops index once per comparison, where at() would check every access
34
35/// @brief Where a table's rows are sorted and filtered.
36enum class TableMode : std::uint8_t {
37 Client, ///< The client holds every row and the engine sorts and filters them.
38 Server, ///< The server sorts and filters; the client holds fetched pages.
39 Hybrid, ///< A server search whose result the client engine sorts and filters.
40};
41
42/// @brief How many rows a selection may hold.
43enum class SelectionMode : std::uint8_t {
44 None, ///< Rows cannot be selected.
45 Single, ///< At most one row.
46 Multiple, ///< Any number of rows.
47};
48
49/// @brief A selection's configuration.
51 /// @brief How many rows may be selected.
53 /// @brief Drop selected rows that leave the view.
54 bool pruneOnFilter = false;
55 /// @brief The table's mode; `Server` rules out select-all and pruning.
57};
58
59/// @brief A table's selected keys and active key.
60class Selection {
61public:
62 /// @brief Builds a selection.
63 /// @param options The configuration.
64 /// @return The selection, or `Unavailable` when @p options asks to prune
65 /// in `server` mode.
66 [[nodiscard]] static std::expected<Selection, TableError> create(SelectionOptions options) {
67 if (options.pruneOnFilter && options.tableMode == TableMode::Server) {
68 return std::unexpected(TableError{.code = TableErrorCode::Unavailable,
69 .column = {},
70 .message = "pruneOnFilter is not available in server mode"});
71 }
72 return Selection{options};
73 }
74
75 /// @brief Selects a row; in `Single` mode it replaces the selection.
76 /// @param key The row's key, by value: it may come from `keys()`, which
77 /// this call changes.
78 /// @return `false` in `None` mode.
79 bool select(RowId key) {
80 if (_options.mode == SelectionMode::None) {
81 return false;
82 }
83 if (_options.mode == SelectionMode::Single) {
84 _keys.clear();
85 _order.clear();
86 }
87 if (_keys.insert(key).second) {
88 _order.push_back(std::move(key));
89 }
90 return true;
91 }
92
93 /// @brief Deselects a row.
94 /// @param key The row's key, by value: it may come from `keys()`, which
95 /// this call changes.
96 /// @return `true` when it was selected.
97 // NOLINTNEXTLINE(performance-unnecessary-value-param) -- a reference into `_order` would change under the erase
98 bool deselect(RowId key) {
99 if (_keys.erase(key) == 0) {
100 return false;
101 }
102 std::erase(_order, key);
103 return true;
104 }
105
106 /// @brief Selects an unselected row, or deselects a selected one.
107 /// @param key The row's key, by value: it may come from `keys()`, which
108 /// this call changes.
109 /// @return Whether the row is selected afterwards.
110 bool toggle(RowId key) {
111 if (deselect(key)) {
112 return false;
113 }
114 return select(std::move(key));
115 }
116
117 /// @brief Deselects every row.
118 void clear() {
119 _keys.clear();
120 _order.clear();
121 }
122
123 /// @brief Selects every row in the engine's view.
124 /// @param engine The table's engine.
125 /// @return `Unavailable` in `server` mode or unless the mode is `Multiple`.
126 std::expected<void, TableError> selectAll(Engine const& engine) {
127 if (_options.tableMode == TableMode::Server || _options.mode != SelectionMode::Multiple) {
128 return std::unexpected(TableError{.code = TableErrorCode::Unavailable,
129 .column = {},
130 .message = "select all needs multiple selection over client rows"});
131 }
132 for (std::size_t row = 0; row < engine.viewRowCount(); ++row) {
133 select(engine.rowIdAt(row));
134 }
135 return {};
136 }
137
138 /// @brief Brings the selection up to date after the view changed: with
139 /// `pruneOnFilter`, drops selected rows no longer in the view.
140 /// @param engine The table's engine.
141 void viewChanged(Engine const& engine) {
142 if (!_options.pruneOnFilter) {
143 return;
144 }
145 std::erase_if(_order, [&](RowId const& key) {
146 if (engine.viewRowOfKey(key)) {
147 return false;
148 }
149 _keys.erase(key);
150 return true;
151 });
152 }
153
154 /// @brief Moves the cursor.
155 /// @param key The row's key, or nothing for no cursor.
156 void setActiveKey(std::optional<RowId> key) { _active = std::move(key); }
157
158 /// @brief The row with the cursor.
159 /// @return Its key, or nothing.
160 [[nodiscard]] std::optional<RowId> const& activeKey() const noexcept { return _active; }
161
162 /// @brief Whether a row is selected.
163 /// @param key The row's key.
164 /// @return `true` when selected.
165 [[nodiscard]] bool contains(RowId const& key) const { return _keys.contains(key); }
166
167 /// @brief Selected rows.
168 /// @return The count.
169 [[nodiscard]] std::size_t selectedCount() const noexcept { return _keys.size(); }
170
171 /// @brief The selected keys, in the order they were selected.
172 /// @return The keys.
173 [[nodiscard]] std::vector<RowId> const& keys() const noexcept { return _order; }
174
175 /// @brief The configuration.
176 /// @return The options.
177 [[nodiscard]] SelectionOptions const& options() const noexcept { return _options; }
178
179 /// @brief Activates the row at a view row: reports it and changes no selection.
180 /// @param engine The table's engine.
181 /// @param viewRow The view row.
182 /// @param onActivate Receives the row's key.
183 /// @return `false` when @p viewRow is past the view.
184 static bool activate(Engine const& engine, std::size_t viewRow,
185 std::function<void(RowId const&)> const& onActivate) {
186 if (viewRow >= engine.viewRowCount()) {
187 return false;
188 }
189 if (onActivate) {
190 onActivate(engine.rowIdAt(viewRow));
191 }
192 return true;
193 }
194
195private:
196 explicit Selection(SelectionOptions options) : _options{options} {}
197
198 SelectionOptions _options;
199 std::unordered_set<RowId> _keys;
200 std::vector<RowId> _order;
201 std::optional<RowId> _active;
202};
203
204/// @brief The counts a status line shows.
206 /// @brief Rows in the view.
207 std::size_t viewCount = 0;
208 /// @brief Rows in the source.
209 std::size_t sourceCount = 0;
210 /// @brief Selected rows.
211 std::size_t selectedCount = 0;
212};
213
214/// @brief A table's counts.
215/// @param engine The table's engine.
216/// @param selection The table's selection.
217/// @return View, source and selected counts.
218[[nodiscard]] inline TableCounts counts(Engine const& engine, Selection const& selection) {
219 return TableCounts{.viewCount = engine.viewRowCount(),
220 .sourceCount = engine.sourceRowCount(),
221 .selectedCount = selection.selectedCount()};
222}
223
224// NOLINTEND(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access)
225
226} // namespace morph::table
Sorts, filters and maps the view of one table.
Definition engine.hpp:661
std::size_t sourceRowCount() const
Rows in the source the view was computed from.
Definition engine.hpp:747
std::optional< std::size_t > viewRowOfKey(RowId const &key) const
The view row of the row keyed id.
Definition engine.hpp:778
RowId rowIdAt(std::size_t viewRow) const
The key of the row at a view row.
Definition engine.hpp:773
std::size_t viewRowCount() const noexcept
Rows in the view.
Definition engine.hpp:743
A table's selected keys and active key.
Definition selection.hpp:60
void viewChanged(Engine const &engine)
Brings the selection up to date after the view changed: with pruneOnFilter, drops selected rows no lo...
bool contains(RowId const &key) const
Whether a row is selected.
void clear()
Deselects every row.
void setActiveKey(std::optional< RowId > key)
Moves the cursor.
static bool activate(Engine const &engine, std::size_t viewRow, std::function< void(RowId const &)> const &onActivate)
Activates the row at a view row: reports it and changes no selection.
bool deselect(RowId key)
Deselects a row.
Definition selection.hpp:98
std::size_t selectedCount() const noexcept
Selected rows.
std::optional< RowId > const & activeKey() const noexcept
The row with the cursor.
SelectionOptions const & options() const noexcept
The configuration.
std::vector< RowId > const & keys() const noexcept
The selected keys, in the order they were selected.
bool toggle(RowId key)
Selects an unselected row, or deselects a selected one.
bool select(RowId key)
Selects a row; in Single mode it replaces the selection.
Definition selection.hpp:79
static std::expected< Selection, TableError > create(SelectionOptions options)
Builds a selection.
Definition selection.hpp:66
std::expected< void, TableError > selectAll(Engine const &engine)
Selects every row in the engine's view.
The rows a table engine reads, and the services it is given.
@ Unavailable
An operation the table's mode does not offer.
std::variant< std::int64_t, std::string > RowId
A row's identity: the table's key, stable across reloads.
The table engine: sort, filter and view mapping, on and off the owner.
SelectionMode
How many rows a selection may hold.
Definition selection.hpp:43
@ Single
At most one row.
Definition selection.hpp:45
@ None
Rows cannot be selected.
Definition selection.hpp:44
@ Multiple
Any number of rows.
Definition selection.hpp:46
TableCounts counts(Engine const &engine, Selection const &selection)
A table's counts.
TableMode
Where a table's rows are sorted and filtered.
Definition selection.hpp:36
@ Client
The client holds every row and the engine sorts and filters them.
Definition selection.hpp:37
@ Server
The server sorts and filters; the client holds fetched pages.
Definition selection.hpp:38
@ Hybrid
A server search whose result the client engine sorts and filters.
Definition selection.hpp:39
A selection's configuration.
Definition selection.hpp:50
TableMode tableMode
The table's mode; Server rules out select-all and pruning.
Definition selection.hpp:56
bool pruneOnFilter
Drop selected rows that leave the view.
Definition selection.hpp:54
SelectionMode mode
How many rows may be selected.
Definition selection.hpp:52
The counts a status line shows.
std::size_t selectedCount
Selected rows.
std::size_t viewCount
Rows in the view.
std::size_t sourceCount
Rows in the source.
The error every fallible table call returns.