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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/corelib/itemmodels/qitemselectionmodel.cpp