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