Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qitemselectionmodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <private/qitemselectionmodel_p.h>
9#include <private/qabstractitemmodel_p.h>
10#include <private/qduplicatetracker_p.h>
11#include <private/qoffsetstringarray_p.h>
12#include <qdebug.h>
13
14#include <algorithm>
15#include <functional>
16
17QT_BEGIN_NAMESPACE
18
19QT_IMPL_METATYPE_EXTERN(QItemSelectionRange)
20QT_IMPL_METATYPE_EXTERN(QItemSelection)
21
22// a small helper to cache calls to QPMI functions as they are expensive
23struct QItemSelectionRangeRefCache
24{
25 QItemSelectionRangeRefCache(const QItemSelectionRange &range)
26 : m_range(range)
27 {}
28 inline void invalidate() { m_top = m_left = m_bottom = m_right = -1; }
29 inline bool contains(int row, int column, const QModelIndex &parentIndex)
30 {
31 populate();
32 return (m_bottom >= row && m_right >= column &&
33 m_top <= row && m_left <= column &&
34 parent() == parentIndex);
35 }
36 inline void populate()
37 {
38 if (m_top > -2)
39 return;
40 m_top = m_range.top();
41 m_left = m_range.left();
42 m_bottom = m_range.bottom();
43 m_right = m_range.right();
44 }
45 inline const QModelIndex &parent()
46 {
47 if (!m_parent)
48 m_parent = m_range.parent();
49 return *m_parent;
50 }
51 // we assume we're initialized for the next functions
52 inline int bottom() const { return m_bottom; }
53 inline int right() const { return m_right; }
54
55private:
56 int m_top = -2;
57 int m_left = 0;
58 int m_bottom = 0;
59 int m_right = 0;
60 std::optional<QModelIndex> m_parent;
61 const QItemSelectionRange &m_range;
62};
63
64/*!
65 \class QItemSelectionRange
66 \inmodule QtCore
67
68 \brief The QItemSelectionRange class manages information about a
69 range of selected items in a model.
70
71 \ingroup model-view
72
73 \compares equality
74
75 A QItemSelectionRange contains information about a range of
76 selected items in a model. A range of items is a contiguous array
77 of model items, extending to cover a number of adjacent rows and
78 columns with a common parent item; this can be visualized as a
79 two-dimensional block of cells in a table. A selection range has a
80 top(), left() a bottom(), right() and a parent().
81
82 The QItemSelectionRange class is one of the \l{Model/View Classes}
83 and is part of Qt's \l{Model/View Programming}{model/view framework}.
84
85 The model items contained in the selection range can be obtained
86 using the indexes() function. Use QItemSelectionModel::selectedIndexes()
87 to get a list of all selected items for a view.
88
89 You can determine whether a given model item lies within a
90 particular range by using the contains() function. Ranges can also
91 be compared using the overloaded operators for equality and
92 inequality, and the intersects() function allows you to determine
93 whether two ranges overlap.
94
95 \sa {Model/View Programming}, QAbstractItemModel, QItemSelection,
96 QItemSelectionModel
97*/
98
99/*!
100 \fn QItemSelectionRange::QItemSelectionRange()
101
102 Constructs an empty selection range.
103*/
104
105/*!
106 \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
107
108 Constructs a new selection range containing only the index specified
109 by the \a topLeft and the index \a bottomRight.
110
111*/
112
113/*!
114 \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &index)
115
116 Constructs a new selection range containing only the model item specified
117 by the model index \a index.
118*/
119
120/*!
121 \fn QItemSelectionRange::swap(QItemSelectionRange &other)
122 \since 5.6
123 \memberswap{selection range's contents}
124*/
125
126/*!
127 \fn int QItemSelectionRange::top() const
128
129 Returns the row index corresponding to the uppermost selected row in the
130 selection range.
131
132*/
133
134/*!
135 \fn int QItemSelectionRange::left() const
136
137 Returns the column index corresponding to the leftmost selected column in the
138 selection range.
139*/
140
141/*!
142 \fn int QItemSelectionRange::bottom() const
143
144 Returns the row index corresponding to the lowermost selected row in the
145 selection range.
146
147*/
148
149/*!
150 \fn int QItemSelectionRange::right() const
151
152 Returns the column index corresponding to the rightmost selected column in
153 the selection range.
154
155*/
156
157/*!
158 \fn int QItemSelectionRange::width() const
159
160 Returns the number of selected columns in the selection range.
161
162*/
163
164/*!
165 \fn int QItemSelectionRange::height() const
166
167 Returns the number of selected rows in the selection range.
168
169*/
170
171/*!
172 \fn const QAbstractItemModel *QItemSelectionRange::model() const
173
174 Returns the model that the items in the selection range belong to.
175*/
176
177/*!
178 \fn QModelIndex QItemSelectionRange::topLeft() const
179
180 Returns the index for the item located at the top-left corner of
181 the selection range.
182
183 \sa top(), left(), bottomRight()
184*/
185
186/*!
187 \fn QModelIndex QItemSelectionRange::bottomRight() const
188
189 Returns the index for the item located at the bottom-right corner
190 of the selection range.
191
192 \sa bottom(), right(), topLeft()
193*/
194
195/*!
196 \fn QModelIndex QItemSelectionRange::parent() const
197
198 Returns the parent model item index of the items in the selection range.
199
200*/
201
202/*!
203 \fn bool QItemSelectionRange::contains(const QModelIndex &index) const
204
205 Returns \c true if the model item specified by the \a index lies within the
206 range of selected items; otherwise returns \c false.
207*/
208
209/*!
210 \fn bool QItemSelectionRange::contains(int row, int column,
211 const QModelIndex &parentIndex) const
212 \overload
213
214 Returns \c true if the model item specified by (\a row, \a column)
215 and with \a parentIndex as the parent item lies within the range
216 of selected items; otherwise returns \c false.
217*/
218
219/*!
220 \fn bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const
221
222 Returns \c true if this selection range intersects (overlaps with) the \a other
223 range given; otherwise returns \c false.
224
225*/
226bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const
227{
228 // isValid() and parent() last since they are more expensive
229 if (model() != other.model())
230 return false;
231
232 if (top() > other.bottom() || bottom() < other.top())
233 return false;
234
235 if (left() > other.right() || right() < other.left())
236 return false;
237
238 if (parent() != other.parent())
239 return false;
240
241 return isValid() && other.isValid();
242}
243
244/*!
245 \fn QItemSelectionRange QItemSelectionRange::intersected(const QItemSelectionRange &other) const
246 \since 4.2
247
248 Returns a new selection range containing only the items that are found in
249 both the selection range and the \a other selection range.
250*/
251
252QItemSelectionRange QItemSelectionRange::intersected(const QItemSelectionRange &other) const
253{
254 if (model() == other.model() && parent() == other.parent()) {
255 QModelIndex topLeft = model()->index(qMax(top(), other.top()),
256 qMax(left(), other.left()),
257 other.parent());
258 QModelIndex bottomRight = model()->index(qMin(bottom(), other.bottom()),
259 qMin(right(), other.right()),
260 other.parent());
261 return QItemSelectionRange(topLeft, bottomRight);
262 }
263 return QItemSelectionRange();
264}
265
266/*!
267 \fn bool QItemSelectionRange::operator==(const QItemSelectionRange &lhs, const QItemSelectionRange &rhs)
268
269 Returns \c true if \a lhs selection range is exactly the same as the \a rhs
270 range given; otherwise returns \c false.
271
272*/
273
274/*!
275 \fn bool QItemSelectionRange::operator!=(const QItemSelectionRange &lhs, const QItemSelectionRange &rhs)
276
277 Returns \c true if \a lhs selection range differs from the \a rhs range given;
278 otherwise returns \c false.
279
280*/
281
282/*!
283 \fn bool QItemSelectionRange::isValid() const
284
285 Returns \c true if the selection range is valid; otherwise returns \c false.
286
287*/
288
289static void rowLengthsFromRange(const QItemSelectionRange &range, QList<std::pair<QPersistentModelIndex, uint>> &result)
290{
291 if (range.isValid() && range.model()) {
292 const QModelIndex topLeft = range.topLeft();
293 const int bottom = range.bottom();
294 const uint width = range.width();
295 const int column = topLeft.column();
296 for (int row = topLeft.row(); row <= bottom; ++row) {
297 // We don't need to keep track of ItemIsSelectable and ItemIsEnabled here. That is
298 // required in indexesFromRange() because that method is called from public API
299 // which requires the limitation.
300 result.emplace_back(topLeft.sibling(row, column), width);
301 }
302 }
303}
304
305static inline bool isSelectableAndEnabled(Qt::ItemFlags flags)
306{
307 return flags.testFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
308}
309
310template<typename ModelIndexContainer>
311static void indexesFromRange(const QItemSelectionRange &range, ModelIndexContainer &result)
312{
313 if (range.isValid() && range.model()) {
314 const QModelIndex topLeft = range.topLeft();
315 const int bottom = range.bottom();
316 const int right = range.right();
317 for (int row = topLeft.row(); row <= bottom; ++row) {
318 const QModelIndex columnLeader = topLeft.sibling(row, topLeft.column());
319 for (int column = topLeft.column(); column <= right; ++column) {
320 QModelIndex index = columnLeader.sibling(row, column);
321 if (isSelectableAndEnabled(range.model()->flags(index)))
322 result.push_back(index);
323 }
324 }
325 }
326}
327
328template<typename ModelIndexContainer>
329static ModelIndexContainer qSelectionIndexes(const QItemSelection &selection)
330{
331 ModelIndexContainer result;
332 for (const auto &range : selection)
333 indexesFromRange(range, result);
334 return result;
335}
336
337/*!
338 Returns \c true if the selection range contains either no items
339 or only items which are either disabled or marked as not selectable.
340
341 \since 4.7
342*/
343
344bool QItemSelectionRange::isEmpty() const
345{
346 if (!isValid() || !model())
347 return true;
348
349 for (int column = left(); column <= right(); ++column) {
350 for (int row = top(); row <= bottom(); ++row) {
351 QModelIndex index = model()->index(row, column, parent());
352 if (isSelectableAndEnabled(model()->flags(index)))
353 return false;
354 }
355 }
356 return true;
357}
358
359/*!
360 Returns the list of model index items stored in the selection.
361*/
362
363QModelIndexList QItemSelectionRange::indexes() const
364{
365 QModelIndexList result;
366 indexesFromRange(*this, result);
367 return result;
368}
369
370/*!
371 \class QItemSelection
372 \inmodule QtCore
373
374 \brief The QItemSelection class manages information about selected items in a model.
375
376 \ingroup model-view
377
378 A QItemSelection describes the items in a model that have been
379 selected by the user. A QItemSelection is basically a list of
380 selection ranges, see QItemSelectionRange. It provides functions for
381 creating and manipulating selections, and selecting a range of items
382 from a model.
383
384 The QItemSelection class is one of the \l{Model/View Classes}
385 and is part of Qt's \l{Model/View Programming}{model/view framework}.
386
387 An item selection can be constructed and initialized to contain a
388 range of items from an existing model. The following example constructs
389 a selection that contains a range of items from the given \c model,
390 beginning at the \c topLeft, and ending at the \c bottomRight.
391
392 \snippet code/src_gui_itemviews_qitemselectionmodel.cpp 0
393
394 An empty item selection can be constructed, and later populated as
395 required. So, if the model is going to be unavailable when we construct
396 the item selection, we can rewrite the above code in the following way:
397
398 \snippet code/src_gui_itemviews_qitemselectionmodel.cpp 1
399
400 QItemSelection saves memory, and avoids unnecessary work, by working with
401 selection ranges rather than recording the model item index for each
402 item in the selection. Generally, an instance of this class will contain
403 a list of non-overlapping selection ranges.
404
405 Use merge() to merge one item selection into another without making
406 overlapping ranges. Use split() to split one selection range into
407 smaller ranges based on a another selection range.
408
409 \sa {Model/View Programming}, QItemSelectionModel
410*/
411
412/*!
413 \fn QItemSelection::QItemSelection()
414
415 Constructs an empty selection.
416*/
417
418/*!
419 Constructs an item selection that extends from the top-left model item,
420 specified by the \a topLeft index, to the bottom-right item, specified
421 by \a bottomRight.
422*/
423QItemSelection::QItemSelection(const QModelIndex &topLeft, const QModelIndex &bottomRight)
424{
425 select(topLeft, bottomRight);
426}
427
428/*!
429 Adds the items in the range that extends from the top-left model
430 item, specified by the \a topLeft index, to the bottom-right item,
431 specified by \a bottomRight to the list.
432
433 \note \a topLeft and \a bottomRight must have the same parent.
434*/
435void QItemSelection::select(const QModelIndex &topLeft, const QModelIndex &bottomRight)
436{
437 if (!topLeft.isValid() || !bottomRight.isValid())
438 return;
439
440 if ((topLeft.model() != bottomRight.model())
441 || topLeft.parent() != bottomRight.parent()) {
442 qWarning("Can't select indexes from different model or with different parents");
443 return;
444 }
445 if (topLeft.row() > bottomRight.row() || topLeft.column() > bottomRight.column()) {
446 int top = qMin(topLeft.row(), bottomRight.row());
447 int bottom = qMax(topLeft.row(), bottomRight.row());
448 int left = qMin(topLeft.column(), bottomRight.column());
449 int right = qMax(topLeft.column(), bottomRight.column());
450 QModelIndex tl = topLeft.sibling(top, left);
451 QModelIndex br = bottomRight.sibling(bottom, right);
452 append(QItemSelectionRange(tl, br));
453 return;
454 }
455 append(QItemSelectionRange(topLeft, bottomRight));
456}
457
458/*!
459 Returns \c true if the selection contains the given \a index; otherwise
460 returns \c false.
461*/
462
463bool QItemSelection::contains(const QModelIndex &index) const
464{
465 if (isSelectableAndEnabled(index.flags()))
466 return std::any_of(begin(), end(), [&](const auto &range) { return range.contains(index); });
467 return false;
468}
469
470/*!
471 Returns a list of model indexes that correspond to the selected items.
472*/
473
474QModelIndexList QItemSelection::indexes() const
475{
476 return qSelectionIndexes<QModelIndexList>(*this);
477}
478
480{
481 QList<std::pair<QPersistentModelIndex, uint>> result;
482 for (const QItemSelectionRange &range : sel)
483 rowLengthsFromRange(range, result);
484 return result;
485}
486
487/*!
488 Merges the \a other selection with this QItemSelection using the
489 \a command given. This method guarantees that no ranges are overlapping.
490
491 Note that only QItemSelectionModel::Select,
492 QItemSelectionModel::Deselect, and QItemSelectionModel::Toggle are
493 supported.
494
495 \sa split()
496*/
497void QItemSelection::merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command)
498{
499 if (other.isEmpty() ||
500 !(command & QItemSelectionModel::Select ||
501 command & QItemSelectionModel::Deselect ||
502 command & QItemSelectionModel::Toggle))
503 return;
504
505 QItemSelection newSelection;
506 newSelection.reserve(other.size());
507 // Collect intersections
508 QItemSelection intersections;
509 for (const auto &range : other) {
510 if (!range.isValid())
511 continue;
512 newSelection.push_back(range);
513 for (int t = 0; t < size(); ++t) {
514 if (range.intersects(at(t)))
515 intersections.append(at(t).intersected(range));
516 }
517 }
518
519 // Split the old (and new) ranges using the intersections
520 for (int i = 0; i < intersections.size(); ++i) { // for each intersection
521 for (int t = 0; t < size();) { // splitt each old range
522 if (at(t).intersects(intersections.at(i))) {
523 split(at(t), intersections.at(i), this);
524 removeAt(t);
525 } else {
526 ++t;
527 }
528 }
529 // only split newSelection if Toggle is specified
530 for (int n = 0; (command & QItemSelectionModel::Toggle) && n < newSelection.size();) {
531 if (newSelection.at(n).intersects(intersections.at(i))) {
532 split(newSelection.at(n), intersections.at(i), &newSelection);
533 newSelection.removeAt(n);
534 } else {
535 ++n;
536 }
537 }
538 }
539 // do not add newSelection for Deselect
540 if (!(command & QItemSelectionModel::Deselect))
541 operator+=(newSelection);
542}
543
544/*!
545 Splits the selection \a range using the selection \a other range.
546 Removes all items in \a other from \a range and puts the result in \a result.
547 This can be compared with the semantics of the \e subtract operation of a set.
548 \sa merge()
549*/
550
551void QItemSelection::split(const QItemSelectionRange &range,
552 const QItemSelectionRange &other, QItemSelection *result)
553{
554 if (range.parent() != other.parent() || range.model() != other.model())
555 return;
556
557 QModelIndex parent = other.parent();
558 int top = range.top();
559 int left = range.left();
560 int bottom = range.bottom();
561 int right = range.right();
562 int other_top = other.top();
563 int other_left = other.left();
564 int other_bottom = other.bottom();
565 int other_right = other.right();
566 const QAbstractItemModel *model = range.model();
567 Q_ASSERT(model);
568 if (other_top > top) {
569 QModelIndex tl = model->index(top, left, parent);
570 QModelIndex br = model->index(other_top - 1, right, parent);
571 result->append(QItemSelectionRange(tl, br));
572 top = other_top;
573 }
574 if (other_bottom < bottom) {
575 QModelIndex tl = model->index(other_bottom + 1, left, parent);
576 QModelIndex br = model->index(bottom, right, parent);
577 result->append(QItemSelectionRange(tl, br));
578 bottom = other_bottom;
579 }
580 if (other_left > left) {
581 QModelIndex tl = model->index(top, left, parent);
582 QModelIndex br = model->index(bottom, other_left - 1, parent);
583 result->append(QItemSelectionRange(tl, br));
584 left = other_left;
585 }
586 if (other_right < right) {
587 QModelIndex tl = model->index(top, other_right + 1, parent);
588 QModelIndex br = model->index(bottom, right, parent);
589 result->append(QItemSelectionRange(tl, br));
590 right = other_right;
591 }
592}
593
594QItemSelectionModelPrivate::~QItemSelectionModelPrivate()
595 = default;
596
597void QItemSelectionModelPrivate::initModel(QAbstractItemModel *m)
598{
599 Q_Q(QItemSelectionModel);
600 const QAbstractItemModel *oldModel = model.valueBypassingBindings();
601 if (oldModel == m)
602 return;
603
604 if (oldModel) {
605 q->reset();
606 disconnectModel();
607 }
608
609 // Caller has to call notify(), unless calling during construction (the common case).
610 model.setValueBypassingBindings(m);
611
612 if (m) {
613 connections = std::array<QMetaObject::Connection, 12> {
614 QObjectPrivate::connect(m, &QAbstractItemModel::rowsAboutToBeRemoved,
615 this, &QItemSelectionModelPrivate::rowsAboutToBeRemoved),
616 QObjectPrivate::connect(m, &QAbstractItemModel::columnsAboutToBeRemoved,
617 this, &QItemSelectionModelPrivate::columnsAboutToBeRemoved),
618 QObjectPrivate::connect(m, &QAbstractItemModel::rowsAboutToBeInserted,
619 this, &QItemSelectionModelPrivate::rowsAboutToBeInserted),
620 QObjectPrivate::connect(m, &QAbstractItemModel::columnsAboutToBeInserted,
621 this, &QItemSelectionModelPrivate::columnsAboutToBeInserted),
622 QObjectPrivate::connect(m, &QAbstractItemModel::rowsAboutToBeMoved,
623 this, &QItemSelectionModelPrivate::triggerLayoutToBeChanged),
624 QObjectPrivate::connect(m, &QAbstractItemModel::columnsAboutToBeMoved,
625 this, &QItemSelectionModelPrivate::triggerLayoutToBeChanged),
626 QObjectPrivate::connect(m, &QAbstractItemModel::rowsMoved,
627 this, &QItemSelectionModelPrivate::triggerLayoutChanged),
628 QObjectPrivate::connect(m, &QAbstractItemModel::columnsMoved,
629 this, &QItemSelectionModelPrivate::triggerLayoutChanged),
630 QObjectPrivate::connect(m, &QAbstractItemModel::layoutAboutToBeChanged,
631 this, &QItemSelectionModelPrivate::layoutAboutToBeChanged),
632 QObjectPrivate::connect(m, &QAbstractItemModel::layoutChanged,
633 this, &QItemSelectionModelPrivate::layoutChanged),
634 QObject::connect(m, &QAbstractItemModel::modelReset,
635 q, &QItemSelectionModel::reset),
636 QObjectPrivate::connect(m, &QAbstractItemModel::destroyed,
637 this, &QItemSelectionModelPrivate::modelDestroyed)
638 };
639 }
640}
641
642void QItemSelectionModelPrivate::disconnectModel()
643{
644 for (auto &connection : connections)
645 QObject::disconnect(connection);
646}
647
648/*!
649 \internal
650
651 returns a QItemSelection where all ranges have been expanded to:
652 Rows: left: 0 and right: columnCount()-1
653 Columns: top: 0 and bottom: rowCount()-1
654*/
655
656QItemSelection QItemSelectionModelPrivate::expandSelection(const QItemSelection &selection,
657 QItemSelectionModel::SelectionFlags command) const
658{
659 if (selection.isEmpty() && !((command & QItemSelectionModel::Rows) ||
660 (command & QItemSelectionModel::Columns)))
661 return selection;
662
663 QItemSelection expanded;
664 if (command & QItemSelectionModel::Rows) {
665 for (int i = 0; i < selection.size(); ++i) {
666 QModelIndex parent = selection.at(i).parent();
667 int colCount = model->columnCount(parent);
668 QModelIndex tl = model->index(selection.at(i).top(), 0, parent);
669 QModelIndex br = model->index(selection.at(i).bottom(), colCount - 1, parent);
670 //we need to merge because the same row could have already been inserted
671 expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select);
672 }
673 }
674 if (command & QItemSelectionModel::Columns) {
675 for (int i = 0; i < selection.size(); ++i) {
676 QModelIndex parent = selection.at(i).parent();
677 int rowCount = model->rowCount(parent);
678 QModelIndex tl = model->index(0, selection.at(i).left(), parent);
679 QModelIndex br = model->index(rowCount - 1, selection.at(i).right(), parent);
680 //we need to merge because the same column could have already been inserted
681 expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select);
682 }
683 }
684 return expanded;
685}
686
687/*!
688 \internal
689*/
690void QItemSelectionModelPrivate::rowsAboutToBeRemoved(const QModelIndex &parent,
691 int start, int end)
692{
693 Q_Q(QItemSelectionModel);
694 Q_ASSERT(start <= end);
695 finalize();
696
697 // update current index
698 if (currentIndex.isValid() && parent == currentIndex.parent()
699 && currentIndex.row() >= start && currentIndex.row() <= end) {
700 QModelIndex old = currentIndex;
701 if (start > 0) {
702 // there are rows left above the change
703 currentIndex = model->index(start - 1, old.column(), parent);
704 } else if (model.value() && end < model->rowCount(parent) - 1) {
705 // there are rows left below the change
706 currentIndex = model->index(end + 1, old.column(), parent);
707 } else {
708 // there are no rows left in the table
709 currentIndex = QModelIndex();
710 }
711 emit q->currentChanged(currentIndex, old);
712 emit q->currentRowChanged(currentIndex, old);
713 if (currentIndex.column() != old.column())
714 emit q->currentColumnChanged(currentIndex, old);
715 }
716
717 QItemSelection deselected;
718 QItemSelection newParts;
719 bool indexesOfSelectionChanged = false;
720 QItemSelection::iterator it = ranges.begin();
721 while (it != ranges.end()) {
722 if (it->topLeft().parent() != parent) { // Check parents until reaching root or contained in range
723 QModelIndex itParent = it->topLeft().parent();
724 while (itParent.isValid() && itParent.parent() != parent)
725 itParent = itParent.parent();
726
727 if (itParent.isValid() && start <= itParent.row() && itParent.row() <= end) {
728 deselected.append(*it);
729 it = ranges.erase(it);
730 } else {
731 if (itParent.isValid() && end < itParent.row())
732 indexesOfSelectionChanged = true;
733 ++it;
734 }
735 } else if (start <= it->bottom() && it->bottom() <= end // Full inclusion
736 && start <= it->top() && it->top() <= end) {
737 deselected.append(*it);
738 it = ranges.erase(it);
739 } else if (start <= it->top() && it->top() <= end) { // Top intersection
740 deselected.append(QItemSelectionRange(it->topLeft(), model->index(end, it->right(), it->parent())));
741 *it = QItemSelectionRange(model->index(end + 1, it->left(), it->parent()), it->bottomRight());
742 ++it;
743 } else if (start <= it->bottom() && it->bottom() <= end) { // Bottom intersection
744 deselected.append(QItemSelectionRange(model->index(start, it->left(), it->parent()), it->bottomRight()));
745 *it = QItemSelectionRange(it->topLeft(), model->index(start - 1, it->right(), it->parent()));
746 ++it;
747 } else if (it->top() < start && end < it->bottom()) { // Middle intersection
748 // If the parent contains (1, 2, 3, 4, 5, 6, 7, 8) and [3, 4, 5, 6] is selected,
749 // and [4, 5] is removed, we need to split [3, 4, 5, 6] into [3], [4, 5] and [6].
750 // [4, 5] is appended to deselected, and [3] and [6] remain part of the selection
751 // in ranges.
752 const QItemSelectionRange removedRange(model->index(start, it->left(), it->parent()),
753 model->index(end, it->right(), it->parent()));
754 deselected.append(removedRange);
755 QItemSelection::split(*it, removedRange, &newParts);
756 it = ranges.erase(it);
757 } else if (end < it->top()) { // deleted row before selection
758 indexesOfSelectionChanged = true;
759 ++it;
760 } else {
761 ++it;
762 }
763 }
764 ranges.append(newParts);
765
766 if (!deselected.isEmpty() || indexesOfSelectionChanged)
767 emit q->selectionChanged(QItemSelection(), deselected);
768}
769
770/*!
771 \internal
772*/
773void QItemSelectionModelPrivate::columnsAboutToBeRemoved(const QModelIndex &parent,
774 int start, int end)
775{
776 Q_Q(QItemSelectionModel);
777
778 // update current index
779 if (currentIndex.isValid() && parent == currentIndex.parent()
780 && currentIndex.column() >= start && currentIndex.column() <= end) {
781 QModelIndex old = currentIndex;
782 if (start > 0) {
783 // there are columns to the left of the change
784 currentIndex = model->index(old.row(), start - 1, parent);
785 } else if (model.value() && end < model->columnCount() - 1) {
786 // there are columns to the right of the change
787 currentIndex = model->index(old.row(), end + 1, parent);
788 } else {
789 // there are no columns left in the table
790 currentIndex = QModelIndex();
791 }
792 emit q->currentChanged(currentIndex, old);
793 if (currentIndex.row() != old.row())
794 emit q->currentRowChanged(currentIndex, old);
795 emit q->currentColumnChanged(currentIndex, old);
796 }
797
798 // update selections
799 QModelIndex tl = model->index(0, start, parent);
800 QModelIndex br = model->index(model->rowCount(parent) - 1, end, parent);
801 q->select(QItemSelection(tl, br), QItemSelectionModel::Deselect);
802 finalize();
803}
804
805/*!
806 \internal
807
808 Split selection ranges if columns are about to be inserted in the middle.
809*/
810void QItemSelectionModelPrivate::columnsAboutToBeInserted(const QModelIndex &parent,
811 int start, int end)
812{
813 Q_UNUSED(end);
814 finalize();
815 QList<QItemSelectionRange> split;
816 QList<QItemSelectionRange>::iterator it = ranges.begin();
817 for (; it != ranges.end(); ) {
818 const QModelIndex &itParent = it->parent();
819 if ((*it).isValid() && itParent == parent
820 && (*it).left() < start && (*it).right() >= start) {
821 QModelIndex bottomMiddle = model->index((*it).bottom(), start - 1, itParent);
822 QItemSelectionRange left((*it).topLeft(), bottomMiddle);
823 QModelIndex topMiddle = model->index((*it).top(), start, itParent);
824 QItemSelectionRange right(topMiddle, (*it).bottomRight());
825 it = ranges.erase(it);
826 split.append(left);
827 split.append(right);
828 } else {
829 ++it;
830 }
831 }
832 ranges += split;
833}
834
835/*!
836 \internal
837
838 Split selection ranges if rows are about to be inserted in the middle.
839*/
840void QItemSelectionModelPrivate::rowsAboutToBeInserted(const QModelIndex &parent,
841 int start, int end)
842{
843 Q_Q(QItemSelectionModel);
844 Q_UNUSED(end);
845 finalize();
846 QList<QItemSelectionRange> split;
847 QList<QItemSelectionRange>::iterator it = ranges.begin();
848 bool indexesOfSelectionChanged = false;
849 for (; it != ranges.end(); ) {
850 const QModelIndex &itParent = it->parent();
851 if ((*it).isValid() && itParent == parent
852 && (*it).top() < start && (*it).bottom() >= start) {
853 QModelIndex middleRight = model->index(start - 1, (*it).right(), itParent);
854 QItemSelectionRange top((*it).topLeft(), middleRight);
855 QModelIndex middleLeft = model->index(start, (*it).left(), itParent);
856 QItemSelectionRange bottom(middleLeft, (*it).bottomRight());
857 it = ranges.erase(it);
858 split.append(top);
859 split.append(bottom);
860 } else if ((*it).isValid() && itParent == parent // insertion before selection
861 && (*it).top() >= start) {
862 indexesOfSelectionChanged = true;
863 ++it;
864 } else {
865 ++it;
866 }
867 }
868 ranges += split;
869
870 if (indexesOfSelectionChanged)
871 emit q->selectionChanged(QItemSelection(), QItemSelection());
872}
873
874/*!
875 \internal
876
877 Split selection into individual (persistent) indexes. This is done in
878 preparation for the layoutChanged() signal, where the indexes can be
879 merged again.
880*/
881void QItemSelectionModelPrivate::layoutAboutToBeChanged(const QList<QPersistentModelIndex> &,
882 QAbstractItemModel::LayoutChangeHint hint)
883{
884 savedPersistentIndexes.clear();
885 savedPersistentCurrentIndexes.clear();
886 savedPersistentRowLengths.clear();
887 savedPersistentCurrentRowLengths.clear();
888
889 // optimization for when all indexes are selected
890 // (only if there is lots of items (1000) because this is not entirely correct)
891 if (ranges.isEmpty() && currentSelection.size() == 1) {
892 QItemSelectionRange range = currentSelection.constFirst();
893 QModelIndex parent = range.parent();
894 tableRowCount = model->rowCount(parent);
895 tableColCount = model->columnCount(parent);
896 if (tableRowCount * tableColCount > 1000
897 && range.top() == 0
898 && range.left() == 0
899 && range.bottom() == tableRowCount - 1
900 && range.right() == tableColCount - 1) {
901 tableSelected = true;
902 tableParent = parent;
903 return;
904 }
905 }
906 tableSelected = false;
907
908 if (hint == QAbstractItemModel::VerticalSortHint) {
909 // Special case when we know we're sorting vertically. We can assume that all indexes for columns
910 // are displaced the same way, and therefore we only need to track an index from one column per
911 // row with a QPersistentModelIndex together with the length of items to the right of it
912 // which are displaced the same way.
913 // An algorithm which contains the same assumption is used to process layoutChanged.
914 savedPersistentRowLengths = qSelectionPersistentRowLengths(ranges);
915 savedPersistentCurrentRowLengths = qSelectionPersistentRowLengths(currentSelection);
916 } else {
917 savedPersistentIndexes = qSelectionIndexes<QList<QPersistentModelIndex>>(ranges);
918 savedPersistentCurrentIndexes = qSelectionIndexes<QList<QPersistentModelIndex>>(currentSelection);
919 }
920}
921/*!
922 \internal
923*/
924static QItemSelection mergeRowLengths(const QList<std::pair<QPersistentModelIndex, uint>> &rowLengths)
925{
926 if (rowLengths.isEmpty())
927 return QItemSelection();
928
929 QItemSelection result;
930 int i = 0;
931 while (i < rowLengths.size()) {
932 const QPersistentModelIndex &tl = rowLengths.at(i).first;
933 if (!tl.isValid()) {
934 ++i;
935 continue;
936 }
937 QPersistentModelIndex br = tl;
938 const uint length = rowLengths.at(i).second;
939 while (++i < rowLengths.size()) {
940 const QPersistentModelIndex &next = rowLengths.at(i).first;
941 if (!next.isValid())
942 continue;
943 const uint nextLength = rowLengths.at(i).second;
944 if ((nextLength == length)
945 && (next.row() == br.row() + 1)
946 && (next.column() == br.column())
947 && (next.parent() == br.parent())) {
948 br = next;
949 } else {
950 break;
951 }
952 }
953 result.append(QItemSelectionRange(tl, br.sibling(br.row(), br.column() + length - 1)));
954 }
955 return result;
956}
957
958/*!
959 \internal
960
961 Merges \a indexes into an item selection made up of ranges.
962 Assumes that the indexes are sorted.
963*/
964static QItemSelection mergeIndexes(const QList<QPersistentModelIndex> &indexes)
965{
966 QItemSelection colSpans;
967 // merge columns
968 int i = 0;
969 while (i < indexes.size()) {
970 const QPersistentModelIndex &tl = indexes.at(i);
971 if (!tl.isValid()) {
972 ++i;
973 continue;
974 }
975 QPersistentModelIndex br = tl;
976 QModelIndex brParent = br.parent();
977 int brRow = br.row();
978 int brColumn = br.column();
979 while (++i < indexes.size()) {
980 const QPersistentModelIndex &next = indexes.at(i);
981 if (!next.isValid())
982 continue;
983 const QModelIndex nextParent = next.parent();
984 const int nextRow = next.row();
985 const int nextColumn = next.column();
986 if ((nextParent == brParent)
987 && (nextRow == brRow)
988 && (nextColumn == brColumn + 1)) {
989 br = next;
990 brParent = nextParent;
991 brRow = nextRow;
992 brColumn = nextColumn;
993 } else {
994 break;
995 }
996 }
997 colSpans.append(QItemSelectionRange(tl, br));
998 }
999 // merge rows
1000 QItemSelection rowSpans;
1001 i = 0;
1002 while (i < colSpans.size()) {
1003 QModelIndex tl = colSpans.at(i).topLeft();
1004 QModelIndex br = colSpans.at(i).bottomRight();
1005 QModelIndex prevTl = tl;
1006 while (++i < colSpans.size()) {
1007 QModelIndex nextTl = colSpans.at(i).topLeft();
1008 QModelIndex nextBr = colSpans.at(i).bottomRight();
1009
1010 if (nextTl.parent() != tl.parent())
1011 break; // we can't merge selection ranges from different parents
1012
1013 if ((nextTl.column() == prevTl.column()) && (nextBr.column() == br.column())
1014 && (nextTl.row() == prevTl.row() + 1) && (nextBr.row() == br.row() + 1)) {
1015 br = nextBr;
1016 prevTl = nextTl;
1017 } else {
1018 break;
1019 }
1020 }
1021 rowSpans.append(QItemSelectionRange(tl, br));
1022 }
1023 return rowSpans;
1024}
1025
1026/*!
1027 \internal
1028
1029 Sort predicate function for QItemSelectionModelPrivate::layoutChanged(),
1030 sorting by parent first in addition to operator<(). This is to prevent
1031 fragmentation of the selection by grouping indexes with the same row, column
1032 of different parents next to each other, which may happen when a selection
1033 spans sub-trees.
1034*/
1035static bool qt_PersistentModelIndexLessThan(const QPersistentModelIndex &i1, const QPersistentModelIndex &i2)
1036{
1037 const QModelIndex parent1 = i1.parent();
1038 const QModelIndex parent2 = i2.parent();
1039 return parent1 == parent2 ? i1 < i2 : parent1 < parent2;
1040}
1041
1042/*!
1043 \internal
1044
1045 Merge the selected indexes into selection ranges again.
1046*/
1047void QItemSelectionModelPrivate::layoutChanged(const QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint hint)
1048{
1049 // special case for when all indexes are selected
1050 if (tableSelected && tableColCount == model->columnCount(tableParent)
1051 && tableRowCount == model->rowCount(tableParent)) {
1052 ranges.clear();
1053 currentSelection.clear();
1054 int bottom = tableRowCount - 1;
1055 int right = tableColCount - 1;
1056 QModelIndex tl = model->index(0, 0, tableParent);
1057 QModelIndex br = model->index(bottom, right, tableParent);
1058 currentSelection << QItemSelectionRange(tl, br);
1059 tableParent = QModelIndex();
1060 tableSelected = false;
1061 return;
1062 }
1063
1064 if ((hint != QAbstractItemModel::VerticalSortHint && savedPersistentCurrentIndexes.isEmpty() && savedPersistentIndexes.isEmpty())
1065 || (hint == QAbstractItemModel::VerticalSortHint && savedPersistentRowLengths.isEmpty() && savedPersistentCurrentRowLengths.isEmpty())) {
1066 // either the selection was actually empty, or we
1067 // didn't get the layoutAboutToBeChanged() signal
1068 return;
1069 }
1070
1071 // clear the "old" selection
1072 ranges.clear();
1073 currentSelection.clear();
1074
1075 if (hint != QAbstractItemModel::VerticalSortHint) {
1076 // sort the "new" selection, as preparation for merging
1077 std::stable_sort(savedPersistentIndexes.begin(), savedPersistentIndexes.end(),
1078 qt_PersistentModelIndexLessThan);
1079 std::stable_sort(savedPersistentCurrentIndexes.begin(), savedPersistentCurrentIndexes.end(),
1080 qt_PersistentModelIndexLessThan);
1081
1082 // update the selection by merging the individual indexes
1083 ranges = mergeIndexes(savedPersistentIndexes);
1084 currentSelection = mergeIndexes(savedPersistentCurrentIndexes);
1085
1086 // release the persistent indexes
1087 savedPersistentIndexes.clear();
1088 savedPersistentCurrentIndexes.clear();
1089 } else {
1090 // sort the "new" selection, as preparation for merging
1091 std::stable_sort(savedPersistentRowLengths.begin(), savedPersistentRowLengths.end());
1092 std::stable_sort(savedPersistentCurrentRowLengths.begin(), savedPersistentCurrentRowLengths.end());
1093
1094 // update the selection by merging the individual indexes
1095 ranges = mergeRowLengths(savedPersistentRowLengths);
1096 currentSelection = mergeRowLengths(savedPersistentCurrentRowLengths);
1097
1098 // release the persistent indexes
1099 savedPersistentRowLengths.clear();
1100 savedPersistentCurrentRowLengths.clear();
1101 }
1102}
1103
1104/*!
1105 \internal
1106
1107 Called when the used model gets destroyed.
1108
1109 It is impossible to have a correct implementation here.
1110 In the following situation, there are two contradicting rules:
1111
1112 \code
1113 QProperty<QAbstractItemModel *> leader(mymodel);
1114 QItemSelectionModel myItemSelectionModel;
1115 myItemSelectionModel.bindableModel().setBinding([&](){ return leader.value(); }
1116 delete mymodel;
1117 QAbstractItemModel *returnedModel = myItemSelectionModel.model();
1118 \endcode
1119
1120 What should returnedModel be in this situation?
1121
1122 Rules for bindable properties say that myItemSelectionModel.model()
1123 should return the same as leader.value(), namely the pointer to the now deleted model.
1124
1125 However, backward compatibility requires myItemSelectionModel.model() to return a
1126 nullptr, because that was done in the past after the model used was deleted.
1127
1128 We decide to break the new rule, imposed by bindable properties, and not break the old
1129 rule, because that may break existing code.
1130*/
1131void QItemSelectionModelPrivate::modelDestroyed()
1132{
1133 model.setValueBypassingBindings(nullptr);
1134 disconnectModel();
1135 model.notify();
1136}
1137
1138/*!
1139 \class QItemSelectionModel
1140 \inmodule QtCore
1141
1142 \brief The QItemSelectionModel class keeps track of a view's selected items.
1143
1144 \ingroup model-view
1145
1146 A QItemSelectionModel keeps track of the selected items in a view, or
1147 in several views onto the same model. It also keeps track of the
1148 currently selected item in a view.
1149
1150 The QItemSelectionModel class is one of the \l{Model/View Classes}
1151 and is part of Qt's \l{Model/View Programming}{model/view framework}.
1152
1153 The selected items are stored using ranges. Whenever you want to
1154 modify the selected items use select() and provide either a
1155 QItemSelection, or a QModelIndex and a QItemSelectionModel::SelectionFlag.
1156
1157 The QItemSelectionModel takes a two layer approach to selection
1158 management, dealing with both selected items that have been committed
1159 and items that are part of the current selection. The current
1160 selected items are part of the current interactive selection (for
1161 example with rubber-band selection or keyboard-shift selections).
1162
1163 To update the currently selected items, use the bitwise OR of
1164 QItemSelectionModel::Current and any of the other SelectionFlags.
1165 If you omit the QItemSelectionModel::Current command, a new current
1166 selection will be created, and the previous one added to the whole
1167 selection. All functions operate on both layers; for example,
1168 \l {QTableWidget::selectedItems()}{selecteditems()} will return items from both layers.
1169
1170 \note Since 5.5, \l{QItemSelectionModel::model()}{model},
1171 \l{QItemSelectionModel::hasSelection()}{hasSelection}, and
1172 \l{QItemSelectionModel::currentIndex()}{currentIndex} are meta-object properties.
1173
1174 \sa {Model/View Programming}, QAbstractItemModel
1175*/
1176
1177/*!
1178 Constructs a selection model that operates on the specified item \a model.
1179*/
1180QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model)
1181 : QObject(*new QItemSelectionModelPrivate, model)
1182{
1183 d_func()->initModel(model);
1184}
1185
1186/*!
1187 Constructs a selection model that operates on the specified item \a model with \a parent.
1188*/
1189QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model, QObject *parent)
1190 : QObject(*new QItemSelectionModelPrivate, parent)
1191{
1192 d_func()->initModel(model);
1193}
1194
1195/*!
1196 \internal
1197*/
1198QItemSelectionModel::QItemSelectionModel(QItemSelectionModelPrivate &dd, QAbstractItemModel *model)
1199 : QObject(dd, model)
1200{
1201 dd.initModel(model);
1202}
1203
1204/*!
1205 Destroys the selection model.
1206*/
1207QItemSelectionModel::~QItemSelectionModel()
1208{
1209}
1210
1211/*!
1212 Selects the model item \a index using the specified \a command, and emits
1213 selectionChanged().
1214
1215 \sa QItemSelectionModel::SelectionFlags
1216*/
1217void QItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
1218{
1219 QItemSelection selection(index, index);
1220 select(selection, command);
1221}
1222
1223/*!
1224 \fn void QItemSelectionModel::currentChanged(const QModelIndex &current, const QModelIndex &previous)
1225
1226 This signal is emitted whenever the current item changes. The \a previous
1227 model item index is replaced by the \a current index as the selection's
1228 current item.
1229
1230 Note that this signal will not be emitted when the item model is reset.
1231
1232 \sa currentIndex(), setCurrentIndex(), selectionChanged()
1233*/
1234
1235/*!
1236 \fn void QItemSelectionModel::currentColumnChanged(const QModelIndex &current, const QModelIndex &previous)
1237
1238 This signal is emitted if the \a current item changes and its column is
1239 different to the column of the \a previous current item.
1240
1241 Note that this signal will not be emitted when the item model is reset.
1242
1243 \sa currentChanged(), currentRowChanged(), currentIndex(), setCurrentIndex()
1244*/
1245
1246/*!
1247 \fn void QItemSelectionModel::currentRowChanged(const QModelIndex &current, const QModelIndex &previous)
1248
1249 This signal is emitted if the \a current item changes and its row is
1250 different to the row of the \a previous current item.
1251
1252 Note that this signal will not be emitted when the item model is reset.
1253
1254 \sa currentChanged(), currentColumnChanged(), currentIndex(), setCurrentIndex()
1255*/
1256
1257/*!
1258 \fn void QItemSelectionModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
1259
1260 This signal is emitted whenever the selection changes. The change in the
1261 selection is represented as an item selection of \a deselected items and
1262 an item selection of \a selected items.
1263
1264 Note the that the current index changes independently from the selection.
1265 Also note that this signal will not be emitted when the item model is reset.
1266
1267 Items which stay selected but change their index are not included in
1268 \a selected and \a deselected. Thus, this signal might be emitted with both
1269 \a selected and \a deselected empty, if only the indices of selected items
1270 change.
1271
1272 \note It is not permitted to modify the model (e.g., by calling setData()) from
1273 within a slot connected directly to this signal. This signal may be emitted while
1274 the model is in the process of being modified, for example during row or column
1275 removal, or a model reset. Attempting to perform additional changes at such times
1276 can lead to undefined behavior. In particular, nested modifications can corrupt
1277 internal state, such as the mapping structures maintained by QSortFilterProxyModel.
1278
1279 \sa select(), currentChanged()
1280*/
1281
1282/*!
1283 \fn void QItemSelectionModel::modelChanged(QAbstractItemModel *model)
1284 \since 5.5
1285
1286 This signal is emitted when the \a model is successfully set with setModel().
1287
1288 \sa model(), setModel()
1289*/
1290
1291
1292/*!
1293 \enum QItemSelectionModel::SelectionFlag
1294
1295 This enum describes the way the selection model will be updated.
1296
1297 \value NoUpdate No selection will be made.
1298 \value Clear The complete selection will be cleared.
1299 \value Select All specified indexes will be selected.
1300 \value Deselect All specified indexes will be deselected.
1301 \value Toggle All specified indexes will be selected or
1302 deselected depending on their current state.
1303 \value Current The current selection will be updated.
1304 \value Rows All indexes will be expanded to span rows.
1305 \value Columns All indexes will be expanded to span columns.
1306 \value SelectCurrent A combination of Select and Current, provided for
1307 convenience.
1308 \value ToggleCurrent A combination of Toggle and Current, provided for
1309 convenience.
1310 \value ClearAndSelect A combination of Clear and Select, provided for
1311 convenience.
1312*/
1313
1314namespace {
1316struct IsNotValid {
1317 typedef bool result_type;
1318 struct is_transparent : std::true_type {};
1319 template <typename T>
1320 constexpr bool operator()(T &t) const noexcept(noexcept(t.isValid()))
1321 { return !t.isValid(); }
1322 template <typename T>
1323 constexpr bool operator()(T *t) const noexcept(noexcept(t->isValid()))
1324 { return !t->isValid(); }
1325};
1326}
1327} // unnamed namespace
1328
1329/*!
1330 Selects the item \a selection using the specified \a command, and emits
1331 selectionChanged().
1332
1333 \sa QItemSelectionModel::SelectionFlag
1334*/
1335void QItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
1336{
1337 Q_D(QItemSelectionModel);
1338 if (!d->model.value()) {
1339 qWarning("QItemSelectionModel: Selecting when no model has been set will result in a no-op.");
1340 return;
1341 }
1342 if (command == NoUpdate)
1343 return;
1344
1345 // store old selection
1346 QItemSelection sel = selection;
1347 // If d->ranges is non-empty when the source model is reset the persistent indexes
1348 // it contains will be invalid. We can't clear them in a modelReset slot because that might already
1349 // be too late if another model observer is connected to the same modelReset slot and is invoked first
1350 // it might call select() on this selection model before any such QItemSelectionModelPrivate::modelReset() slot
1351 // is invoked, so it would not be cleared yet. We clear it invalid ranges in it here.
1352 d->ranges.removeIf(QtFunctionObjects::IsNotValid());
1353
1354 QItemSelection old = d->ranges;
1355 old.merge(d->currentSelection, d->currentCommand);
1356
1357 // expand selection according to SelectionBehavior
1358 if (command & Rows || command & Columns)
1359 sel = d->expandSelection(sel, command);
1360
1361 // clear ranges and currentSelection
1362 if (command & Clear) {
1363 d->ranges.clear();
1364 d->currentSelection.clear();
1365 }
1366
1367 // merge and clear currentSelection if Current was not set (ie. start new currentSelection)
1368 if (!(command & Current))
1369 d->finalize();
1370
1371 // update currentSelection
1372 if (command & Toggle || command & Select || command & Deselect) {
1373 d->currentCommand = command;
1374 d->currentSelection = sel;
1375 }
1376
1377 // generate new selection, compare with old and emit selectionChanged()
1378 QItemSelection newSelection = d->ranges;
1379 newSelection.merge(d->currentSelection, d->currentCommand);
1380 emitSelectionChanged(newSelection, old);
1381}
1382
1383/*!
1384 Clears the selection model. Emits selectionChanged() and currentChanged().
1385*/
1386void QItemSelectionModel::clear()
1387{
1388 clearSelection();
1389 clearCurrentIndex();
1390}
1391
1392/*!
1393 Clears the current index. Emits currentChanged().
1394 */
1395void QItemSelectionModel::clearCurrentIndex()
1396{
1397 Q_D(QItemSelectionModel);
1398 QModelIndex previous = d->currentIndex;
1399 d->currentIndex = QModelIndex();
1400 if (previous.isValid()) {
1401 emit currentChanged(d->currentIndex, previous);
1402 emit currentRowChanged(d->currentIndex, previous);
1403 emit currentColumnChanged(d->currentIndex, previous);
1404 }
1405}
1406
1407/*!
1408 Clears the selection model. Does not emit any signals.
1409*/
1410void QItemSelectionModel::reset()
1411{
1412 const QSignalBlocker blocker(this);
1413 clear();
1414}
1415
1416/*!
1417 \since 4.2
1418 Clears the selection in the selection model. Emits selectionChanged().
1419*/
1420void QItemSelectionModel::clearSelection()
1421{
1422 Q_D(QItemSelectionModel);
1423 if (d->ranges.size() == 0 && d->currentSelection.size() == 0)
1424 return;
1425
1426 select(QItemSelection(), Clear);
1427}
1428
1429
1430/*!
1431 Sets the model item \a index to be the current item, and emits
1432 currentChanged(). The current item is used for keyboard navigation and
1433 focus indication; it is independent of any selected items, although a
1434 selected item can also be the current item.
1435
1436 Depending on the specified \a command, the \a index can also become part
1437 of the current selection.
1438 \sa select()
1439*/
1440void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
1441{
1442 Q_D(QItemSelectionModel);
1443 if (!d->model.value()) {
1444 qWarning("QItemSelectionModel: Setting the current index when no model has been set will result in a no-op.");
1445 return;
1446 }
1447 if (index == d->currentIndex) {
1448 if (command != NoUpdate)
1449 select(index, command); // select item
1450 return;
1451 }
1452 QPersistentModelIndex previous = d->currentIndex;
1453 d->currentIndex = index; // set current before emitting selection changed below
1454 if (command != NoUpdate)
1455 select(d->currentIndex, command); // select item
1456 emit currentChanged(d->currentIndex, previous);
1457 if (d->currentIndex.row() != previous.row() ||
1458 d->currentIndex.parent() != previous.parent())
1459 emit currentRowChanged(d->currentIndex, previous);
1460 if (d->currentIndex.column() != previous.column() ||
1461 d->currentIndex.parent() != previous.parent())
1462 emit currentColumnChanged(d->currentIndex, previous);
1463}
1464
1465/*!
1466 Returns the model item index for the current item, or an invalid index
1467 if there is no current item.
1468*/
1469QModelIndex QItemSelectionModel::currentIndex() const
1470{
1471 return static_cast<QModelIndex>(d_func()->currentIndex);
1472}
1473
1474/*!
1475 Returns \c true if the given model item \a index is selected.
1476*/
1477bool QItemSelectionModel::isSelected(const QModelIndex &index) const
1478{
1479 Q_D(const QItemSelectionModel);
1480 if (d->model != index.model() || !index.isValid())
1481 return false;
1482
1483 // search model ranges
1484 auto contains = [](const auto &index) {
1485 return [&index](const auto &range) { return range.contains(index); };
1486 };
1487 bool selected = std::any_of(d->ranges.begin(), d->ranges.end(), contains(index));
1488
1489 // check currentSelection
1490 if (d->currentSelection.size()) {
1491 if ((d->currentCommand & Deselect) && selected)
1492 selected = !d->currentSelection.contains(index);
1493 else if (d->currentCommand & Toggle)
1494 selected ^= d->currentSelection.contains(index);
1495 else if ((d->currentCommand & Select) && !selected)
1496 selected = d->currentSelection.contains(index);
1497 }
1498
1499 if (selected)
1500 return isSelectableAndEnabled(d->model->flags(index));
1501
1502 return false;
1503}
1504
1505/*!
1506 Returns \c true if all items are selected in the \a row with the given
1507 \a parent.
1508
1509 Note that this function is usually faster than calling isSelected()
1510 on all items in the same row and that unselectable items are
1511 ignored.
1512
1513 \note Since Qt 5.15, the default argument for \a parent is an empty
1514 model index.
1515*/
1516bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) const
1517{
1518 Q_D(const QItemSelectionModel);
1519 if (!d->model.value())
1520 return false;
1521 if (parent.isValid() && d->model != parent.model())
1522 return false;
1523
1524 // return false if row exist in currentSelection (Deselect)
1525 if (d->currentCommand & Deselect) {
1526 const auto matches = [](auto row, const auto &parent) {
1527 return [row, &parent](const auto &selection) {
1528 return row >= selection.top() &&
1529 row <= selection.bottom() &&
1530 parent == selection.parent();
1531 };
1532 };
1533 if (std::any_of(d->currentSelection.cbegin(), d->currentSelection.cend(), matches(row, parent)))
1534 return false;
1535 }
1536 // return false if ranges in both currentSelection and ranges
1537 // intersect and have the same row contained
1538 if (d->currentCommand & Toggle) {
1539 for (const auto &selection : d->currentSelection) {
1540 if (row >= selection.top() && row <= selection.bottom()) {
1541 const auto intersects = [](auto row, const auto &selection) {
1542 return [row, &selection](const auto &range) {
1543 return row >= range.top() &&
1544 row <= range.bottom() &&
1545 selection.intersected(range).isValid();
1546 };
1547 };
1548 if (std::any_of(d->ranges.cbegin(), d->ranges.cend(), intersects(row, selection)))
1549 return false;
1550 }
1551 }
1552 }
1553
1554 auto isSelectable = [&](int row, int column) {
1555 return isSelectableAndEnabled(d->model->index(row, column, parent).flags());
1556 };
1557
1558 const int colCount = d->model->columnCount(parent);
1559 int unselectable = 0;
1560 std::vector<QItemSelectionRangeRefCache> cache;
1561 cache.reserve(d->currentSelection.size() + d->ranges.size());
1562 std::copy(d->currentSelection.begin(), d->currentSelection.end(), std::back_inserter(cache));
1563 std::copy(d->ranges.begin(), d->ranges.end(), std::back_inserter(cache));
1564
1565 // check ranges and currentSelection
1566 for (int column = 0; column < colCount; ++column) {
1567 if (!isSelectable(row, column)) {
1568 ++unselectable;
1569 continue;
1570 }
1571 bool foundSelection = false;
1572 for (auto &curSel : cache) {
1573 if (curSel.contains(row, column, parent)) {
1574 const auto right = curSel.right();
1575 for (int i = column + 1; i <= right; ++i) {
1576 if (!isSelectable(row, i))
1577 ++unselectable;
1578 }
1579 column = right;
1580 foundSelection = true;
1581 curSel.invalidate();
1582 break;
1583 }
1584 }
1585 if (!foundSelection)
1586 return false;
1587 }
1588 return unselectable < colCount;
1589}
1590
1591/*!
1592 Returns \c true if all items are selected in the \a column with the given
1593 \a parent.
1594
1595 Note that this function is usually faster than calling isSelected()
1596 on all items in the same column and that unselectable items are
1597 ignored.
1598
1599 \note Since Qt 5.15, the default argument for \a parent is an empty
1600 model index.
1601*/
1602bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent) const
1603{
1604 Q_D(const QItemSelectionModel);
1605 if (!d->model.value())
1606 return false;
1607 if (parent.isValid() && d->model != parent.model())
1608 return false;
1609
1610 // return false if column exist in currentSelection (Deselect)
1611 if (d->currentCommand & Deselect) {
1612 const auto matches = [](auto column, const auto &parent) {
1613 return [column, &parent](const auto &selection) {
1614 return column >= selection.left() &&
1615 column <= selection.right() &&
1616 parent == selection.parent();
1617 };
1618 };
1619 if (std::any_of(d->currentSelection.cbegin(), d->currentSelection.cend(), matches(column, parent)))
1620 return false;
1621 }
1622 // return false if ranges in both currentSelection and the selection model
1623 // intersect and have the same column contained
1624 if (d->currentCommand & Toggle) {
1625 for (const auto &selection : d->currentSelection) {
1626 if (column >= selection.left() && column <= selection.right()) {
1627 const auto intersects = [](auto column, const auto &selection) {
1628 return [column, &selection](const auto &range) {
1629 return column >= range.left() &&
1630 column <= range.right() &&
1631 selection.intersected(range).isValid();
1632 };
1633 };
1634 if (std::any_of(d->ranges.cbegin(), d->ranges.cend(), intersects(column, selection)))
1635 return false;
1636 }
1637 }
1638 }
1639
1640 auto isSelectable = [&](int row, int column) {
1641 return isSelectableAndEnabled(d->model->index(row, column, parent).flags());
1642 };
1643
1644 const int rowCount = d->model->rowCount(parent);
1645 int unselectable = 0;
1646 std::vector<QItemSelectionRangeRefCache> cache;
1647 cache.reserve(d->currentSelection.size() + d->ranges.size());
1648 std::copy(d->currentSelection.begin(), d->currentSelection.end(), std::back_inserter(cache));
1649 std::copy(d->ranges.begin(), d->ranges.end(), std::back_inserter(cache));
1650
1651 // check ranges and currentSelection
1652 for (int row = 0; row < rowCount; ++row) {
1653 if (!isSelectable(row, column)) {
1654 ++unselectable;
1655 continue;
1656 }
1657 bool foundSelection = false;
1658 for (auto &curSel : cache) {
1659 if (curSel.contains(row, column, parent)) {
1660 const auto bottom = curSel.bottom();
1661 for (int i = row + 1; i <= bottom; ++i) {
1662 if (!isSelectable(i, column))
1663 ++unselectable;
1664 }
1665 row = bottom;
1666 foundSelection = true;
1667 curSel.invalidate();
1668 break;
1669 }
1670 }
1671 if (!foundSelection)
1672 return false;
1673 }
1674 return unselectable < rowCount;
1675}
1676
1677/*!
1678 Returns \c true if there are any items selected in the \a row with the given
1679 \a parent.
1680
1681 \note Since Qt 5.15, the default argument for \a parent is an empty
1682 model index.
1683*/
1684bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &parent) const
1685{
1686 Q_D(const QItemSelectionModel);
1687 if (!d->model.value())
1688 return false;
1689 if (parent.isValid() && d->model != parent.model())
1690 return false;
1691
1692 QItemSelection sel = d->ranges;
1693 sel.merge(d->currentSelection, d->currentCommand);
1694 if (sel.isEmpty() || sel.constFirst().parent() != parent)
1695 return false;
1696
1697 for (const QItemSelectionRange &range : std::as_const(sel)) {
1698 int top = range.top();
1699 int bottom = range.bottom();
1700 if (top <= row && bottom >= row) {
1701 int left = range.left();
1702 int right = range.right();
1703 for (int j = left; j <= right; j++) {
1704 if (isSelectableAndEnabled(d->model->index(row, j, parent).flags()))
1705 return true;
1706 }
1707 }
1708 }
1709
1710 return false;
1711}
1712
1713/*!
1714 Returns \c true if there are any items selected in the \a column with the given
1715 \a parent.
1716
1717 \note Since Qt 5.15, the default argument for \a parent is an empty
1718 model index.
1719*/
1720bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelIndex &parent) const
1721{
1722 Q_D(const QItemSelectionModel);
1723 if (!d->model.value())
1724 return false;
1725 if (parent.isValid() && d->model != parent.model())
1726 return false;
1727
1728 QItemSelection sel = d->ranges;
1729 sel.merge(d->currentSelection, d->currentCommand);
1730 if (sel.isEmpty() || sel.constFirst().parent() != parent)
1731 return false;
1732
1733 for (const QItemSelectionRange &range : std::as_const(sel)) {
1734 int left = range.left();
1735 int right = range.right();
1736 if (left <= column && right >= column) {
1737 int top = range.top();
1738 int bottom = range.bottom();
1739 for (int j = top; j <= bottom; j++) {
1740 if (isSelectableAndEnabled(d->model->index(j, column, parent).flags()))
1741 return true;
1742 }
1743 }
1744 }
1745
1746 return false;
1747}
1748
1749/*!
1750 \internal
1751
1752 Check whether the selection is empty.
1753 In contrast to selection.isEmpty(), this takes into account
1754 whether items are enabled and whether they are selectable.
1755*/
1756static inline bool selectionIsEmpty(const QItemSelection &selection)
1757{
1758 return std::all_of(selection.begin(), selection.end(),
1759 [](const QItemSelectionRange &r) { return r.isEmpty(); });
1760}
1761
1762/*!
1763 \since 4.2
1764
1765 Returns \c true if the selection model contains any selected item,
1766 otherwise returns \c false.
1767*/
1768bool QItemSelectionModel::hasSelection() const
1769{
1770 Q_D(const QItemSelectionModel);
1771
1772 // QTreeModel unfortunately sorts itself lazily.
1773 // When it sorts itself, it emits are layoutChanged signal.
1774 // This layoutChanged signal invalidates d->ranges here.
1775 // So QTreeModel must not sort itself while we are iterating over
1776 // d->ranges here. It sorts itself in executePendingOperations,
1777 // thus preventing the sort to happen inside of selectionIsEmpty below.
1778 // Sad story, read more in QTBUG-94546
1779 const QAbstractItemModel *model = QItemSelectionModel::model();
1780 if (model != nullptr) {
1781 auto model_p = static_cast<const QAbstractItemModelPrivate *>(QObjectPrivate::get(model));
1782 model_p->executePendingOperations();
1783 }
1784
1785 if (d->currentCommand & (Toggle | Deselect)) {
1786 QItemSelection sel = d->ranges;
1787 sel.merge(d->currentSelection, d->currentCommand);
1788 return !selectionIsEmpty(sel);
1789 } else {
1790 return !(selectionIsEmpty(d->ranges) && selectionIsEmpty(d->currentSelection));
1791 }
1792}
1793
1794/*!
1795 Returns a list of all selected model item indexes. The list contains no
1796 duplicates, and is not sorted.
1797*/
1798QModelIndexList QItemSelectionModel::selectedIndexes() const
1799{
1800 Q_D(const QItemSelectionModel);
1801 QItemSelection selected = d->ranges;
1802 selected.merge(d->currentSelection, d->currentCommand);
1803 return selected.indexes();
1804}
1805
1809
1810 friend bool operator==(const RowOrColumnDefinition &lhs, const RowOrColumnDefinition &rhs) noexcept
1811 { return lhs.parent == rhs.parent && lhs.rowOrColumn == rhs.rowOrColumn; }
1812 friend bool operator!=(const RowOrColumnDefinition &lhs, const RowOrColumnDefinition &rhs) noexcept
1813 { return !operator==(lhs, rhs); }
1814};
1815size_t qHash(const RowOrColumnDefinition &key, size_t seed = 0) noexcept
1816{
1817 return qHashMulti(seed, key.parent, key.rowOrColumn);
1818}
1819
1820QT_SPECIALIZE_STD_HASH_TO_CALL_QHASH_BY_CREF(RowOrColumnDefinition)
1821
1822/*!
1823 \since 4.2
1824 Returns the indexes in the given \a column for the rows where all columns are selected.
1825
1826 \sa selectedIndexes(), selectedColumns()
1827*/
1828
1829QModelIndexList QItemSelectionModel::selectedRows(int column) const
1830{
1831 QModelIndexList indexes;
1832
1833 QDuplicateTracker<RowOrColumnDefinition> rowsSeen;
1834
1835 const QItemSelection ranges = selection();
1836 for (int i = 0; i < ranges.size(); ++i) {
1837 const QItemSelectionRange &range = ranges.at(i);
1838 QModelIndex parent = range.parent();
1839 for (int row = range.top(); row <= range.bottom(); row++) {
1840 if (!rowsSeen.hasSeen({parent, row})) {
1841 if (isRowSelected(row, parent)) {
1842 indexes.append(model()->index(row, column, parent));
1843 }
1844 }
1845 }
1846 }
1847
1848 return indexes;
1849}
1850
1851/*!
1852 \since 4.2
1853 Returns the indexes in the given \a row for columns where all rows are selected.
1854
1855 \sa selectedIndexes(), selectedRows()
1856*/
1857
1858QModelIndexList QItemSelectionModel::selectedColumns(int row) const
1859{
1860 QModelIndexList indexes;
1861
1862 QDuplicateTracker<RowOrColumnDefinition> columnsSeen;
1863
1864 const QItemSelection ranges = selection();
1865 for (int i = 0; i < ranges.size(); ++i) {
1866 const QItemSelectionRange &range = ranges.at(i);
1867 QModelIndex parent = range.parent();
1868 for (int column = range.left(); column <= range.right(); column++) {
1869 if (!columnsSeen.hasSeen({parent, column})) {
1870 if (isColumnSelected(column, parent)) {
1871 indexes.append(model()->index(row, column, parent));
1872 }
1873 }
1874 }
1875 }
1876
1877 return indexes;
1878}
1879
1880/*!
1881 Returns the selection ranges stored in the selection model.
1882*/
1883const QItemSelection QItemSelectionModel::selection() const
1884{
1885 Q_D(const QItemSelectionModel);
1886 QItemSelection selected = d->ranges;
1887 selected.merge(d->currentSelection, d->currentCommand);
1888 // make sure we have no invalid ranges
1889 // ### should probably be handled more generic somewhere else
1890 selected.removeIf(QtFunctionObjects::IsNotValid());
1891 return selected;
1892}
1893
1894/*!
1895 \since 5.5
1896
1897 \property QItemSelectionModel::hasSelection
1898 \internal
1899*/
1900/*!
1901 \since 5.5
1902
1903 \property QItemSelectionModel::currentIndex
1904 \internal
1905*/
1906/*!
1907 \since 5.5
1908
1909 \property QItemSelectionModel::selectedIndexes
1910*/
1911
1912/*!
1913 \since 5.5
1914
1915 \property QItemSelectionModel::selection
1916 \internal
1917*/
1918/*!
1919 \since 5.5
1920
1921 \property QItemSelectionModel::model
1922 \internal
1923*/
1924/*!
1925 \since 5.5
1926
1927 Returns the item model operated on by the selection model.
1928*/
1929QAbstractItemModel *QItemSelectionModel::model()
1930{
1931 return d_func()->model.value();
1932}
1933
1934/*!
1935 Returns the item model operated on by the selection model.
1936*/
1937const QAbstractItemModel *QItemSelectionModel::model() const
1938{
1939 return d_func()->model.value();
1940}
1941
1942QBindable<QAbstractItemModel *> QItemSelectionModel::bindableModel()
1943{
1944 return &d_func()->model;
1945}
1946
1947/*!
1948 \since 5.5
1949
1950 Sets the model to \a model. The modelChanged() signal will be emitted.
1951
1952 \sa model(), modelChanged()
1953*/
1954void QItemSelectionModel::setModel(QAbstractItemModel *model)
1955{
1956 Q_D(QItemSelectionModel);
1957 d->model.removeBindingUnlessInWrapper();
1958 if (d->model.valueBypassingBindings() == model)
1959 return;
1960 d->initModel(model);
1961 d->model.notify();
1962}
1963
1964/*!
1965 Compares the two selections \a newSelection and \a oldSelection
1966 and emits selectionChanged() with the deselected and selected items.
1967*/
1968void QItemSelectionModel::emitSelectionChanged(const QItemSelection &newSelection,
1969 const QItemSelection &oldSelection)
1970{
1971 // if both selections are empty or equal we return
1972 if ((oldSelection.isEmpty() && newSelection.isEmpty()) ||
1973 oldSelection == newSelection)
1974 return;
1975
1976 // if either selection is empty we do not need to compare
1977 if (oldSelection.isEmpty() || newSelection.isEmpty()) {
1978 emit selectionChanged(newSelection, oldSelection);
1979 return;
1980 }
1981
1982 QItemSelection deselected = oldSelection;
1983 QItemSelection selected = newSelection;
1984
1985 // remove equal ranges
1986 bool advance;
1987 for (int o = 0; o < deselected.size(); ++o) {
1988 advance = true;
1989 for (int s = 0; s < selected.size() && o < deselected.size();) {
1990 if (deselected.at(o) == selected.at(s)) {
1991 deselected.removeAt(o);
1992 selected.removeAt(s);
1993 advance = false;
1994 } else {
1995 ++s;
1996 }
1997 }
1998 if (advance)
1999 ++o;
2000 }
2001
2002 // find intersections
2003 QItemSelection intersections;
2004 for (int o = 0; o < deselected.size(); ++o) {
2005 for (int s = 0; s < selected.size(); ++s) {
2006 if (deselected.at(o).intersects(selected.at(s)))
2007 intersections.append(deselected.at(o).intersected(selected.at(s)));
2008 }
2009 }
2010
2011 // compare remaining ranges with intersections and split them to find deselected and selected
2012 for (int i = 0; i < intersections.size(); ++i) {
2013 // split deselected
2014 for (int o = 0; o < deselected.size();) {
2015 if (deselected.at(o).intersects(intersections.at(i))) {
2016 QItemSelection::split(deselected.at(o), intersections.at(i), &deselected);
2017 deselected.removeAt(o);
2018 } else {
2019 ++o;
2020 }
2021 }
2022 // split selected
2023 for (int s = 0; s < selected.size();) {
2024 if (selected.at(s).intersects(intersections.at(i))) {
2025 QItemSelection::split(selected.at(s), intersections.at(i), &selected);
2026 selected.removeAt(s);
2027 } else {
2028 ++s;
2029 }
2030 }
2031 }
2032
2033 if (!selected.isEmpty() || !deselected.isEmpty())
2034 emit selectionChanged(selected, deselected);
2035}
2036
2037#ifndef QT_NO_DEBUG_STREAM
2038QDebug operator<<(QDebug dbg, const QItemSelectionRange &range)
2039{
2040 QDebugStateSaver saver(dbg);
2041 dbg.nospace() << "QItemSelectionRange(" << range.topLeft()
2042 << ',' << range.bottomRight() << ')';
2043 return dbg;
2044}
2045#endif
2046
2047QT_END_NAMESPACE
2048
2049#include "moc_qitemselectionmodel.cpp"
QDebug operator<<(QDebug dbg, const QFileInfo &fi)
static bool selectionIsEmpty(const QItemSelection &selection)
static QItemSelection mergeIndexes(const QList< QPersistentModelIndex > &indexes)
static bool qt_PersistentModelIndexLessThan(const QPersistentModelIndex &i1, const QPersistentModelIndex &i2)
static ModelIndexContainer qSelectionIndexes(const QItemSelection &selection)
static void indexesFromRange(const QItemSelectionRange &range, ModelIndexContainer &result)
static QList< std::pair< QPersistentModelIndex, uint > > qSelectionPersistentRowLengths(const QItemSelection &sel)
static bool isSelectableAndEnabled(Qt::ItemFlags flags)
static void rowLengthsFromRange(const QItemSelectionRange &range, QList< std::pair< QPersistentModelIndex, uint > > &result)
static QItemSelection mergeRowLengths(const QList< std::pair< QPersistentModelIndex, uint > > &rowLengths)
size_t qHash(const RowOrColumnDefinition &key, size_t seed=0) noexcept
friend bool operator==(const RowOrColumnDefinition &lhs, const RowOrColumnDefinition &rhs) noexcept
friend bool operator!=(const RowOrColumnDefinition &lhs, const RowOrColumnDefinition &rhs) noexcept