neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
item_selection_model.hpp
Go to the documentation of this file.
1// basic_item_selection_model.hpp
2/*
3 neogfx C++ App/Game Engine
4 Copyright (c) 2015, 2020 Leigh Johnston. All Rights Reserved.
5
6 This program is free software: you can redistribute it and / or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#pragma once
21
22#include <neogfx/neogfx.hpp>
24#include <neolib/core/map.hpp>
28
29namespace neogfx
30{
31 template <typename Alloc = std::allocator<std::pair<const item_presentation_model_index, selection_area>>>
32 class basic_item_selection_model : public object<reference_counted<i_item_selection_model>>
33 {
34 public:
35 define_declared_event(CurrentIndexChanged, current_index_changed, const optional_item_presentation_model_index& /* aCurrentIndex */, const optional_item_presentation_model_index& /* aPreviousIndex */)
37 define_declared_event(PresentationModelAdded, presentation_model_added, i_item_presentation_model&)
38 define_declared_event(PresentationModelChanged, presentation_model_changed, i_item_presentation_model&, i_item_presentation_model&)
39 define_declared_event(PresentationModelRemoved, presentation_model_removed, i_item_presentation_model&)
41 public:
42 typedef Alloc allocator_type;
43 private:
44 using concrete_item_selection = neolib::map<item_presentation_model_index, selection_area, std::less<item_presentation_model_index>, allocator_type>;
45 typedef std::deque<std::pair<item_presentation_model_index, item_selection_operation>> operation_queue_t;
46 public:
48 iModel{ nullptr },
49 iMode{ aMode },
50 iSorting{ false },
51 iFiltering{ false },
52 iNotifying{ false },
53 iInQueue{ false }
54 {
55 set_alive();
56 }
58 iModel{ nullptr },
59 iMode{ aMode },
60 iSorting{ false },
61 iFiltering{ false },
62 iNotifying{ false },
63 iInQueue{ false }
64 {
66 set_alive();
67 }
69 {
70 iSink.clear();
72 }
73 public:
74 bool has_presentation_model() const override
75 {
76 return iModel != nullptr;
77 }
78 i_item_presentation_model& presentation_model() const override
79 {
80 if (iModel == nullptr)
81 throw no_presentation_model();
82 return *iModel;
83 }
84 void set_presentation_model(i_item_presentation_model& aModel) override
85 {
86 if (iModel == &aModel)
87 return;
88
89 iSink.clear();
90
92 i_item_presentation_model* oldModel = iModel;
93
94 iModel = &aModel;
95
96 iSink += presentation_model().item_model_changed([this](const i_item_model&)
97 {
98 iCurrentIndex = std::nullopt;
99 reindex();
100 });
101 iSink += presentation_model().item_added([this](item_presentation_model_index const& aIndex)
102 {
103 if (has_current_index())
104 {
105 if (current_index().row() >= aIndex.row())
106 iCurrentIndex->set_row(current_index().row() + 1u);
107 }
108 reindex();
109 });
110 iSink += presentation_model().item_removing([this](item_presentation_model_index const& aIndex)
111 {
112 if (has_current_index())
113 {
114 if (presentation_model().rows() <= 1u)
115 iCurrentIndex = std::nullopt;
116 else if (current_index().row() > aIndex.row())
117 iCurrentIndex->set_row(current_index().row() - 1u);
118 else if (current_index().row() == aIndex.row() && aIndex.row() == presentation_model().rows() - 1u)
119 iCurrentIndex->set_row(aIndex.row() - 1u);
120 }
121 reindex();
122 });
123 iSink += presentation_model().item_expanded([this](item_presentation_model_index const& aIndex)
124 {
125 if (has_current_index() && current_index().row() > aIndex.row())
126 iCurrentIndex = std::nullopt;
127 reindex();
128 });
129 iSink += presentation_model().item_collapsed([this](item_presentation_model_index const& aIndex)
130 {
131 if (has_current_index() && current_index().row() > aIndex.row())
132 iCurrentIndex = std::nullopt;
133 reindex();
134 });
135 iSink += presentation_model().items_sorting([this]()
136 {
137 neolib::scoped_flag sf{ iSorting };
138 iSavedModelIndex = has_current_index() ? presentation_model().to_item_model_index(current_index()) : optional_item_model_index{};
140 });
141 iSink += presentation_model().items_sorted([this]()
142 {
143 neolib::scoped_flag sf{ iSorting };
144 if (iSavedModelIndex != std::nullopt)
145 set_current_index(presentation_model().from_item_model_index(*iSavedModelIndex));
146 iSavedModelIndex = std::nullopt;
147 reindex();
148 });
149 iSink += presentation_model().items_filtering([this]()
150 {
151 neolib::scoped_flag sf{ iFiltering };
152 iSavedModelIndex = has_current_index() ? presentation_model().to_item_model_index(current_index()) : optional_item_model_index{};
154 });
155 iSink += presentation_model().items_filtered([this]()
156 {
157 neolib::scoped_flag sf{ iFiltering };
158 if (iSavedModelIndex != std::nullopt && presentation_model().has_item_model_index(*iSavedModelIndex))
159 set_current_index(presentation_model().from_item_model_index(*iSavedModelIndex));
160 else if (presentation_model().rows() >= 1)
161 set_current_index(item_presentation_model_index{ 0u, 0u });
162 iSavedModelIndex = std::nullopt;
163 reindex();
164 });
165 iSink += neolib::destroying(presentation_model(), [this]()
166 {
167 auto oldModel = iModel;
168 iModel = nullptr;
169 iCurrentIndex = std::nullopt;
170 iSavedModelIndex = std::nullopt;
171 iSelection = {};
172 PresentationModelRemoved.trigger(*oldModel);
173 });
174
175 if (oldModel == nullptr)
176 PresentationModelAdded.trigger(presentation_model());
177 else
178 PresentationModelChanged.trigger(presentation_model(), *oldModel);
179 }
180 public:
181 item_selection_mode mode() const override
182 {
183 return iMode;
184 }
185 void set_mode(item_selection_mode aMode) override
186 {
187 if (iMode == aMode)
188 return;
189 iMode = aMode;
190 ModeChanged.trigger(mode());
191 clear(item_presentation_model_index{});
192 }
193 public:
194 bool has_current_index() const override
195 {
196 return iCurrentIndex != std::nullopt;
197 }
198 item_presentation_model_index const& current_index() const override
199 {
200 if (iCurrentIndex == std::nullopt)
201 throw no_current_index();
202 return *iCurrentIndex;
203 }
204 void set_current_index(item_presentation_model_index const& aIndex) override
205 {
206 do_set_current_index(aIndex);
207 }
208 void clear_current_index() override
209 {
210 do_set_current_index(optional_item_presentation_model_index{});
211 }
212 item_presentation_model_index relative_to_current_index(index_location aRelativeLocation, bool aSelectable = true, bool aEditable = false) const override
213 {
214 return relative_to_index(has_current_index() ? current_index() : item_presentation_model_index{ 0, 0 }, aRelativeLocation, aSelectable, aEditable);
215 }
216 item_presentation_model_index relative_to_index(item_presentation_model_index const& aIndex, index_location aRelativeLocation, bool aSelectable = true, bool aEditable = false) const override
217 {
218 item_presentation_model_index result = aIndex;
219 auto acceptable = [this, aSelectable, aEditable](item_presentation_model_index aIndex)
220 {
221 return (!aSelectable || is_selectable(aIndex)) && (!aEditable || is_editable(aIndex));
222 };
223 switch (aRelativeLocation)
224 {
227 result = item_presentation_model_index{ 0u, 0u };
228 while (!acceptable(result) && (result = next_cell(result)) != item_presentation_model_index{ 0u, 0u })
229 ;
230 break;
232 result = item_presentation_model_index{ presentation_model().rows() - 1u , presentation_model().columns() - 1u };
233 while (!acceptable(result) && (result = previous_cell(result)) != item_presentation_model_index{ presentation_model().rows() - 1u , presentation_model().columns() - 1u })
234 ;
235 break;
237 result = previous_cell();
238 while (!acceptable(result) && (result = previous_cell(result)) != previous_cell())
239 ;
240 break;
242 result = next_cell();
243 while (!acceptable(result) && (result = next_cell(result)) != next_cell())
244 ;
245 break;
247 result.set_column(0u);
248 while (!acceptable(result) && (result += item_presentation_model_index{ 0u, 1u }).column() != presentation_model().columns() - 1u)
249 ;
250 break;
252 result.set_column(presentation_model().columns() - 1u);
253 while (!acceptable(result) && (result -= item_presentation_model_index{ 0u, 1u }).column() != 0u)
254 ;
255 break;
257 result.set_row(0);
258 while (!acceptable(result) && (result += item_presentation_model_index{ 1u, 0u }).column() != presentation_model().rows() - 1u)
259 ;
260 break;
262 result.set_row(presentation_model().rows() - 1u);
263 while (!acceptable(result) && (result -= item_presentation_model_index{ 1, 0 }).row() != 0u)
264 ;
265 break;
267 if (result.column() > 0u)
268 {
269 result -= item_presentation_model_index{ 0u, 1u };
270 while (!acceptable(result) && result.column() > 0u)
271 result -= item_presentation_model_index{ 0u, 1u };
272 }
273 break;
275 if (result.column() < presentation_model().columns() - 1u)
276 {
277 result += item_presentation_model_index{ 0u, 1u };
278 while (!acceptable(result) && result.column() < presentation_model().columns() - 1u)
279 result += item_presentation_model_index{ 0u, 1u };
280 }
281 break;
283 if (result.row() > 0u)
284 {
285 result -= item_presentation_model_index{ 1u, 0u };
286 while (!acceptable(result) && result.row() > 0u)
287 result -= item_presentation_model_index{ 1u, 0u };
288 }
289 break;
291 if (result.row() < presentation_model().rows() - 1u)
292 {
293 result += item_presentation_model_index{ 1u, 0u };
294 while (!acceptable(result) && result.row() < presentation_model().rows() - 1u)
295 result += item_presentation_model_index{ 1u, 0u };
296 }
297 break;
298 }
299 return acceptable(result) ? result : aIndex;
300 }
301 item_presentation_model_index next_cell() const override
302 {
303 if (!has_current_index())
304 return item_presentation_model_index{ 0u, 0u };
305 else
306 return next_cell(current_index());
307 }
308 item_presentation_model_index next_cell(item_presentation_model_index const& aIndex) const override
309 {
310 if (aIndex.column() + 1u < presentation_model().columns())
311 return item_presentation_model_index{ aIndex.row(), aIndex.column() + 1u };
312 else if (aIndex.row() + 1u < presentation_model().rows())
313 return item_presentation_model_index{ aIndex.row() + 1u, 0u };
314 else
315 return item_presentation_model_index{ 0u, 0u };
316 }
317 item_presentation_model_index previous_cell() const override
318 {
319 if (!has_current_index())
320 return item_presentation_model_index{ 0u, 0u };
321 else
323 }
324 item_presentation_model_index previous_cell(item_presentation_model_index const& aIndex) const override
325 {
326 if (aIndex.column() > 0u)
327 return item_presentation_model_index{ aIndex.row(), aIndex.column() - 1u };
328 else if (aIndex.row() > 0u)
329 return item_presentation_model_index{ aIndex.row() - 1u, presentation_model().columns() - 1u };
330 else
331 return item_presentation_model_index{ presentation_model().rows() - 1u , presentation_model().columns() - 1u };
332 }
333 public:
334 const item_selection& selection() const override
335 {
336 return iSelection;
337 }
338 bool is_selected(item_presentation_model_index const& aIndex) const override
339 {
341 }
342 bool is_selectable(item_presentation_model_index const& aIndex) const override
343 {
345 }
346 void clear_selection() override
347 {
348 for (item_presentation_model_index::row_type row = 0; row < presentation_model().rows(); ++row)
349 for (item_presentation_model_index::column_type column = 0; column < presentation_model().columns(); ++column)
350 presentation_model().cell_meta(item_presentation_model_index{ row, column }).selection &= ~item_cell_selection_flags::Selected;
351 iPreviousSelection = iSelection;
352 iSelection.clear();
353 SelectionChanged.trigger(iSelection, iPreviousSelection);
354 iPreviousSelection.clear();
355 }
356 void select(item_presentation_model_index const& aIndex, item_selection_operation aOperation) override
357 {
358 if (aOperation == item_selection_operation::None)
359 return;
361 !is_selectable(aIndex))
362 return;
363 if (iNotifying && (aOperation & item_selection_operation::Queued) != item_selection_operation::Queued)
366 {
367 iOperationQueue.push_back(std::make_pair(aIndex, aOperation));
368 return;
369 }
371 {
373 set_current_index(aIndex);
374 else
376 }
379 // todo: cell and column
380 static auto find = [](item_presentation_model_index const& aIndex, concrete_item_selection& aSelection)
381 {
382 if (aSelection.empty())
383 return aSelection.end();
384 auto iterExisting = aSelection.lower_bound(aIndex.with_column(0u));
385 if (iterExisting == aSelection.end())
386 iterExisting = std::prev(iterExisting);
387 if (iterExisting != aSelection.begin())
388 {
389 auto previous = std::prev(iterExisting);
390 if (previous->second().topLeft.row() <= aIndex.row() &&
391 previous->second().bottomRight.row() >= aIndex.row())
392 return previous;
393 }
394 if (iterExisting->second().topLeft.row() <= aIndex.row() &&
395 iterExisting->second().bottomRight.row() >= aIndex.row())
396 return iterExisting;
397 return aSelection.end();
398 };
399 static auto find_adjacent = [](item_presentation_model_index const& aIndex, concrete_item_selection& aSelection)
400 {
401 if (aSelection.empty())
402 return aSelection.end();
403 auto iterExisting = find(aIndex.with_row(aIndex.row() - 1u), aSelection);
404 if (iterExisting != aSelection.end())
405 return iterExisting;
406 return find(aIndex.with_row(aIndex.row() + 1u), aSelection);
407 };
408 auto update = [&, this](concrete_item_selection& aSelection, bool aUpdateCells)
409 {
410 bool const clear = (aOperation & item_selection_operation::Clear) == item_selection_operation::Clear;
411 bool const rowCurrentlySelected = aIndex.row() < presentation_model().rows() && (presentation_model().cell_meta(aIndex.with_column(0u)).selection & item_cell_selection_flags::Selected) ==
414 ((aOperation & item_selection_operation::Toggle) == item_selection_operation::Toggle && !rowCurrentlySelected);
415 bool const deselect = (aOperation & item_selection_operation::Deselect) == item_selection_operation::Deselect ||
416 ((aOperation & item_selection_operation::Toggle) == item_selection_operation::Toggle && rowCurrentlySelected);
417 if (clear)
418 {
419 if (aUpdateCells)
420 {
421 struct selection_clearer : i_item_presentation_model::i_meta_visitor
422 {
423 void visit(i_item_presentation_model::cell_meta_type& aMeta) override
424 {
425 aMeta.selection &= ~item_cell_selection_flags::Selected;
426 }
427 } selectionClearer;
428 presentation_model().accept(selectionClearer, true);
429 }
430 aSelection.clear();
431 }
432 if (select)
433 {
434 if (find(aIndex, aSelection) == aSelection.end())
435 {
436 auto iterAdjacent = find_adjacent(aIndex, aSelection);
437 if (iterAdjacent == aSelection.end())
438 aSelection.emplace(aIndex.with_column(0u), selection_area{ aIndex.with_column(0u), aIndex.with_column(presentation_model().columns() - 1u) });
439 else
440 {
441 auto& adjacent = *iterAdjacent;
442 if (adjacent.second().bottomRight.row() == aIndex.row() - 1u)
443 adjacent.second().bottomRight = aIndex.with_column(presentation_model().columns() - 1u);
444 else
445 {
446 aSelection.emplace(aIndex.with_column(0u), selection_area{ aIndex.with_column(0u), adjacent.second().bottomRight });
447 aSelection.erase(iterAdjacent);
448 }
449 }
450 }
451 if (aUpdateCells)
452 for (auto& cellIndex : *this)
453 if (cellIndex.row() == aIndex.row() && is_selectable(cellIndex))
454 presentation_model().cell_meta(cellIndex).selection |= item_cell_selection_flags::Selected;
455 }
456 else if (deselect)
457 {
458 if (aUpdateCells)
459 for (auto& cellIndex : *this)
460 if (cellIndex.row() == aIndex.row())
461 presentation_model().cell_meta(cellIndex).selection &= ~item_cell_selection_flags::Selected;
462 auto iterExisting = find(aIndex, aSelection);
463 if (iterExisting != aSelection.end())
464 {
465 auto& existing = *iterExisting;
466 if (existing.second().topLeft.row() == existing.second().bottomRight.row())
467 aSelection.erase(iterExisting);
468 else if (existing.second().topLeft.row() == aIndex.row())
469 {
470 aSelection.emplace(aIndex.with_row(aIndex.row() + 1u).with_column(0u), selection_area{ aIndex.with_row(aIndex.row() + 1u).with_column(0u), existing.second().bottomRight });
471 aSelection.erase(iterExisting);
472 }
473 else if (existing.second().bottomRight.row() == aIndex.row())
474 existing.second().bottomRight.set_row(aIndex.row() - 1u);
475 else
476 {
477 aSelection.emplace(aIndex.with_row(aIndex.row() + 1u).with_column(0u), selection_area{ aIndex.with_row(aIndex.row() + 1u).with_column(0u), existing.second().bottomRight });
478 existing.second().bottomRight.set_row(aIndex.row() - 1u);
479 }
480 }
481 }
482 };
483 update(iSelection, true);
485 {
486 neolib::scoped_flag sf{ iNotifying };
487 SelectionChanged.trigger(iSelection, iPreviousSelection);
488 }
489 update(iPreviousSelection, false);
491 process_queue();
492 }
493 void select(item_model_index const& aIndex, item_selection_operation aOperation) override
494 {
495 presentation_model().expand_to(aIndex);
496 select(presentation_model().from_item_model_index(aIndex), aOperation);
497 }
498 public:
499 bool sorting() const override
500 {
501 return iSorting;
502 }
503 bool filtering() const override
504 {
505 return iFiltering;
506 }
507 public:
508 bool is_editable(item_presentation_model_index const& aIndex) const override
509 {
510 return is_selectable(aIndex) && has_presentation_model() && presentation_model().cell_editable(aIndex);
511 }
512 private:
513 void do_set_current_index(const optional_item_presentation_model_index& aNewIndex)
514 {
515 if (iCurrentIndex != aNewIndex)
516 {
517 if (aNewIndex != std::nullopt && !is_selectable(*aNewIndex))
518 return;
519 optional_item_presentation_model_index previousIndex = iCurrentIndex;
520 iCurrentIndex = aNewIndex;
521 if (previousIndex != std::nullopt)
522 presentation_model().cell_meta(*previousIndex).selection =
523 presentation_model().cell_meta(*previousIndex).selection & ~item_cell_selection_flags::Current;
524 if (iCurrentIndex != std::nullopt)
525 presentation_model().cell_meta(*iCurrentIndex).selection =
526 presentation_model().cell_meta(*iCurrentIndex).selection | item_cell_selection_flags::Current;
527 CurrentIndexChanged.trigger(iCurrentIndex, previousIndex);
528 }
529 }
530 void reindex()
531 {
532 iPreviousSelection.clear();
533 iSelection.clear();
534 for (item_presentation_model_index::row_type row = 0; row < presentation_model().rows(); ++row)
535 for (item_presentation_model_index::column_type column = 0; column < presentation_model().columns(); ++column)
536 if ((presentation_model().cell_meta(item_presentation_model_index{ row, column }).selection & item_cell_selection_flags::Selected) == item_cell_selection_flags::Selected)
537 select(item_presentation_model_index{ row, column }, item_selection_operation::Select | item_selection_operation::Internal);
538 }
539 void process_queue()
540 {
541 if (iInQueue)
542 return;
543 neolib::scoped_flag sf{ iInQueue };
544 while (!iOperationQueue.empty())
545 {
546 auto next = iOperationQueue.front();
547 iOperationQueue.pop_front();
549 }
550 }
551 private:
552 i_item_presentation_model* iModel;
554 optional_item_presentation_model_index iCurrentIndex;
555 optional_item_model_index iSavedModelIndex;
556 concrete_item_selection iPreviousSelection;
557 concrete_item_selection iSelection;
558 bool iSorting;
559 bool iFiltering;
560 bool iNotifying;
561 bool iInQueue;
562 operation_queue_t iOperationQueue;
563 sink iSink;
564 };
565
567}
void set_current_index(item_presentation_model_index const &aIndex) override
void set_presentation_model(i_item_presentation_model &aModel) override
void set_mode(item_selection_mode aMode) override
const item_selection const item_selection i_item_presentation_model i_item_presentation_model mode_changed
item_presentation_model_index next_cell(item_presentation_model_index const &aIndex) const override
item_presentation_model_index next_cell() const override
void select(item_presentation_model_index const &aIndex, item_selection_operation aOperation) override
const item_selection & selection() const override
item_presentation_model_index const & current_index() const override
define_declared_event(CurrentIndexChanged, current_index_changed, const optional_item_presentation_model_index &, const optional_item_presentation_model_index &) define_declared_event(SelectionChanged
i_item_presentation_model & presentation_model() const override
void select(item_model_index const &aIndex, item_selection_operation aOperation) override
basic_item_selection_model(i_item_presentation_model &aModel, item_selection_mode aMode=item_selection_mode::SingleSelection)
item_presentation_model_index previous_cell(item_presentation_model_index const &aIndex) const override
item_presentation_model_index relative_to_current_index(index_location aRelativeLocation, bool aSelectable=true, bool aEditable=false) const override
item_presentation_model_index relative_to_index(item_presentation_model_index const &aIndex, index_location aRelativeLocation, bool aSelectable=true, bool aEditable=false) const override
bool is_editable(item_presentation_model_index const &aIndex) const override
bool is_selected(item_presentation_model_index const &aIndex) const override
item_presentation_model_index previous_cell() const override
const item_selection const item_selection presentation_model_changed
bool is_selectable(item_presentation_model_index const &aIndex) const override
item_selection_mode mode() const override
void set_alive() override
Definition lifetime.hpp:157
value_type & emplace(Key2 &&aKey, Args &&... aArgs)
Definition map.hpp:180
void clear() final
Definition map.hpp:132
void set_destroying() override
Definition object.hpp:66
void clear()
Definition i_event.hpp:290
basic_item_selection_model<> item_selection_model
std::optional< item_model_index > optional_item_model_index
auto destroying(Object &aObject, const Handler aHandler)
Definition i_object.hpp:64
Definition plf_hive.h:79
it_type next(it_type it, const typename iterator_traits< it_type >::difference_type distance=1)
Definition plf_hive.h:89
it_type prev(it_type it, const typename iterator_traits< it_type >::difference_type distance=1)
Definition plf_hive.h:98
#define define_declared_event(name, declName,...)
Definition event.hpp:195