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

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