neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
item_presentation_model.hpp
Go to the documentation of this file.
1// item_presentation_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>
23#include <vector>
24#include <deque>
25#include <boost/algorithm/string.hpp>
31#include <neogfx/app/i_app.hpp>
38
39namespace neogfx
40{
41 template <typename ItemModel>
42 class basic_item_presentation_model : public object<reference_counted<i_item_presentation_model>>
43 {
46 public:
47 define_declared_event(VisualAppearanceChanged, visual_appearance_changed)
48 define_declared_event(ColumnInfoChanged, column_info_changed, item_presentation_model_index::column_type)
49 define_declared_event(ItemModelChanged, item_model_changed, const i_item_model&)
50 define_declared_event(ItemAdded, item_added, item_presentation_model_index const&)
51 define_declared_event(ItemChanged, item_changed, item_presentation_model_index const&)
52 define_declared_event(ItemRemoving, item_removing, item_presentation_model_index const&)
53 define_declared_event(ItemRemoved, item_removed, item_presentation_model_index const&)
54 define_declared_event(ItemExpanding, item_expanding, item_presentation_model_index const&)
55 define_declared_event(ItemCollapsing, item_collapsing, item_presentation_model_index const&)
56 define_declared_event(ItemExpanded, item_expanded, item_presentation_model_index const&)
57 define_declared_event(ItemCollapsed, item_collapsed, item_presentation_model_index const&)
58 define_declared_event(ItemToggled, item_toggled, item_presentation_model_index const&)
59 define_declared_event(ItemChecked, item_checked, item_presentation_model_index const&)
60 define_declared_event(ItemUnchecked, item_unchecked, item_presentation_model_index const&)
61 define_declared_event(ItemIndeterminate, item_indeterminate, item_presentation_model_index const&)
62 define_declared_event(ItemsUpdating, items_updating)
63 define_declared_event(ItemsUpdated, items_updated)
64 define_declared_event(ItemsSorting, items_sorting)
65 define_declared_event(ItemsSorted, items_sorted)
66 define_declared_event(ItemsFiltering, items_filtering)
67 define_declared_event(ItemsFiltered, items_filtered)
69 define_declared_event(DraggingItemRenderInfo, dragging_item_render_info, i_drag_drop_item const&, bool&, size&)
71 define_declared_event(DraggingItemCancelled, dragging_item_cancelled, i_drag_drop_item const&)
73 public:
74 using typename base_type::sort_direction;
75 using typename base_type::optional_sort_direction;
76 using typename base_type::sort_by_param;
77 using typename base_type::optional_sort_by_param;
78 using typename base_type::filter_search_key;
79 using typename base_type::filter_search_type;
80 using typename base_type::case_sensitivity;
81 private:
82 typedef ItemModel item_model_type;
83 typedef typename item_model_type::container_traits::template rebind<item_model_index::row_type, cell_meta_type, true>::other container_traits;
84 typedef typename container_traits::row_cell_array row_cell_array;
85 typedef typename container_traits::container_type container_type;
86 typedef typename container_traits::const_skip_iterator const_iterator;
87 typedef typename container_traits::skip_iterator iterator;
88 typedef typename container_traits::const_sibling_iterator const_sibling_iterator;
89 typedef typename container_traits::sibling_iterator sibling_iterator;
90 typedef typename container_traits::allocator_type allocator_type;
91 typedef typename container_type::value_type row_type;
92 typedef std::optional<i_scrollbar::value_type> optional_position;
93 private:
94 typedef std::vector<item_presentation_model_index::optional_row_type, typename std::allocator_traits<allocator_type>:: template rebind_alloc<item_presentation_model_index::optional_row_type>> row_map_type;
95 typedef std::vector<item_presentation_model_index::optional_column_type, typename std::allocator_traits<allocator_type>:: template rebind_alloc<item_presentation_model_index::optional_column_type>> column_map_type;
96 struct column_info
97 {
98 column_info(const item_model_index::optional_column_type modelColumn = {}) : modelColumn{ modelColumn } {}
99 ~column_info() {}
100 mutable item_model_index::optional_column_type modelColumn;
102 mutable std::map<dimension, uint32_t> cellWidths;
103 mutable std::optional<std::string> headingText;
104 mutable font headingFont;
105 mutable optional_size headingExtents;
106 optional_size imageSize;
107
108 void add_cell_width(dimension aWidth) const
109 {
110 ++cellWidths[aWidth];
111 }
112 void remove_cell_width(dimension aWidth) const
113 {
114 auto existing = cellWidths.find(aWidth);
115 if (existing != cellWidths.end())
116 {
117 if (--existing->second == 0)
118 cellWidths.erase(existing);
119 }
120 }
121 };
122 typedef typename container_traits::template rebind<item_presentation_model_index::row_type, column_info>::other::row_cell_array column_info_array;
123 public:
124 using typename base_type::no_item_model;
125 using typename base_type::bad_index;
126 using typename base_type::no_mapped_row;
127 public:
128 basic_item_presentation_model(bool aSortable = false) : iItemModel{ nullptr }, iSortable{ aSortable }, iAlternatingRowColor{ false }
129 {
130 init();
131 }
132 basic_item_presentation_model(i_item_model& aItemModel, bool aSortable = false) : iItemModel{ nullptr }, iSortable{ aSortable }, iAlternatingRowColor{ false }
133 {
134 init();
135 set_item_model(aItemModel);
136 }
138 {
140 iSink.clear();
141 iItemModelSink.clear();
142 }
143 public:
144 bool metrics_available() const
145 {
146 return attached() && attachment().has_root();
147 }
148 bool attached() const final
149 {
150 return iAttachment != nullptr;
151 }
152 i_widget& attachment() const final
153 {
154 if (iAttachment == nullptr)
155 throw not_attached();
156 return *iAttachment;
157 }
158 void attach(i_ref_ptr<i_widget> const& aWidget) final
159 {
160 if (iAttachment && aWidget != nullptr)
161 throw already_attached();
162 iAttachment = aWidget;
163 }
164 void detach() final
165 {
166 if (iAttachment == nullptr)
167 throw not_attached();
168 iAttachment = nullptr;
169 }
170 public:
171 bool updating() const final
172 {
173 return iUpdating != 0u;
174 }
175 void begin_update() final
176 {
177 if (++iUpdating == 1)
178 ItemsUpdating.trigger();
179 }
180 void end_update() final
181 {
182 if (--iUpdating == 0)
183 {
184 reset_maps();
185 reset_meta();
186 reset_sort();
187
188 ItemsUpdated.trigger();
189 }
190 }
191 bool has_item_model() const final
192 {
193 return iItemModel != nullptr;
194 }
195 item_model_type& item_model() const final
196 {
197 if (iItemModel == nullptr)
198 throw no_item_model();
199 return static_cast<item_model_type&>(*iItemModel);
200 }
201 void set_item_model(i_item_model& aItemModel) final
202 {
203 if (iItemModel != &aItemModel)
204 {
205 auto reset_model = [this]()
206 {
207 {
208 scoped_item_update siu{ *this };
209 iColumns.clear();
210 for (item_model_index::column_type col = 0; col < item_model().columns(); ++col)
211 iColumns.emplace_back(col);
212 iRows.clear();
213 for (item_model_index::row_type row = 0; row < item_model().rows(); ++row)
215 }
216
217 ItemModelChanged.trigger(item_model());
218 };
219 iItemModelSink.clear();
220 iItemModel = &aItemModel;
221 iItemModelSink += item_model().column_info_changed([this](item_model_index::column_type aColumnIndex) { item_model_column_info_changed(aColumnIndex); });
222 iItemModelSink += item_model().item_added([this](const item_model_index& aItemIndex) { item_added(aItemIndex); });
223 iItemModelSink += item_model().item_changed([this](const item_model_index& aItemIndex) { item_changed(aItemIndex); });
224 iItemModelSink += item_model().item_removing([this](const item_model_index& aItemIndex) { item_removing(aItemIndex); });
225 iItemModelSink += item_model().item_removed([this](const item_model_index& aItemIndex) { item_removed(aItemIndex); });
226 iItemModelSink += item_model().cleared([this]()
227 {
228 iRows.clear();
229 reset_maps();
230 reset_meta();
231 reset_sort();
232 ItemModelChanged.trigger(item_model());
233 });
234 iItemModelSink += item_model().destroying([this]()
235 {
236 iItemModel = nullptr;
237 iColumns.clear();
238 iRows.clear();
239 reset_maps();
240 reset_meta();
241 reset_sort();
242 });
243 reset_model();
244 }
245 }
246 item_model_index to_item_model_index(item_presentation_model_index const& aIndex) const final
247 {
248 return item_model_index{ row(aIndex).value, model_column(aIndex.column()) };
249 }
250 bool has_item_model_index(item_model_index const& aIndex) const final
251 {
252 return aIndex.row() < row_map().size() && row_map()[aIndex.row()];
253 }
254 item_presentation_model_index from_item_model_index(item_model_index const& aIndex, bool aIgnoreColumn = false) const final
255 {
256 if (has_item_model_index(aIndex))
257 return item_presentation_model_index{ mapped_row(aIndex.row()), !aIgnoreColumn ? mapped_column(aIndex.column()) : 0 };
258 throw bad_index();
259 }
260 public:
261 uint32_t rows() const final
262 {
263 if constexpr (container_traits::is_flat)
264 return static_cast<uint32_t>(iRows.size());
265 else
266 return static_cast<uint32_t>(iRows.ksize());
267 }
268 uint32_t columns() const final
269 {
270 return static_cast<uint32_t>(iColumns.size());
271 }
272 uint32_t columns(item_presentation_model_index const& aIndex) const final
273 {
274 return static_cast<uint32_t>(row(aIndex).cells.size());
275 }
276 public:
277 void accept(i_meta_visitor& aVisitor, bool aIgnoreCollapsedState = false) final
278 {
279 if constexpr (container_traits::is_flat)
280 {
281 for (auto row = iRows.begin(); row != iRows.end(); ++row)
282 for (auto& cell : row->cells)
283 aVisitor.visit(cell);
284 }
285 else if (!aIgnoreCollapsedState)
286 {
287 for (auto row = iRows.kbegin(); row != iRows.kend(); ++row)
288 for (auto& cell : row->cells)
289 aVisitor.visit(cell);
290 }
291 else
292 {
293 for (auto row = iRows.begin(); row != iRows.end(); ++row)
294 for (auto& cell : row->cells)
295 aVisitor.visit(cell);
296 }
297 }
298 public:
299 dimension column_width(item_presentation_model_index::column_type aColumnIndex, i_units_context const& aUnitsContext, bool aExtendIntoPadding = true) const override
300 {
301 if (rows() == 0 || columns() < aColumnIndex + 1u)
302 return 0.0;
303 auto& cellWidths = column(aColumnIndex).cellWidths;
304 if (!cellWidths.empty())
305 return units_converter(aUnitsContext).from_device_units(cellWidths.rbegin()->first) + (aExtendIntoPadding ? cell_padding(aUnitsContext).size().cx : 0.0);
306 for (item_presentation_model_index::row_type row = 0u; row < rows(); ++row)
307 cell_extents(item_presentation_model_index{ row, aColumnIndex }, aUnitsContext);
308 return units_converter(aUnitsContext).from_device_units(cellWidths.rbegin()->first) + (aExtendIntoPadding ? cell_padding(aUnitsContext).size().cx : 0.0);
309 }
310 std::string const& column_heading_text(item_presentation_model_index::column_type aColumnIndex) const override
311 {
312 if (column(aColumnIndex).headingText != std::nullopt)
313 return *column(aColumnIndex).headingText;
314 else if (has_model_column(aColumnIndex))
315 return item_model().column_name(model_column(aColumnIndex));
316 else
317 {
318 static std::string const none;
319 return none;
320 }
321 }
322 size column_heading_extents(item_presentation_model_index::column_type aColumnIndex, i_units_context const& aUnitsContext) const override
323 {
324 if (column(aColumnIndex).headingFont != font{})
325 {
326 column(aColumnIndex).headingFont = font{};
327 column(aColumnIndex).headingExtents = std::nullopt;
328 }
329 if (column(aColumnIndex).headingExtents != std::nullopt)
330 return units_converter(aUnitsContext).from_device_units(*column(aColumnIndex).headingExtents);
331 if (!metrics_available())
332 return size{};
334 multiline_text_extent(column_heading_text(aColumnIndex), column(aColumnIndex).headingFont);
335 column(aColumnIndex).headingExtents = units_converter(aUnitsContext).to_device_units(columnHeadingExtents).ceil();
336 return units_converter(aUnitsContext).from_device_units(*column(aColumnIndex).headingExtents);
337 }
338 void set_column_heading_text(item_presentation_model_index::column_type aColumnIndex, std::string const& aHeadingText) final
339 {
340 column(aColumnIndex).headingText = aHeadingText;
341 column(aColumnIndex).headingExtents = std::nullopt;
342 ColumnInfoChanged.trigger(aColumnIndex);
343 }
344 item_cell_flags column_flags(item_presentation_model_index::column_type aColumnIndex) const override
345 {
346 if (aColumnIndex < columns())
347 return column(aColumnIndex).flags;
349 }
350 void set_column_flags(item_presentation_model_index::column_type aColumnIndex, item_cell_flags aFlags) final
351 {
352 if (column(aColumnIndex).flags != aFlags)
353 {
354 column(aColumnIndex).flags = aFlags;
355 ColumnInfoChanged.trigger(aColumnIndex);
356 }
357 }
358 optional_size column_image_size(item_presentation_model_index::column_type aColumnIndex) const override
359 {
360 return column(aColumnIndex).imageSize;
361 }
362 void set_column_image_size(item_presentation_model_index::column_type aColumnIndex, optional_size const& aImageSize) final
363 {
364 if (column(aColumnIndex).imageSize != aImageSize)
365 {
366 column(aColumnIndex).imageSize = aImageSize;
367 reset_meta();
368 ColumnInfoChanged.trigger(aColumnIndex);
369 }
370 }
371 bool expand(item_presentation_model_index const& aIndex) final
372 {
373 if constexpr (container_traits::is_tree)
374 {
375 if (!cell_meta(aIndex.with_column(0)).expanded)
376 {
377 toggle_expanded(aIndex.with_column(0));
378 return true;
379 }
380 }
381 return false;
382 }
383 bool collapse(item_presentation_model_index const& aIndex) final
384 {
385 if constexpr (container_traits::is_tree)
386 {
387 if (cell_meta(aIndex.with_column(0)).expanded)
388 {
389 toggle_expanded(aIndex.with_column(0));
390 return true;
391 }
392 }
393 return false;
394 }
395 bool toggle_expanded(item_presentation_model_index const& aIndex) final
396 {
397 if constexpr (container_traits::is_tree)
398 {
399 if (!item_model().has_children(to_item_model_index(aIndex)))
400 return false;
401 item_presentation_model_index const indexFirstColumn{ aIndex.row() };
402 if (!cell_meta(indexFirstColumn).expanded)
403 ItemExpanding.trigger(aIndex);
404 else
405 ItemCollapsing.trigger(aIndex);
406 cell_meta(indexFirstColumn).expanded = !cell_meta(indexFirstColumn).expanded;
407 if (cell_meta(indexFirstColumn).expanded)
408 std::next(begin(), aIndex.row()).unskip_children();
409 else
410 std::next(begin(), aIndex.row()).skip_children();
411 reset_row_map();
412 reset_meta();
413 if (cell_meta(indexFirstColumn).expanded)
414 ItemExpanded.trigger(aIndex);
415 else
416 ItemCollapsed.trigger(aIndex);
417 return true;
418 }
419 else
420 return false;
421 }
422 bool expand_to(item_model_index const& aIndex) final
423 {
424 if constexpr (container_traits::is_tree)
425 {
426 if (!has_item_model_index(aIndex) && item_model().has_parent(aIndex))
427 expand_to(item_model().parent(aIndex));
429 return true;
430 }
431 else
432 return false;
433 }
434 const button_checked_state& checked_state(item_presentation_model_index const& aIndex) final
435 {
436 return cell_meta(aIndex).checked;
437 }
438 bool is_checked(item_presentation_model_index const& aIndex) const final
439 {
440 return cell_meta(aIndex).checked == true;
441 }
442 bool is_unchecked(item_presentation_model_index const& aIndex) const final
443 {
444 return cell_meta(aIndex).checked == false;
445 }
446 bool is_indeterminate(item_presentation_model_index const& aIndex) const final
447 {
448 return cell_meta(aIndex).checked == std::nullopt;
449 }
450 void set_checked_state(item_presentation_model_index const& aIndex, button_checked_state const& aState) final
451 {
452 if (cell_meta(aIndex).checked != aState)
453 {
454 cell_meta(aIndex).checked = aState;
455 if (aState == std::nullopt && !cell_tri_state_checkable(aIndex))
456 throw not_tri_state_checkable();
457 cell_meta(aIndex).checked = aState;
458 ItemToggled.trigger(aIndex);
459 if (is_checked(aIndex))
460 ItemChecked.trigger(aIndex);
461 else if (is_unchecked(aIndex))
462 ItemUnchecked.trigger(aIndex);
463 else if (is_indeterminate(aIndex))
464 ItemIndeterminate.trigger(aIndex);
465 }
466 }
467 void check(item_presentation_model_index const& aIndex) final
468 {
469 set_checked(aIndex, true);
470 }
471 void uncheck(item_presentation_model_index const& aIndex) final
472 {
473 set_checked(aIndex, false);
474 }
475 void set_indeterminate(item_presentation_model_index const& aIndex) final
476 {
477 set_checked_state(aIndex, button_checked_state{});
478 }
479 void set_checked(item_presentation_model_index const& aIndex, bool aChecked) final
480 {
481 set_checked_state(aIndex, aChecked);
482 }
483 void toggle_check(item_presentation_model_index const& aIndex) final
484 {
485 if (is_checked(aIndex))
486 {
487 if (cell_tri_state_checkable(aIndex))
488 set_indeterminate(aIndex);
489 else
490 set_checked(aIndex, false);
491 }
492 else if (is_indeterminate(aIndex))
493 set_checked(aIndex, false);
494 else
495 set_checked(aIndex, true);
496 }
497 const font& default_font() const final
498 {
499 if (iDefaultFont != std::nullopt)
500 return *iDefaultFont;
501 return service<i_app>().current_style().font();
502 }
503 void set_default_font(optional_font const& aDefaultFont) final
504 {
505 if (iDefaultFont != aDefaultFont)
506 {
507 iDefaultFont = aDefaultFont;
508 reset_meta();
509 }
510 }
511 size cell_spacing(i_units_context const& aUnitsContext) const final
512 {
513 if (iCellSpacing == std::nullopt)
514 {
515 std::optional<scoped_units> su;
516 if (attached())
517 su.emplace(attachment(), scoped_units::current_units());
518 size result{ 1.0_mm, 1.0_mm };
519 if (to_px<uint32_t>(result.cx) % 2u == 1u)
520 result.cx = from_px<dimension>(to_px<uint32_t>(result.cx) + 1u);
521 if (to_px<uint32_t>(result.cy) % 2u == 1u)
522 result.cy = from_px<dimension>(to_px<uint32_t>(result.cy) + 1u);
523 return result;
524 }
525 return units_converter(aUnitsContext).from_device_units(*iCellSpacing);
526 }
527 void set_cell_spacing(optional_size const& aSpacing, i_units_context const& aUnitsContext) final
528 {
529 if (aSpacing == std::nullopt)
530 iCellSpacing = aSpacing;
531 else
532 iCellSpacing = units_converter(aUnitsContext).to_device_units(*aSpacing);
533 }
534 neogfx::padding cell_padding(i_units_context const& aUnitsContext) const final
535 {
536 if (iCellPadding == std::nullopt)
537 {
538 return units_converter(aUnitsContext).from_device_units(neogfx::padding{ 1.0, 1.0, 1.0, 1.0 });
539 }
540 return units_converter(aUnitsContext).from_device_units(*iCellPadding);
541 }
542 void set_cell_padding(optional_padding const& aPadding, i_units_context const& aUnitsContext) final
543 {
544 if (aPadding == std::nullopt)
545 iCellPadding = aPadding;
546 else
547 iCellPadding = units_converter(aUnitsContext).to_device_units(*aPadding);
548 }
549 bool alternating_row_color() const final
550 {
551 return iAlternatingRowColor;
552 }
553 void set_alternating_row_color(bool aAlternatingColor) final
554 {
555 iAlternatingRowColor = aAlternatingColor;
556 }
557 public:
558 dimension item_height(item_presentation_model_index const& aIndex, i_units_context const& aUnitsContext) const final
559 {
560 dimension height = 0.0;
561 for (uint32_t col = 0; col < row(aIndex).cells.size(); ++col)
562 {
563 auto const index = item_presentation_model_index{ aIndex.row(), col };
564 auto const modelIndex = to_item_model_index(index);
565 if (cell_meta(index).extents != std::nullopt)
566 height = std::max(height, units_converter(aUnitsContext).from_device_units(*cell_meta(index).extents).cy);
567 else
568 {
569 std::string cellString = cell_to_string(index);
570 auto const& cellFont = cell_font(index);
571 auto const& effectiveFont = (cellFont == std::nullopt ? default_font() : *cellFont);
572 height = std::max(height, units_converter(aUnitsContext).from_device_units(size{ 0.0, std::ceil(effectiveFont.height()) }).cy *
573 (1 + std::count(cellString.begin(), cellString.end(), '\n')));
574 auto const& maybeCellImageSize = cell_image_size(index);
575 if (maybeCellImageSize != std::nullopt)
576 height = std::max(height, units_converter(aUnitsContext).from_device_units(*maybeCellImageSize).cy);
577 auto const& cellInfo = item_model().cell_info(modelIndex);
578 if (cell_editable(index) && cellInfo.dataStep != neolib::none)
579 height = std::max<dimension>(height, dip(basic_spin_box<double>::SPIN_BUTTON_MINIMUM_SIZE.cy * 2.0));
580 }
581 }
582 return height + cell_padding(aUnitsContext).size().cy + cell_spacing(aUnitsContext).cy;
583 }
584 double total_height(i_units_context const& aUnitsContext) const final
585 {
586 if (iTotalHeight != std::nullopt)
587 return *iTotalHeight;
588 i_scrollbar::value_type height = 0.0;
589 for (item_presentation_model_index::row_type row = 0; row < rows(); ++row)
590 height += item_height(item_presentation_model_index(row, 0), aUnitsContext);
591 iTotalHeight = height;
592 return *iTotalHeight;
593 }
594 double item_position(item_presentation_model_index const& aIndex, i_units_context const& aUnitsContext) const final
595 {
596 if (iPositions[aIndex.row()] == std::nullopt)
597 {
598 auto pred = [](const optional_position& lhs, const optional_position& rhs) -> bool
599 {
600 if (lhs == std::nullopt && rhs == std::nullopt)
601 return false;
602 else if (lhs != std::nullopt && rhs == std::nullopt)
603 return true;
604 else if (lhs == std::nullopt && rhs != std::nullopt)
605 return false;
606 else
607 return *lhs < *rhs;
608 };
609 auto i = std::lower_bound(iPositions.begin(), iPositions.end(), optional_position(), pred);
610 auto row = static_cast<item_presentation_model_index::row_type>(std::distance(iPositions.begin(), i));
611 double position = (row == 0 ? 0.0 : **(std::prev(i)) + item_height(item_presentation_model_index(row - 1), aUnitsContext));
612 while (row <= aIndex.row())
613 {
614 iPositions[row] = position;
615 position += item_height(item_presentation_model_index(row), aUnitsContext);
616 ++row;
617 }
618 }
619 return *iPositions[aIndex.row()];
620 }
621 std::pair<item_presentation_model_index::row_type, coordinate> item_at(double aPosition, i_units_context const& aUnitsContext) const final
622 {
623 if (rows() == 0)
624 return std::pair<item_presentation_model_index::row_type, coordinate>{ 0u, 0.0 };
625 auto pred = [](const optional_position& lhs, const optional_position& rhs) -> bool
626 {
627 if (lhs == std::nullopt && rhs == std::nullopt)
628 return false;
629 else if (lhs != std::nullopt && rhs == std::nullopt)
630 return true;
631 else if (lhs == std::nullopt && rhs != std::nullopt)
632 return false;
633 else
634 return *lhs < *rhs;
635 };
636 auto i = std::lower_bound(iPositions.begin(), iPositions.end(), aPosition, pred);
637 if ((i == iPositions.end() || (*i != std::nullopt && **i > aPosition)) && i != iPositions.begin())
638 --i;
639 while (*i == std::nullopt)
640 {
641 auto j = std::lower_bound(iPositions.begin(), iPositions.end(), optional_position(), pred);
642 auto row = static_cast<item_presentation_model_index::row_type>(std::distance(iPositions.begin(), j));
643 double position = (row == 0 ? 0.0 : **(std::prev(j)) + item_height(item_presentation_model_index(row - 1), aUnitsContext));
644 while (row < iPositions.size())
645 {
646 iPositions[row] = position;
647 position += item_height(item_presentation_model_index(row), aUnitsContext);
648 ++row;
649 }
650 i = std::lower_bound(iPositions.begin(), iPositions.end(), aPosition, pred);
651 if ((i == iPositions.end() || (*i != std::nullopt && **i > aPosition)) && i != iPositions.begin())
652 --i;
653 }
654 auto const result = std::pair<item_presentation_model_index::row_type, coordinate>{ static_cast<item_presentation_model_index::row_type>(std::distance(iPositions.begin(), i)), static_cast<coordinate>(**i - aPosition) };
655 return result;
656 }
657 public:
658 item_cell_flags cell_flags(item_presentation_model_index const& aIndex) const override
659 {
660 if (cell_meta(aIndex).flags != std::nullopt)
661 return *cell_meta(aIndex).flags;
662 return column_flags(aIndex.column());
663 }
664 void set_cell_flags(item_presentation_model_index const& aIndex, item_cell_flags aFlags) final
665 {
666 if (cell_meta(aIndex).flags != aFlags)
667 {
668 cell_meta(aIndex).flags = aFlags;
669 ItemChanged.trigger(aIndex);
670 }
671 }
672 cell_meta_type& cell_meta(item_presentation_model_index const& aIndex) const final
673 {
674 if (aIndex.row() < rows())
675 {
676 if (aIndex.column() >= row(aIndex).cells.size())
677 {
678 row(aIndex).cells.resize(aIndex.column() + 1);
679 if constexpr (container_traits::is_tree)
680 if (aIndex.column() == 0)
681 row(aIndex).cells[aIndex.column()].expanded = !std::next(begin(), aIndex.row()).children_skipped();
682 }
683 return row(aIndex).cells[aIndex.column()];
684 }
685 throw bad_index();
686 }
687 public:
688 std::string cell_to_string(item_presentation_model_index const& aIndex) const final
689 {
690 auto const modelIndex = to_item_model_index(aIndex);
691 return std::visit([&, this](auto&& arg) -> std::string
692 {
693 typedef std::decay_t<decltype(arg)> type;
694 if constexpr(!std::is_same_v<type, std::monostate> && classify_item_call_data<type>::category == item_cell_data_category::Value)
695 return (cell_format(aIndex) % std::get<type>(item_model().cell_data(modelIndex))).str();
696 else
697 return "";
698 }, item_model().cell_data(modelIndex));
699 }
700 item_cell_data string_to_cell_data(item_presentation_model_index const& aIndex, std::string const& aString) const final
701 {
702 bool error = false;
703 return string_to_cell_data(aIndex, aString, error);
704 }
705 item_cell_data string_to_cell_data(item_presentation_model_index const& aIndex, std::string const& aString, bool& aError) const final
706 {
707 aError = false;
708 auto const& cellInfo = item_model().cell_info(to_item_model_index(aIndex));
709 std::istringstream input{ aString };
710 std::string guff;
711 switch (cellInfo.dataType)
712 {
714 {
715 bool value;
716 if (!(input >> value) || (input >> guff))
717 {
718 aError = true;
719 return has_item_model() ? item_model().cell_data(to_item_model_index(aIndex)) : item_cell_data{};
720 }
721 return value;
722 }
724 {
725 int32_t value;
726 if (!(input >> value) || (input >> guff))
727 {
728 aError = true;
729 return has_item_model() ? item_model().cell_data(to_item_model_index(aIndex)) : item_cell_data{};
730 }
731 if (cellInfo.dataMin != neolib::none)
732 value = std::max(value, static_variant_cast<int32_t>(cellInfo.dataMin));
733 if (cellInfo.dataMax != neolib::none)
734 value = std::min(value, static_variant_cast<int32_t>(cellInfo.dataMax));
735 return value;
736 }
738 {
739 uint32_t value;
740 if (!(input >> value) || (input >> guff))
741 {
742 aError = true;
743 return has_item_model() ? item_model().cell_data(to_item_model_index(aIndex)) : item_cell_data{};
744 }
745 if (cellInfo.dataMin != neolib::none)
746 value = std::max(value, static_variant_cast<uint32_t>(cellInfo.dataMin));
747 if (cellInfo.dataMax != neolib::none)
748 value = std::min(value, static_variant_cast<uint32_t>(cellInfo.dataMax));
749 return value;
750 }
752 {
753 int64_t value;
754 if (!(input >> value) || (input >> guff))
755 {
756 aError = true;
757 return has_item_model() ? item_model().cell_data(to_item_model_index(aIndex)) : item_cell_data{};
758 }
759 if (cellInfo.dataMin != neolib::none)
760 value = std::max(value, static_variant_cast<int64_t>(cellInfo.dataMin));
761 if (cellInfo.dataMax != neolib::none)
762 value = std::min(value, static_variant_cast<int64_t>(cellInfo.dataMax));
763 return value;
764 }
766 {
767 uint64_t value;
768 if (!(input >> value) || (input >> guff))
769 {
770 aError = true;
771 return has_item_model() ? item_model().cell_data(to_item_model_index(aIndex)) : item_cell_data{};
772 }
773 if (cellInfo.dataMin != neolib::none)
774 value = std::max(value, static_variant_cast<uint64_t>(cellInfo.dataMin));
775 if (cellInfo.dataMax != neolib::none)
776 value = std::min(value, static_variant_cast<uint64_t>(cellInfo.dataMax));
777 return value;
778 }
780 {
781 float value;
782 if (!(input >> value) || (input >> guff))
783 {
784 aError = true;
785 return has_item_model() ? item_model().cell_data(to_item_model_index(aIndex)) : item_cell_data{};
786 }
787 if (cellInfo.dataMin != neolib::none)
788 value = std::max(value, static_variant_cast<float>(cellInfo.dataMin));
789 if (cellInfo.dataMax != neolib::none)
790 value = std::min(value, static_variant_cast<float>(cellInfo.dataMax));
791 return value;
792 }
794 {
795 double value;
796 if (!(input >> value) || (input >> guff))
797 {
798 aError = true;
799 return has_item_model() ? item_model().cell_data(to_item_model_index(aIndex)) : item_cell_data{};
800 }
801 if (cellInfo.dataMin != neolib::none)
802 value = std::max(value, static_variant_cast<double>(cellInfo.dataMin));
803 if (cellInfo.dataMax != neolib::none)
804 value = std::min(value, static_variant_cast<double>(cellInfo.dataMax));
805 return value;
806 }
808 default:
809 if (!aString.empty())
810 return string{ aString };
811 else
812 return {};
813 }
814 }
815 boost::basic_format<char> cell_format(item_presentation_model_index const&) const override
816 {
817 static const boost::basic_format<char> sDefaultFormat("%1%");
818 return sDefaultFormat;
819 }
820 optional_color cell_color(item_presentation_model_index const&, color_role) const override
821 {
822 return optional_color{};
823 }
824 optional_font cell_font(item_presentation_model_index const&) const override
825 {
826 return optional_font{};
827 }
828 optional_size cell_image_size(item_presentation_model_index const& aIndex) const override
829 {
830 if (column(aIndex.column()).imageSize)
831 return column(aIndex.column()).imageSize;
832 else if (cell_image(aIndex))
833 return cell_image(aIndex)->extents();
834 return {};
835 }
836 optional_size cell_check_box_size(item_presentation_model_index const& aIndex, i_units_context const& aUnitsContext) const override
837 {
838 if (cell_checkable(aIndex))
839 {
840 auto const& cellFont = cell_font(aIndex);
841 auto const& effectiveFont = (cellFont == std::nullopt ? default_font() : *cellFont);
842 dimension const length = units_converter(aUnitsContext).from_device_units(effectiveFont.height() * (2.0 / 3.0));
843 auto const checkBoxSize = service<i_skin_manager>().active_skin().preferred_size(skin_element::CheckBox, size{ length });
844 return checkBoxSize;
845 }
846 return {};
847 }
848 optional_size cell_tree_expander_size(item_presentation_model_index const& aIndex, i_units_context const& aUnitsContext) const override
849 {
850 if constexpr (container_traits::is_flat)
851 return {};
852 else
853 return size{ 16.0_dip };
854 }
855 optional_texture cell_image(item_presentation_model_index const&) const override
856 {
857 return optional_texture{};
858 }
859 neogfx::glyph_text& cell_glyph_text(item_presentation_model_index const& aIndex) const override
860 {
861 if (cell_meta(aIndex).text != std::nullopt)
862 return *cell_meta(aIndex).text;
863 auto const& cellFont = cell_font(aIndex);
864 auto const& effectiveFont = (cellFont == std::nullopt ? default_font() : *cellFont);
866 to_glyph_text(cell_to_string(aIndex), effectiveFont);
867 return *cell_meta(aIndex).text;
868 }
869 size cell_extents(item_presentation_model_index const& aIndex, i_units_context const& aUnitsContext) const override
870 {
871 auto oldItemHeight = item_height(aIndex, aUnitsContext);
872 auto const& cellFont = cell_font(aIndex);
873 auto const& effectiveFont = (cellFont == std::nullopt ? default_font() : *cellFont);
874 auto& cellMeta = cell_meta(aIndex);
875 if (cellMeta.extents != std::nullopt)
876 return units_converter(aUnitsContext).from_device_units(*cellMeta.extents);
877 size cellExtents = cell_glyph_text(aIndex).extents();
878 auto const& cellInfo = item_model().cell_info(to_item_model_index(aIndex));
879 if (cell_editable(aIndex) && cellInfo.dataStep != neolib::none)
880 {
881 cellExtents.cx = std::max(cellExtents.cx, graphics_context{ attachment(), graphics_context::type::Unattached }.
882 text_extent(cellInfo.dataMax.to_string(), effectiveFont).cx);
883 cellExtents.cx += dip(basic_spin_box<double>::INTERNAL_SPACING.cx + basic_spin_box<double>::SPIN_BUTTON_MINIMUM_SIZE.cx); // todo: get this from widget metrics (skin API)
884 cellExtents.cy = std::max<dimension>(cellExtents.cy, dip(basic_spin_box<double>::SPIN_BUTTON_MINIMUM_SIZE.cy * 2.0));
885 }
886 cellExtents.cx += indent(aIndex, aUnitsContext);
887 auto const& maybeCheckBoxSize = cell_check_box_size(aIndex, aUnitsContext);
888 if (maybeCheckBoxSize != std::nullopt)
889 {
890 cellExtents.cx += (maybeCheckBoxSize->cx + units_converter(aUnitsContext).to_device_units(cell_spacing(aUnitsContext)).cx);
891 cellExtents.cy = std::max(cellExtents.cy, maybeCheckBoxSize->cy);
892 }
893 auto const& maybeCellImageSize = cell_image_size(aIndex);
894 if (maybeCellImageSize != std::nullopt)
895 {
896 cellExtents.cx += (maybeCellImageSize->cx + units_converter(aUnitsContext).to_device_units(cell_spacing(aUnitsContext)).cx);
897 cellExtents.cy = std::max(cellExtents.cy, maybeCellImageSize->cy);
898 }
899 cellExtents.cy = std::max(cellExtents.cy, effectiveFont.height());
900 cache_cell_meta_extents(aIndex, cellExtents.ceil());
901 if (iTotalHeight != std::nullopt)
902 *iTotalHeight += (item_height(aIndex, aUnitsContext) - oldItemHeight);
903 return units_converter(aUnitsContext).from_device_units(*cell_meta(aIndex).extents);
904 }
905 dimension indent(item_presentation_model_index const& aIndex, i_units_context const& aUnitsContext) const override
906 {
907 if constexpr (container_traits::is_flat)
908 return 0.0;
909 else if (aIndex.column() != 0)
910 return 0.0;
911 else
912 {
913 auto baseIter = typename item_model_type::const_base_iterator{ item_model().index_to_iterator(to_item_model_index(aIndex)) };
914 auto treeIter = baseIter.get<typename item_model_type::const_iterator, typename item_model_type::const_iterator,
915 typename item_model_type::iterator, typename item_model_type::const_sibling_iterator, typename item_model_type::sibling_iterator>();
916 return (treeIter.depth() + 1) * cell_tree_expander_size(aIndex, aUnitsContext)->cx;
917 }
918 }
919 public:
920 void sort(i_item_sort_predicate const& aPredicate) final
921 {
922 iSortOrder.clear();
923 ItemsSorting.trigger();
924 if constexpr (container_traits::is_flat)
925 std::sort(iRows.begin(), iRows.end(), [&](auto const& lhs, auto const& rhs) { return aPredicate.compare(lhs.value, rhs.value); });
926 else
927 iRows.sort([&](auto const& lhs, auto const& rhs) { return aPredicate.compare(lhs.value, rhs.value); });
928 reset_row_map();
929 reset_position_meta(0);
930 ItemsSorted.trigger();
931 }
932 bool sortable() const final
933 {
934 return iSortable;
935 }
936 void set_sortable(bool aSortable) final
937 {
938 iSortable = aSortable;
939 }
940 optional_sort_by_param sorting_by() const final
941 {
942 if (!iSortOrder.empty())
943 return iSortOrder.front();
944 else
945 return optional_sort_by_param{};
946 }
947 void sort_by(item_presentation_model_index::column_type aColumnIndex, const optional_sort_direction& aSortDirection = optional_sort_direction{}) final
948 {
949 iSortOrder.push_front(sort_by_param{ aColumnIndex, aSortDirection == std::nullopt ? sort_direction::Ascending : *aSortDirection });
950 for (auto i = std::next(iSortOrder.begin()); i != iSortOrder.end(); ++i)
951 {
952 if (i->first == aColumnIndex)
953 {
954 if (aSortDirection == std::nullopt)
955 {
956 if (i == std::next(iSortOrder.begin()))
957 iSortOrder.front().second = (i->second == sort_direction::Ascending ? sort_direction::Descending : sort_direction::Ascending);
958 else
959 iSortOrder.front().second = i->second;
960 }
961 iSortOrder.erase(i);
962 break;
963 }
964 }
965 execute_sort(true);
966 }
967 void reset_sort() final
968 {
969 iSortOrder.clear();
970 if (sortable())
971 execute_sort();
972 }
973 public:
974 optional_item_presentation_model_index find_item(filter_search_key const& aFilterSearchKey, item_presentation_model_index::column_type aColumnIndex = 0,
975 filter_search_type aFilterSearchType = filter_search_type::Prefix, case_sensitivity aCaseSensitivity = case_sensitivity::CaseInsensitive) const final
976 {
977 for (item_presentation_model_index::row_type row = 0; row < rows(); ++row)
978 {
979 auto modelIndex = to_item_model_index(item_presentation_model_index{ row, aColumnIndex });
980 auto const& origValue = item_model().cell_data(modelIndex).to_string();
981 auto const& value = aCaseSensitivity == case_sensitivity::CaseSensitive ? origValue : boost::to_upper_copy<std::string>(origValue);
982 auto const& origKey = aFilterSearchKey;
983 auto const& key = aCaseSensitivity == case_sensitivity::CaseSensitive ? origKey : boost::to_upper_copy<std::string>(origKey);
984 if (key.empty())
985 continue;
986 switch (aFilterSearchType)
987 {
988 case filter_search_type::Prefix:
989 if (value.size() >= key.size() && value.substr(0, key.size()) == key)
990 return from_item_model_index(modelIndex);
991 }
992 }
993 return optional_item_presentation_model_index{};
994 }
995 public:
996 bool filtering() const final
997 {
998 return iFiltering;
999 }
1000 optional_filter filtering_by() const final
1001 {
1002 if (!iFilters.empty())
1003 return iFilters.front();
1004 else
1005 return optional_filter{};
1006 }
1007 void filter_by(item_presentation_model_index::column_type aColumnIndex, filter_search_key const& aFilterSearchKey,
1008 filter_search_type aFilterSearchType = filter_search_type::Value, case_sensitivity aCaseSensitivity = case_sensitivity::CaseInsensitive) final
1009 {
1010 iFilters.push_back(filter{ aColumnIndex, aFilterSearchKey, aFilterSearchType, aCaseSensitivity });
1011 for (auto i = iFilters.begin(); i != std::prev(iFilters.end()); ++i)
1012 {
1013 if (std::get<0>(*i) == aColumnIndex)
1014 {
1015 iFilters.erase(i);
1016 break;
1017 }
1018 }
1019 execute_filter();
1020 }
1021 void reset_filter() final
1022 {
1023 if (!iFilters.empty())
1024 {
1025 iFilters.clear();
1026 execute_filter();
1027 }
1028 }
1029 private:
1030 void init()
1031 {
1032 iSink = service<i_rendering_engine>().subpixel_rendering_changed([this]()
1033 {
1034 reset_meta();
1035 });
1036 iSink += service<i_app>().current_style_changed([this](style_aspect aAspect)
1037 {
1039 reset_meta();
1040 });
1041 reset_sort();
1042 set_alive();
1043 }
1044 void execute_sort(bool aForce = false)
1045 {
1046 if (!sortable() && !aForce)
1047 return;
1048 if (rows() <= 1)
1049 return;
1050 if (iSortOrder.empty())
1051 {
1052 sort_by(0, sort_direction::Ascending);
1053 return;
1054 }
1055 ItemsSorting.trigger();
1056 auto sortPredicate = [&](const typename container_type::value_type& aLhs, const typename container_type::value_type& aRhs) -> bool
1057 {
1058 for (std::size_t i = 0; i < iSortOrder.size(); ++i)
1059 {
1060 auto col = iSortOrder[i].first;
1061 auto const& v1 = item_model().cell_data(item_model_index{ aLhs.value, model_column(col) });
1062 auto const& v2 = item_model().cell_data(item_model_index{ aRhs.value, model_column(col) });
1063 if (std::holds_alternative<string>(v1) && std::holds_alternative<string>(v2))
1064 {
1065 std::string s1 = boost::to_upper_copy<std::string>(std::get<string>(v1));
1066 std::string s2 = boost::to_upper_copy<std::string>(std::get<string>(v2));
1067 if (s1 < s2)
1068 return iSortOrder[i].second == sort_direction::Ascending;
1069 else if (s2 < s1)
1070 return iSortOrder[i].second == sort_direction::Descending;
1071 }
1072 if (v1 < v2)
1073 return iSortOrder[i].second == sort_direction::Ascending;
1074 else if (v2 < v1)
1075 return iSortOrder[i].second == sort_direction::Descending;
1076 }
1077 return false;
1078 };
1079 if constexpr (container_traits::is_flat)
1080 std::sort(iRows.begin(), iRows.end(), sortPredicate);
1081 else
1082 iRows.sort(sortPredicate);
1083 reset_row_map();
1084 reset_position_meta(0);
1085 ItemsSorted.trigger();
1086 }
1087 void execute_filter()
1088 {
1089 {
1090 scoped_item_update siu{ *this };
1091 neolib::scoped_flag sf2{ iFiltering };
1092 ItemsFiltering.trigger();
1093 iRows.clear();
1094 for (item_model_index::row_type row = 0; row < item_model().rows(); ++row)
1095 {
1096 bool matches = true;
1097 for (auto const& filter : iFilters)
1098 {
1099 auto const& origValue = item_model().cell_data(item_model_index{ row, model_column(std::get<0>(filter)) }).to_string();
1100 auto const& value = (std::get<3>(filter) == case_sensitivity::CaseSensitive ? origValue : boost::to_upper_copy<std::string>(origValue));
1101 auto const& origKey = std::get<1>(filter);
1102 auto const& key = (std::get<3>(filter) == case_sensitivity::CaseSensitive ? origKey : boost::to_upper_copy<std::string>(origKey));
1103 if (key.empty())
1104 continue;
1105 switch (std::get<2>(filter))
1106 {
1107 case filter_search_type::Prefix:
1108 if (value.size() < key.size() || value.substr(0, key.size()) != key)
1109 matches = false;
1110 break;
1111 case filter_search_type::Glob:
1112 // todo
1113 break;
1114 case filter_search_type::Regex:
1115 // todo
1116 break;
1117 }
1118 }
1119 if (matches)
1120 item_added(item_model_index{ row });
1121 }
1122 }
1123 ItemsFiltered.trigger();
1124 execute_sort();
1125 }
1126 private:
1127 void item_model_column_info_changed(item_model_index::column_type aColumnIndex)
1128 {
1129 reset_column_map(false);
1130 if (has_item_model_index(item_model_index{ 0, aColumnIndex }))
1131 {
1132 auto const index = from_item_model_index(item_model_index{ 0, aColumnIndex });
1133 reset_cell_meta(index.column());
1134 reset_column_meta(index.column());
1135 ColumnInfoChanged.trigger(index.column());
1136 }
1137 }
1138 void item_added(const item_model_index& aItemIndex)
1139 {
1140 if constexpr (container_traits::is_tree)
1141 if (item_model().has_parent(aItemIndex) && !has_item_model_index(item_model().parent(aItemIndex)))
1142 return;
1143 for (auto& row : iRows)
1144 if (row.value >= aItemIndex.row())
1145 ++row.value;
1146 if constexpr (container_traits::is_flat)
1147 iRows.push_back(row_type{ aItemIndex.row() });
1148 else
1149 {
1150 if (!item_model().has_parent(aItemIndex))
1151 {
1152 auto const pos = iRows.csend();
1153 iRows.insert(pos, row_type{ aItemIndex.row() });
1154 }
1155 else
1156 {
1157 auto const parentIndex = from_item_model_index(item_model().parent(aItemIndex));
1158 auto const pos = const_sibling_iterator{ std::next(iRows.cbegin(), parentIndex.row()) };
1159 iRows.insert(pos.end(), row_type{ aItemIndex.row() });
1160 }
1161 }
1162
1163 if (!updating() || container_traits::is_tree)
1164 reset_row_map(aItemIndex);
1165
1166 if (!updating())
1167 {
1168 reset_position_meta(aItemIndex.row());
1169 execute_sort();
1170 ItemAdded.trigger(from_item_model_index(aItemIndex, true));
1171 }
1172 }
1173 void item_changed(const item_model_index& aItemIndex)
1174 {
1175 if (!has_item_model_index(aItemIndex))
1176 return;
1177 if (!updating())
1178 {
1179 reset_row_map();
1180 reset_position_meta(aItemIndex.row());
1181 execute_sort();
1182 auto const index = from_item_model_index(aItemIndex);
1183 auto& cellMeta = cell_meta(index);
1184 cellMeta.text = std::nullopt;
1185 cache_cell_meta_extents(index, std::nullopt);
1186 if (attached())
1187 cell_extents(index, attachment());
1188 ItemChanged.trigger(from_item_model_index(aItemIndex));
1189 }
1190 }
1191 void item_removing(const item_model_index& aItemIndex)
1192 {
1193 if (!has_item_model_index(aItemIndex))
1194 return;
1195 auto const index = from_item_model_index(aItemIndex);
1196 for (item_presentation_model_index::column_type col = 0; col < columns(); ++col)
1197 cache_cell_meta_extents(index.with_column(col), std::nullopt);
1198 if (!updating())
1199 ItemRemoving.trigger(index);
1200 iRows.erase(std::next(begin(), index.row()));
1201 for (auto& row : iRows)
1202 if (row.value >= aItemIndex.row())
1203 --row.value;
1204 if (iRowMap[aItemIndex.row()])
1205 for (auto& row : iRowMap)
1206 if (row && *row >= index.row())
1207 --* row;
1208 iRowMap.erase(std::next(iRowMap.begin(), aItemIndex.row()));
1209 if (!updating())
1210 {
1211 reset_position_meta(index.row());
1212 ItemRemoved.trigger(index);
1213 }
1214 }
1215 void item_removed(const item_model_index& aItemIndex)
1216 {
1217 }
1218 private:
1219 void cache_cell_meta_extents(item_presentation_model_index const& aIndex, const optional_size& aExtents = {}) const
1220 {
1221 auto& cellMeta = cell_meta(aIndex);
1222 if (aExtents != cellMeta.extents)
1223 {
1224 auto previousExtents = cellMeta.extents;
1225 cellMeta.extents = aExtents;
1226 if (previousExtents)
1227 column(aIndex.column()).remove_cell_width(previousExtents->cx);
1228 if (cellMeta.extents)
1229 column(aIndex.column()).add_cell_width(cellMeta.extents->cx);
1230 }
1231 }
1232 void reset_maps(const item_model_index& aFrom = {}) const
1233 {
1234 reset_row_map(aFrom);
1235 reset_column_map();
1236 }
1237 void reset_row_map(const item_model_index& aFrom = {}) const
1238 {
1239 if (aFrom.row() < iRowMap.size() && (iRowMapDirtyFrom == std::nullopt || *iRowMapDirtyFrom > aFrom.row()))
1240 iRowMapDirtyFrom = aFrom.row();
1241 }
1242 void reset_column_map(bool aClear = true) const
1243 {
1244 if (aClear)
1245 iColumnMap.clear();
1246 if (has_item_model())
1247 for (item_model_index::column_type col = 0; col < item_model().columns(); ++col)
1248 mapped_column(col);
1249 }
1250 item_presentation_model_index::row_type mapped_row(item_model_index::row_type aRowIndex) const
1251 {
1252 if (aRowIndex < row_map().size() && row_map()[aRowIndex])
1253 return *row_map()[aRowIndex];
1254 throw no_mapped_row();
1255 }
1256 const row_map_type& row_map() const
1257 {
1258 if (iRowMapDirtyFrom)
1259 iRowMap.erase(std::next(iRowMap.begin(), *iRowMapDirtyFrom), iRowMap.end());
1260 iRowMapDirtyFrom = std::nullopt;
1261 if (iRowMap.size() < item_model().rows())
1262 {
1263 iRowMap.resize(item_model().rows());
1264 for (item_presentation_model_index::row_type row = 0; row < rows(); ++row)
1265 iRowMap[self_type::row(row).value] = row;
1266 }
1267 return iRowMap;
1268 }
1269 row_map_type& row_map()
1270 {
1271 return const_cast<row_map_type&>(to_const(*this).row_map());
1272 }
1273 item_presentation_model_index::column_type mapped_column(item_model_index::column_type aColumnIndex) const
1274 {
1275 if (aColumnIndex >= iColumnMap.size())
1276 iColumnMap.resize(aColumnIndex + 1);
1277 auto& mapCol = iColumnMap[aColumnIndex];
1278 if (!mapCol)
1279 {
1280 for (item_presentation_model_index::column_type col = 0; col < columns(); ++col)
1281 if (iColumns[col].modelColumn == aColumnIndex)
1282 mapCol = col;
1283 if (!mapCol)
1284 {
1285 auto newColumn = columns();
1286 for (item_presentation_model_index::column_type col = 0; col < columns(); ++col)
1287 if (iColumns[col].modelColumn == std::nullopt)
1288 {
1289 newColumn = col;
1290 break;
1291 }
1292 column(newColumn).modelColumn = aColumnIndex;
1293 mapCol = newColumn;
1294 }
1295 }
1296 return *mapCol;
1297 }
1298 bool has_model_column(item_presentation_model_index::column_type aColumnIndex) const
1299 {
1300 auto const& col = column(aColumnIndex);
1301 if (col.modelColumn)
1302 reset_column_map(false);
1303 return col.modelColumn.has_value();
1304 }
1305 item_model_index::column_type model_column(item_presentation_model_index::column_type aColumnIndex) const
1306 {
1307 auto const& col = column(aColumnIndex);
1308 if (col.modelColumn)
1309 return *col.modelColumn;
1310 reset_column_map(false);
1311 if (col.modelColumn)
1312 return *col.modelColumn;
1313 throw bad_index();
1314 }
1315 const column_map_type& column_map() const
1316 {
1317 return iColumnMap;
1318 }
1319 column_map_type& column_map()
1320 {
1321 return iColumnMap;
1322 }
1323 void reset_meta() const
1324 {
1325 reset_cell_meta();
1326 reset_column_meta();
1327 reset_position_meta(0);
1328
1329 if (attached())
1330 for (item_presentation_model_index::row_type row = 0; row < rows(); ++row)
1331 for (item_presentation_model_index::column_type col = 0; col < iColumns.size(); ++col)
1332 cell_extents(item_presentation_model_index{row, col}, attachment());
1333 }
1334 void reset_cell_meta(const std::optional<item_presentation_model_index::column_type>& aColumn = {}) const
1335 {
1336 for (item_presentation_model_index::row_type row = 0; row < rows(); ++row)
1337 {
1338 for (item_presentation_model_index::column_type col = 0; col < iColumns.size(); ++col)
1339 {
1340 if (aColumn != std::nullopt && col != *aColumn)
1341 continue;
1342 item_presentation_model_index const index{ row, col };
1343 cell_meta(index).text = std::nullopt;
1344 cache_cell_meta_extents(index, std::nullopt);
1345 }
1346 }
1347 }
1348 void reset_column_meta(const std::optional<item_presentation_model_index::column_type>& aColumn = {}) const
1349 {
1350 for (item_presentation_model_index::column_type col = 0; col < iColumns.size(); ++col)
1351 {
1352 if (aColumn != std::nullopt && col != *aColumn)
1353 continue;
1354 column(col).headingExtents = std::nullopt;
1355 }
1356 }
1357 void reset_position_meta(item_presentation_model_index::row_type aFromRow) const
1358 {
1359 iTotalHeight = std::nullopt;
1360 iPositions.resize(rows());
1361 for (std::size_t i = aFromRow; i < iPositions.size(); ++i)
1362 iPositions[i] = std::nullopt;
1363 }
1364 private:
1365 const_iterator cbegin() const
1366 {
1367 if constexpr (container_traits::is_flat)
1368 return iRows.begin();
1369 else
1370 return iRows.kbegin();
1371 }
1372 const_iterator begin() const
1373 {
1374 return cbegin();
1375 }
1376 iterator begin()
1377 {
1378 if constexpr (container_traits::is_flat)
1379 return iRows.begin();
1380 else
1381 return iRows.kbegin();
1382 }
1383 const_iterator cend() const
1384 {
1385 if constexpr (container_traits::is_flat)
1386 return iRows.end();
1387 else
1388 return iRows.kend();
1389 }
1390 const_iterator end() const
1391 {
1392 return cend();
1393 }
1394 iterator end()
1395 {
1396 if constexpr (container_traits::is_flat)
1397 return iRows.end();
1398 else
1399 return iRows.kend();
1400 }
1401 private:
1402 const row_type& row(item_presentation_model_index::row_type aRow) const
1403 {
1404 return *std::next(begin(), aRow);
1405 }
1406 const row_type& row(item_presentation_model_index aIndex) const
1407 {
1408 return row(aIndex.row());
1409 }
1410 row_type& row(item_presentation_model_index::row_type aRow)
1411 {
1412 return *std::next(begin(), aRow);
1413 }
1414 row_type& row(item_presentation_model_index aIndex)
1415 {
1416 return row(aIndex.row());
1417 }
1418 const column_info& column(item_presentation_model_index::column_type aColumnIndex) const
1419 {
1420 while(iColumns.size() <= aColumnIndex)
1421 iColumns.emplace_back();
1422 return iColumns[aColumnIndex];
1423 }
1424 column_info& column(item_presentation_model_index::column_type aColumnIndex)
1425 {
1426 return const_cast<column_info&>(to_const(*this).column(aColumnIndex));
1427 }
1428 private:
1429 weak_ref_ptr<i_widget> iAttachment;
1430 i_item_model* iItemModel;
1431 bool iSortable;
1432 sink iItemModelSink;
1433 optional_size iCellSpacing;
1434 optional_padding iCellPadding;
1435 container_type iRows;
1436 mutable row_map_type iRowMap;
1437 mutable item_model_index::optional_row_type iRowMapDirtyFrom;
1438 mutable column_info_array iColumns;
1439 mutable column_map_type iColumnMap;
1440 mutable optional_font iDefaultFont;
1441 mutable std::optional<i_scrollbar::value_type> iTotalHeight;
1443 bool iAlternatingRowColor;
1444 std::deque<sort_by_param> iSortOrder;
1445 std::vector<filter> iFilters;
1446 sink iSink;
1447 std::uint32_t iUpdating = 0u;
1448 bool iFiltering = false;
1449 };
1450
1453}
size_type size() const
void set_checked_state(item_presentation_model_index const &aIndex, button_checked_state const &aState) final
boost::basic_format< char > cell_format(item_presentation_model_index const &) const override
bool is_unchecked(item_presentation_model_index const &aIndex) const final
item_presentation_model_index::column_type item_presentation_model_index const item_removing
optional_size cell_tree_expander_size(item_presentation_model_index const &aIndex, i_units_context const &aUnitsContext) const override
void attach(i_ref_ptr< i_widget > const &aWidget) final
void set_cell_padding(optional_padding const &aPadding, i_units_context const &aUnitsContext) final
dimension item_height(item_presentation_model_index const &aIndex, i_units_context const &aUnitsContext) const final
std::string cell_to_string(item_presentation_model_index const &aIndex) const final
item_presentation_model_index::column_type item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_expanded
void set_column_flags(item_presentation_model_index::column_type aColumnIndex, item_cell_flags aFlags) final
bool toggle_expanded(item_presentation_model_index const &aIndex) final
item_presentation_model_index from_item_model_index(item_model_index const &aIndex, bool aIgnoreColumn=false) const final
bool collapse(item_presentation_model_index const &aIndex) final
optional_item_presentation_model_index find_item(filter_search_key const &aFilterSearchKey, item_presentation_model_index::column_type aColumnIndex=0, filter_search_type aFilterSearchType=filter_search_type::Prefix, case_sensitivity aCaseSensitivity=case_sensitivity::CaseInsensitive) const final
const button_checked_state & checked_state(item_presentation_model_index const &aIndex) final
double total_height(i_units_context const &aUnitsContext) const final
item_cell_data string_to_cell_data(item_presentation_model_index const &aIndex, std::string const &aString) const final
double item_position(item_presentation_model_index const &aIndex, i_units_context const &aUnitsContext) const final
void set_checked(item_presentation_model_index const &aIndex, bool aChecked) final
item_presentation_model_index::column_type item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const items_updating items_sorting items_filtering i_drag_drop_item const dragging_item_render
basic_item_presentation_model(i_item_model &aItemModel, bool aSortable=false)
define_declared_event(VisualAppearanceChanged, visual_appearance_changed) define_declared_event(ColumnInfoChanged
item_presentation_model_index::column_type item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_toggled
neogfx::glyph_text & cell_glyph_text(item_presentation_model_index const &aIndex) const override
neogfx::padding cell_padding(i_units_context const &aUnitsContext) const final
bool expand(item_presentation_model_index const &aIndex) final
item_presentation_model_index::column_type item_added
void sort_by(item_presentation_model_index::column_type aColumnIndex, const optional_sort_direction &aSortDirection=optional_sort_direction{}) final
item_model_index to_item_model_index(item_presentation_model_index const &aIndex) const final
size cell_spacing(i_units_context const &aUnitsContext) const final
optional_size column_image_size(item_presentation_model_index::column_type aColumnIndex) const override
cell_meta_type & cell_meta(item_presentation_model_index const &aIndex) const final
item_presentation_model_index::column_type item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const items_updating items_sorting items_filtering dragging_item
size cell_extents(item_presentation_model_index const &aIndex, i_units_context const &aUnitsContext) const override
item_cell_data string_to_cell_data(item_presentation_model_index const &aIndex, std::string const &aString, bool &aError) const final
bool is_indeterminate(item_presentation_model_index const &aIndex) const final
void set_column_heading_text(item_presentation_model_index::column_type aColumnIndex, std::string const &aHeadingText) final
void set_item_model(i_item_model &aItemModel) final
void set_alternating_row_color(bool aAlternatingColor) final
std::pair< item_presentation_model_index::row_type, coordinate > item_at(double aPosition, i_units_context const &aUnitsContext) const final
size column_heading_extents(item_presentation_model_index::column_type aColumnIndex, i_units_context const &aUnitsContext) const override
void set_default_font(optional_font const &aDefaultFont) final
optional_color cell_color(item_presentation_model_index const &, color_role) const override
item_presentation_model_index::column_type item_presentation_model_index const item_presentation_model_index const item_expanding
void accept(i_meta_visitor &aVisitor, bool aIgnoreCollapsedState=false) final
void uncheck(item_presentation_model_index const &aIndex) final
void set_column_image_size(item_presentation_model_index::column_type aColumnIndex, optional_size const &aImageSize) final
dimension column_width(item_presentation_model_index::column_type aColumnIndex, i_units_context const &aUnitsContext, bool aExtendIntoPadding=true) const override
void toggle_check(item_presentation_model_index const &aIndex) final
bool expand_to(item_model_index const &aIndex) final
bool has_item_model_index(item_model_index const &aIndex) const final
void set_cell_flags(item_presentation_model_index const &aIndex, item_cell_flags aFlags) final
optional_texture cell_image(item_presentation_model_index const &) const override
void filter_by(item_presentation_model_index::column_type aColumnIndex, filter_search_key const &aFilterSearchKey, filter_search_type aFilterSearchType=filter_search_type::Value, case_sensitivity aCaseSensitivity=case_sensitivity::CaseInsensitive) final
void set_indeterminate(item_presentation_model_index const &aIndex) final
uint32_t columns(item_presentation_model_index const &aIndex) const final
void set_cell_spacing(optional_size const &aSpacing, i_units_context const &aUnitsContext) final
item_presentation_model_index::column_type item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_unchecked
item_presentation_model_index::column_type item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const item_presentation_model_index const items_updating items_sorting items_filtering i_drag_drop_item const i_drag_drop_item const i_graphics_context point const item_dropped
std::string const & column_heading_text(item_presentation_model_index::column_type aColumnIndex) const override
optional_font cell_font(item_presentation_model_index const &) const override
optional_size cell_image_size(item_presentation_model_index const &aIndex) const override
dimension indent(item_presentation_model_index const &aIndex, i_units_context const &aUnitsContext) const override
item_cell_flags column_flags(item_presentation_model_index::column_type aColumnIndex) const override
void check(item_presentation_model_index const &aIndex) final
optional_sort_by_param sorting_by() const final
bool is_checked(item_presentation_model_index const &aIndex) const final
void sort(i_item_sort_predicate const &aPredicate) final
optional_size cell_check_box_size(item_presentation_model_index const &aIndex, i_units_context const &aUnitsContext) const override
item_cell_flags cell_flags(item_presentation_model_index const &aIndex) const override
basic_size ceil() const
dimension_type cy
dimension_type cx
neogfx::size extents() const
virtual bool has_root() const =0
row_type row() const
column_type column() const
std::optional< column_type > optional_column_type
std::optional< row_type > optional_row_type
vector2 to_device_units(const vector2 &aValue) const
vector2 from_device_units(const vector2 &aValue) const
void set_alive() override
Definition lifetime.hpp:157
self_type ceil() const
void set_destroying() override
Definition object.hpp:66
const_iterator end() const
const_iterator begin() const
void clear()
Definition i_event.hpp:290
style_aspect
Definition i_style.hpp:31
default_geometry_value_type dimension
std::optional< texture > optional_texture
Definition texture.hpp:82
optional< padding > optional_padding
default_geometry_value_type coordinate
std::string to_string(note const &aNote)
basic_item_model< void * > item_model
basic_length< default_geometry_value_type > length
Definition units.hpp:926
basic_length< T > dip(T aValue)
Definition units.hpp:696
optional< size > optional_size
basic_size< coordinate > size
to_const_reference_t< T > to_const(T &&object)
Definition neolib.hpp:113
const none_t none
Definition variant.hpp:111
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
iterator_traits< it_type >::difference_type distance(const it_type first, const it_type last)
Definition plf_hive.h:107
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