diff options
Diffstat (limited to 'src/corelib/itemmodels/qitemselectionmodel.cpp')
-rw-r--r-- | src/corelib/itemmodels/qitemselectionmodel.cpp | 500 |
1 files changed, 264 insertions, 236 deletions
diff --git a/src/corelib/itemmodels/qitemselectionmodel.cpp b/src/corelib/itemmodels/qitemselectionmodel.cpp index 5eed378f9e..6df60aaf61 100644 --- a/src/corelib/itemmodels/qitemselectionmodel.cpp +++ b/src/corelib/itemmodels/qitemselectionmodel.cpp @@ -1,45 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qitemselectionmodel.h" +#include "qitemselectionmodel_p.h" + #include <private/qitemselectionmodel_p.h> +#include <private/qabstractitemmodel_p.h> #include <private/qduplicatetracker_p.h> +#include <private/qoffsetstringarray_p.h> #include <qdebug.h> #include <algorithm> @@ -47,6 +15,9 @@ QT_BEGIN_NAMESPACE +QT_IMPL_METATYPE_EXTERN(QItemSelectionRange) +QT_IMPL_METATYPE_EXTERN(QItemSelection) + /*! \class QItemSelectionRange \inmodule QtCore @@ -56,6 +27,8 @@ QT_BEGIN_NAMESPACE \ingroup model-view + \compares equality + A QItemSelectionRange contains information about a range of selected items in a model. A range of items is a contiguous array of model items, extending to cover a number of adjacent rows and @@ -245,68 +218,29 @@ QItemSelectionRange QItemSelectionRange::intersected(const QItemSelectionRange & } /*! - \fn bool QItemSelectionRange::operator==(const QItemSelectionRange &other) const + \fn bool QItemSelectionRange::operator==(const QItemSelectionRange &lhs, const QItemSelectionRange &rhs) - Returns \c true if the selection range is exactly the same as the \a other + Returns \c true if \a lhs selection range is exactly the same as the \a rhs range given; otherwise returns \c false. */ /*! - \fn bool QItemSelectionRange::operator!=(const QItemSelectionRange &other) const + \fn bool QItemSelectionRange::operator!=(const QItemSelectionRange &lhs, const QItemSelectionRange &rhs) - Returns \c true if the selection range differs from the \a other range given; + Returns \c true if \a lhs selection range differs from the \a rhs range given; otherwise returns \c false. */ /*! - Returns \c true if the selection range is less than the \a other - range given; otherwise returns \c false. - - The less than calculation is not directly useful to developers - the way that ranges - with different parents compare is not defined. This operator only exists so that the - class can be used with QMap. - -*/ -bool QItemSelectionRange::operator<(const QItemSelectionRange &other) const -{ - // ### Qt 6: This is inconsistent with op== and needs to be fixed, nay, - // ### removed, but cannot, because it was inline up to and including 5.9 - - // Comparing parents will compare the models, but if two equivalent ranges - // in two different models have invalid parents, they would appear the same - if (other.tl.model() == tl.model()) { - // parent has to be calculated, so we only do so once. - const QModelIndex topLeftParent = tl.parent(); - const QModelIndex otherTopLeftParent = other.tl.parent(); - if (topLeftParent == otherTopLeftParent) { - if (other.tl.row() == tl.row()) { - if (other.tl.column() == tl.column()) { - if (other.br.row() == br.row()) { - return br.column() < other.br.column(); - } - return br.row() < other.br.row(); - } - return tl.column() < other.tl.column(); - } - return tl.row() < other.tl.row(); - } - return topLeftParent < otherTopLeftParent; - } - - std::less<const QAbstractItemModel *> less; - return less(tl.model(), other.tl.model()); -} - -/*! \fn bool QItemSelectionRange::isValid() const Returns \c true if the selection range is valid; otherwise returns \c false. */ -static void rowLengthsFromRange(const QItemSelectionRange &range, QList<QPair<QPersistentModelIndex, uint>> &result) +static void rowLengthsFromRange(const QItemSelectionRange &range, QList<std::pair<QPersistentModelIndex, uint>> &result) { if (range.isValid() && range.model()) { const QModelIndex topLeft = range.topLeft(); @@ -317,11 +251,16 @@ static void rowLengthsFromRange(const QItemSelectionRange &range, QList<QPair<QP // We don't need to keep track of ItemIsSelectable and ItemIsEnabled here. That is // required in indexesFromRange() because that method is called from public API // which requires the limitation. - result.push_back(qMakePair(QPersistentModelIndex(topLeft.sibling(row, column)), width)); + result.emplace_back(topLeft.sibling(row, column), width); } } } +static bool isSelectableAndEnabled(Qt::ItemFlags flags) +{ + return flags.testFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); +} + template<typename ModelIndexContainer> static void indexesFromRange(const QItemSelectionRange &range, ModelIndexContainer &result) { @@ -333,8 +272,7 @@ static void indexesFromRange(const QItemSelectionRange &range, ModelIndexContain const QModelIndex columnLeader = topLeft.sibling(row, topLeft.column()); for (int column = topLeft.column(); column <= right; ++column) { QModelIndex index = columnLeader.sibling(row, column); - Qt::ItemFlags flags = range.model()->flags(index); - if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + if (isSelectableAndEnabled(range.model()->flags(index))) result.push_back(index); } } @@ -351,7 +289,9 @@ static ModelIndexContainer qSelectionIndexes(const QItemSelection &selection) } /*! - Returns \c true if the selection range contains no selectable item + Returns \c true if the selection range contains either no items + or only items which are either disabled or marked as not selectable. + \since 4.7 */ @@ -363,8 +303,7 @@ bool QItemSelectionRange::isEmpty() const for (int column = left(); column <= right(); ++column) { for (int row = top(); row <= bottom(); ++row) { QModelIndex index = model()->index(row, column, parent()); - Qt::ItemFlags flags = model()->flags(index); - if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + if (isSelectableAndEnabled(model()->flags(index))) return false; } } @@ -477,7 +416,7 @@ void QItemSelection::select(const QModelIndex &topLeft, const QModelIndex &botto bool QItemSelection::contains(const QModelIndex &index) const { - if (index.flags() & Qt::ItemIsSelectable) { + if (isSelectableAndEnabled(index.flags())) { QList<QItemSelectionRange>::const_iterator it = begin(); for (; it != end(); ++it) if ((*it).contains(index)) @@ -495,9 +434,9 @@ QModelIndexList QItemSelection::indexes() const return qSelectionIndexes<QModelIndexList>(*this); } -static QList<QPair<QPersistentModelIndex, uint>> qSelectionPersistentRowLengths(const QItemSelection &sel) +static QList<std::pair<QPersistentModelIndex, uint>> qSelectionPersistentRowLengths(const QItemSelection &sel) { - QList<QPair<QPersistentModelIndex, uint>> result; + QList<std::pair<QPersistentModelIndex, uint>> result; for (const QItemSelectionRange &range : sel) rowLengthsFromRange(range, result); return result; @@ -521,25 +460,23 @@ void QItemSelection::merge(const QItemSelection &other, QItemSelectionModel::Sel command & QItemSelectionModel::Toggle)) return; - QItemSelection newSelection = other; + QItemSelection newSelection; + newSelection.reserve(other.size()); // Collect intersections QItemSelection intersections; - QItemSelection::iterator it = newSelection.begin(); - while (it != newSelection.end()) { - if (!(*it).isValid()) { - it = newSelection.erase(it); + for (const auto &range : other) { + if (!range.isValid()) continue; + newSelection.push_back(range); + for (int t = 0; t < size(); ++t) { + if (range.intersects(at(t))) + intersections.append(at(t).intersected(range)); } - for (int t = 0; t < count(); ++t) { - if ((*it).intersects(at(t))) - intersections.append(at(t).intersected(*it)); - } - ++it; } // Split the old (and new) ranges using the intersections - for (int i = 0; i < intersections.count(); ++i) { // for each intersection - for (int t = 0; t < count();) { // splitt each old range + for (int i = 0; i < intersections.size(); ++i) { // for each intersection + for (int t = 0; t < size();) { // splitt each old range if (at(t).intersects(intersections.at(i))) { split(at(t), intersections.at(i), this); removeAt(t); @@ -548,7 +485,7 @@ void QItemSelection::merge(const QItemSelection &other, QItemSelectionModel::Sel } } // only split newSelection if Toggle is specified - for (int n = 0; (command & QItemSelectionModel::Toggle) && n < newSelection.count();) { + for (int n = 0; (command & QItemSelectionModel::Toggle) && n < newSelection.size();) { if (newSelection.at(n).intersects(intersections.at(i))) { split(newSelection.at(n), intersections.at(i), &newSelection); newSelection.removeAt(n); @@ -615,52 +552,55 @@ void QItemSelection::split(const QItemSelectionRange &range, void QItemSelectionModelPrivate::initModel(QAbstractItemModel *m) { - struct Cx { - const char *signal; - const char *slot; - }; - static const Cx connections[] = { - { SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int)) }, - { SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), - SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int)) }, - { SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), - SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int)) }, - { SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), - SLOT(_q_columnsAboutToBeInserted(QModelIndex,int,int)) }, - { SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), - SLOT(_q_layoutAboutToBeChanged()) }, - { SIGNAL(columnsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), - SLOT(_q_layoutAboutToBeChanged()) }, - { SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), - SLOT(_q_layoutChanged()) }, - { SIGNAL(columnsMoved(QModelIndex,int,int,QModelIndex,int)), - SLOT(_q_layoutChanged()) }, - { SIGNAL(layoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), - SLOT(_q_layoutAboutToBeChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)) }, - { SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), - SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)) }, - { SIGNAL(modelReset()), - SLOT(reset()) }, - { nullptr, nullptr } - }; - - if (model == m) + Q_Q(QItemSelectionModel); + const QAbstractItemModel *oldModel = model.valueBypassingBindings(); + if (oldModel == m) return; - Q_Q(QItemSelectionModel); - if (model) { - for (const Cx *cx = &connections[0]; cx->signal; cx++) - QObject::disconnect(model, cx->signal, q, cx->slot); + if (oldModel) { q->reset(); + disconnectModel(); } - model = m; - if (model) { - for (const Cx *cx = &connections[0]; cx->signal; cx++) - QObject::connect(model, cx->signal, q, cx->slot); + + // Caller has to call notify(), unless calling during construction (the common case). + model.setValueBypassingBindings(m); + + if (m) { + connections = std::array<QMetaObject::Connection, 12> { + QObjectPrivate::connect(m, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QItemSelectionModelPrivate::rowsAboutToBeRemoved), + QObjectPrivate::connect(m, &QAbstractItemModel::columnsAboutToBeRemoved, + this, &QItemSelectionModelPrivate::columnsAboutToBeRemoved), + QObjectPrivate::connect(m, &QAbstractItemModel::rowsAboutToBeInserted, + this, &QItemSelectionModelPrivate::rowsAboutToBeInserted), + QObjectPrivate::connect(m, &QAbstractItemModel::columnsAboutToBeInserted, + this, &QItemSelectionModelPrivate::columnsAboutToBeInserted), + QObjectPrivate::connect(m, &QAbstractItemModel::rowsAboutToBeMoved, + this, &QItemSelectionModelPrivate::triggerLayoutToBeChanged), + QObjectPrivate::connect(m, &QAbstractItemModel::columnsAboutToBeMoved, + this, &QItemSelectionModelPrivate::triggerLayoutToBeChanged), + QObjectPrivate::connect(m, &QAbstractItemModel::rowsMoved, + this, &QItemSelectionModelPrivate::triggerLayoutChanged), + QObjectPrivate::connect(m, &QAbstractItemModel::columnsMoved, + this, &QItemSelectionModelPrivate::triggerLayoutChanged), + QObjectPrivate::connect(m, &QAbstractItemModel::layoutAboutToBeChanged, + this, &QItemSelectionModelPrivate::layoutAboutToBeChanged), + QObjectPrivate::connect(m, &QAbstractItemModel::layoutChanged, + this, &QItemSelectionModelPrivate::layoutChanged), + QObject::connect(m, &QAbstractItemModel::modelReset, + q, &QItemSelectionModel::reset), + QObjectPrivate::connect(m, &QAbstractItemModel::destroyed, + this, &QItemSelectionModelPrivate::modelDestroyed) + }; } } +void QItemSelectionModelPrivate::disconnectModel() +{ + for (auto &connection : connections) + QObject::disconnect(connection); +} + /*! \internal @@ -678,7 +618,7 @@ QItemSelection QItemSelectionModelPrivate::expandSelection(const QItemSelection QItemSelection expanded; if (command & QItemSelectionModel::Rows) { - for (int i = 0; i < selection.count(); ++i) { + for (int i = 0; i < selection.size(); ++i) { QModelIndex parent = selection.at(i).parent(); int colCount = model->columnCount(parent); QModelIndex tl = model->index(selection.at(i).top(), 0, parent); @@ -688,7 +628,7 @@ QItemSelection QItemSelectionModelPrivate::expandSelection(const QItemSelection } } if (command & QItemSelectionModel::Columns) { - for (int i = 0; i < selection.count(); ++i) { + for (int i = 0; i < selection.size(); ++i) { QModelIndex parent = selection.at(i).parent(); int rowCount = model->rowCount(parent); QModelIndex tl = model->index(0, selection.at(i).left(), parent); @@ -703,22 +643,27 @@ QItemSelection QItemSelectionModelPrivate::expandSelection(const QItemSelection /*! \internal */ -void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &parent, +void QItemSelectionModelPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { Q_Q(QItemSelectionModel); + Q_ASSERT(start <= end); finalize(); // update current index if (currentIndex.isValid() && parent == currentIndex.parent() && currentIndex.row() >= start && currentIndex.row() <= end) { QModelIndex old = currentIndex; - if (start > 0) // there are rows left above the change + if (start > 0) { + // there are rows left above the change currentIndex = model->index(start - 1, old.column(), parent); - else if (model && end < model->rowCount(parent) - 1) // there are rows left below the change + } else if (model.value() && end < model->rowCount(parent) - 1) { + // there are rows left below the change currentIndex = model->index(end + 1, old.column(), parent); - else // there are no rows left in the table + } else { + // there are no rows left in the table currentIndex = QModelIndex(); + } emit q->currentChanged(currentIndex, old); emit q->currentRowChanged(currentIndex, old); if (currentIndex.column() != old.column()) @@ -727,6 +672,7 @@ void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &pare QItemSelection deselected; QItemSelection newParts; + bool indexesOfSelectionChanged = false; QItemSelection::iterator it = ranges.begin(); while (it != ranges.end()) { if (it->topLeft().parent() != parent) { // Check parents until reaching root or contained in range @@ -738,6 +684,8 @@ void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &pare deselected.append(*it); it = ranges.erase(it); } else { + if (itParent.isValid() && end < itParent.row()) + indexesOfSelectionChanged = true; ++it; } } else if (start <= it->bottom() && it->bottom() <= end // Full inclusion @@ -762,19 +710,23 @@ void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &pare deselected.append(removedRange); QItemSelection::split(*it, removedRange, &newParts); it = ranges.erase(it); - } else + } else if (end < it->top()) { // deleted row before selection + indexesOfSelectionChanged = true; + ++it; + } else { ++it; + } } ranges.append(newParts); - if (!deselected.isEmpty()) + if (!deselected.isEmpty() || indexesOfSelectionChanged) emit q->selectionChanged(QItemSelection(), deselected); } /*! \internal */ -void QItemSelectionModelPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, +void QItemSelectionModelPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { Q_Q(QItemSelectionModel); @@ -783,12 +735,16 @@ void QItemSelectionModelPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &p if (currentIndex.isValid() && parent == currentIndex.parent() && currentIndex.column() >= start && currentIndex.column() <= end) { QModelIndex old = currentIndex; - if (start > 0) // there are columns to the left of the change + if (start > 0) { + // there are columns to the left of the change currentIndex = model->index(old.row(), start - 1, parent); - else if (model && end < model->columnCount() - 1) // there are columns to the right of the change + } else if (model.value() && end < model->columnCount() - 1) { + // there are columns to the right of the change currentIndex = model->index(old.row(), end + 1, parent); - else // there are no columns left in the table + } else { + // there are no columns left in the table currentIndex = QModelIndex(); + } emit q->currentChanged(currentIndex, old); if (currentIndex.row() != old.row()) emit q->currentRowChanged(currentIndex, old); @@ -807,7 +763,7 @@ void QItemSelectionModelPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &p Split selection ranges if columns are about to be inserted in the middle. */ -void QItemSelectionModelPrivate::_q_columnsAboutToBeInserted(const QModelIndex &parent, +void QItemSelectionModelPrivate::columnsAboutToBeInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(end); @@ -815,11 +771,12 @@ void QItemSelectionModelPrivate::_q_columnsAboutToBeInserted(const QModelIndex & QList<QItemSelectionRange> split; QList<QItemSelectionRange>::iterator it = ranges.begin(); for (; it != ranges.end(); ) { - if ((*it).isValid() && (*it).parent() == parent + const QModelIndex &itParent = it->parent(); + if ((*it).isValid() && itParent == parent && (*it).left() < start && (*it).right() >= start) { - QModelIndex bottomMiddle = model->index((*it).bottom(), start - 1, (*it).parent()); + QModelIndex bottomMiddle = model->index((*it).bottom(), start - 1, itParent); QItemSelectionRange left((*it).topLeft(), bottomMiddle); - QModelIndex topMiddle = model->index((*it).top(), start, (*it).parent()); + QModelIndex topMiddle = model->index((*it).top(), start, itParent); QItemSelectionRange right(topMiddle, (*it).bottomRight()); it = ranges.erase(it); split.append(left); @@ -836,28 +793,38 @@ void QItemSelectionModelPrivate::_q_columnsAboutToBeInserted(const QModelIndex & Split selection ranges if rows are about to be inserted in the middle. */ -void QItemSelectionModelPrivate::_q_rowsAboutToBeInserted(const QModelIndex &parent, +void QItemSelectionModelPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) { + Q_Q(QItemSelectionModel); Q_UNUSED(end); finalize(); QList<QItemSelectionRange> split; QList<QItemSelectionRange>::iterator it = ranges.begin(); + bool indexesOfSelectionChanged = false; for (; it != ranges.end(); ) { - if ((*it).isValid() && (*it).parent() == parent + const QModelIndex &itParent = it->parent(); + if ((*it).isValid() && itParent == parent && (*it).top() < start && (*it).bottom() >= start) { - QModelIndex middleRight = model->index(start - 1, (*it).right(), (*it).parent()); + QModelIndex middleRight = model->index(start - 1, (*it).right(), itParent); QItemSelectionRange top((*it).topLeft(), middleRight); - QModelIndex middleLeft = model->index(start, (*it).left(), (*it).parent()); + QModelIndex middleLeft = model->index(start, (*it).left(), itParent); QItemSelectionRange bottom(middleLeft, (*it).bottomRight()); it = ranges.erase(it); split.append(top); split.append(bottom); + } else if ((*it).isValid() && itParent == parent // insertion before selection + && (*it).top() >= start) { + indexesOfSelectionChanged = true; + ++it; } else { ++it; } } ranges += split; + + if (indexesOfSelectionChanged) + emit q->selectionChanged(QItemSelection(), QItemSelection()); } /*! @@ -867,7 +834,8 @@ void QItemSelectionModelPrivate::_q_rowsAboutToBeInserted(const QModelIndex &par preparation for the layoutChanged() signal, where the indexes can be merged again. */ -void QItemSelectionModelPrivate::_q_layoutAboutToBeChanged(const QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint hint) +void QItemSelectionModelPrivate::layoutAboutToBeChanged(const QList<QPersistentModelIndex> &, + QAbstractItemModel::LayoutChangeHint hint) { savedPersistentIndexes.clear(); savedPersistentCurrentIndexes.clear(); @@ -876,7 +844,7 @@ void QItemSelectionModelPrivate::_q_layoutAboutToBeChanged(const QList<QPersiste // optimization for when all indexes are selected // (only if there is lots of items (1000) because this is not entirely correct) - if (ranges.isEmpty() && currentSelection.count() == 1) { + if (ranges.isEmpty() && currentSelection.size() == 1) { QItemSelectionRange range = currentSelection.constFirst(); QModelIndex parent = range.parent(); tableRowCount = model->rowCount(parent); @@ -909,14 +877,14 @@ void QItemSelectionModelPrivate::_q_layoutAboutToBeChanged(const QList<QPersiste /*! \internal */ -static QItemSelection mergeRowLengths(const QList<QPair<QPersistentModelIndex, uint>> &rowLengths) +static QItemSelection mergeRowLengths(const QList<std::pair<QPersistentModelIndex, uint>> &rowLengths) { if (rowLengths.isEmpty()) return QItemSelection(); QItemSelection result; int i = 0; - while (i < rowLengths.count()) { + while (i < rowLengths.size()) { const QPersistentModelIndex &tl = rowLengths.at(i).first; if (!tl.isValid()) { ++i; @@ -924,7 +892,7 @@ static QItemSelection mergeRowLengths(const QList<QPair<QPersistentModelIndex, u } QPersistentModelIndex br = tl; const uint length = rowLengths.at(i).second; - while (++i < rowLengths.count()) { + while (++i < rowLengths.size()) { const QPersistentModelIndex &next = rowLengths.at(i).first; if (!next.isValid()) continue; @@ -954,7 +922,7 @@ static QItemSelection mergeIndexes(const QList<QPersistentModelIndex> &indexes) QItemSelection colSpans; // merge columns int i = 0; - while (i < indexes.count()) { + while (i < indexes.size()) { const QPersistentModelIndex &tl = indexes.at(i); if (!tl.isValid()) { ++i; @@ -964,7 +932,7 @@ static QItemSelection mergeIndexes(const QList<QPersistentModelIndex> &indexes) QModelIndex brParent = br.parent(); int brRow = br.row(); int brColumn = br.column(); - while (++i < indexes.count()) { + while (++i < indexes.size()) { const QPersistentModelIndex &next = indexes.at(i); if (!next.isValid()) continue; @@ -987,11 +955,11 @@ static QItemSelection mergeIndexes(const QList<QPersistentModelIndex> &indexes) // merge rows QItemSelection rowSpans; i = 0; - while (i < colSpans.count()) { + while (i < colSpans.size()) { QModelIndex tl = colSpans.at(i).topLeft(); QModelIndex br = colSpans.at(i).bottomRight(); QModelIndex prevTl = tl; - while (++i < colSpans.count()) { + while (++i < colSpans.size()) { QModelIndex nextTl = colSpans.at(i).topLeft(); QModelIndex nextBr = colSpans.at(i).bottomRight(); @@ -1014,7 +982,7 @@ static QItemSelection mergeIndexes(const QList<QPersistentModelIndex> &indexes) /*! \internal - Sort predicate function for QItemSelectionModelPrivate::_q_layoutChanged(), + Sort predicate function for QItemSelectionModelPrivate::layoutChanged(), sorting by parent first in addition to operator<(). This is to prevent fragmentation of the selection by grouping indexes with the same row, column of different parents next to each other, which may happen when a selection @@ -1032,7 +1000,7 @@ static bool qt_PersistentModelIndexLessThan(const QPersistentModelIndex &i1, con Merge the selected indexes into selection ranges again. */ -void QItemSelectionModelPrivate::_q_layoutChanged(const QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint hint) +void QItemSelectionModelPrivate::layoutChanged(const QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint hint) { // special case for when all indexes are selected if (tableSelected && tableColCount == model->columnCount(tableParent) @@ -1090,6 +1058,40 @@ void QItemSelectionModelPrivate::_q_layoutChanged(const QList<QPersistentModelIn } /*! + \internal + + Called when the used model gets destroyed. + + It is impossible to have a correct implementation here. + In the following situation, there are two contradicting rules: + + \code + QProperty<QAbstractItemModel *> leader(mymodel); + QItemSelectionModel myItemSelectionModel; + myItemSelectionModel.bindableModel().setBinding([&](){ return leader.value(); } + delete mymodel; + QAbstractItemModel *returnedModel = myItemSelectionModel.model(); + \endcode + + What should returnedModel be in this situation? + + Rules for bindable properties say that myItemSelectionModel.model() + should return the same as leader.value(), namely the pointer to the now deleted model. + + However, backward compatibility requires myItemSelectionModel.model() to return a + nullptr, because that was done in the past after the model used was deleted. + + We decide to break the new rule, imposed by bindable properties, and not break the old + rule, because that may break existing code. +*/ +void QItemSelectionModelPrivate::modelDestroyed() +{ + model.setValueBypassingBindings(nullptr); + disconnectModel(); + model.notify(); +} + +/*! \class QItemSelectionModel \inmodule QtCore @@ -1125,7 +1127,7 @@ void QItemSelectionModelPrivate::_q_layoutChanged(const QList<QPersistentModelIn \l{QItemSelectionModel::hasSelection()}{hasSelection}, and \l{QItemSelectionModel::currentIndex()}{currentIndex} are meta-object properties. - \sa {Model/View Programming}, QAbstractItemModel, {Chart Example} + \sa {Model/View Programming}, QAbstractItemModel */ /*! @@ -1218,6 +1220,11 @@ void QItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel:: Note the that the current index changes independently from the selection. Also note that this signal will not be emitted when the item model is reset. + Items which stay selected but change their index are not included in + \a selected and \a deselected. Thus, this signal might be emitted with both + \a selected and \a deselected empty, if only the indices of selected items + change. + \sa select(), currentChanged() */ @@ -1259,10 +1266,10 @@ struct IsNotValid { typedef bool result_type; struct is_transparent : std::true_type {}; template <typename T> - Q_DECL_CONSTEXPR bool operator()(T &t) const noexcept(noexcept(t.isValid())) + constexpr bool operator()(T &t) const noexcept(noexcept(t.isValid())) { return !t.isValid(); } template <typename T> - Q_DECL_CONSTEXPR bool operator()(T *t) const noexcept(noexcept(t->isValid())) + constexpr bool operator()(T *t) const noexcept(noexcept(t->isValid())) { return !t->isValid(); } }; } @@ -1277,7 +1284,7 @@ struct IsNotValid { void QItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { Q_D(QItemSelectionModel); - if (!d->model) { + if (!d->model.value()) { qWarning("QItemSelectionModel: Selecting when no model has been set will result in a no-op."); return; } @@ -1289,11 +1296,9 @@ void QItemSelectionModel::select(const QItemSelection &selection, QItemSelection // If d->ranges is non-empty when the source model is reset the persistent indexes // it contains will be invalid. We can't clear them in a modelReset slot because that might already // be too late if another model observer is connected to the same modelReset slot and is invoked first - // it might call select() on this selection model before any such QItemSelectionModelPrivate::_q_modelReset() slot + // it might call select() on this selection model before any such QItemSelectionModelPrivate::modelReset() slot // is invoked, so it would not be cleared yet. We clear it invalid ranges in it here. - using namespace QtFunctionObjects; - d->ranges.erase(std::remove_if(d->ranges.begin(), d->ranges.end(), IsNotValid()), - d->ranges.end()); + d->ranges.removeIf(QtFunctionObjects::IsNotValid()); QItemSelection old = d->ranges; old.merge(d->currentSelection, d->currentCommand); @@ -1364,7 +1369,7 @@ void QItemSelectionModel::reset() void QItemSelectionModel::clearSelection() { Q_D(QItemSelectionModel); - if (d->ranges.count() == 0 && d->currentSelection.count() == 0) + if (d->ranges.size() == 0 && d->currentSelection.size() == 0) return; select(QItemSelection(), Clear); @@ -1384,7 +1389,7 @@ void QItemSelectionModel::clearSelection() void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) { Q_D(QItemSelectionModel); - if (!d->model) { + if (!d->model.value()) { qWarning("QItemSelectionModel: Setting the current index when no model has been set will result in a no-op."); return; } @@ -1435,7 +1440,7 @@ bool QItemSelectionModel::isSelected(const QModelIndex &index) const } // check currentSelection - if (d->currentSelection.count()) { + if (d->currentSelection.size()) { if ((d->currentCommand & Deselect) && selected) selected = !d->currentSelection.contains(index); else if (d->currentCommand & Toggle) @@ -1444,10 +1449,8 @@ bool QItemSelectionModel::isSelected(const QModelIndex &index) const selected = d->currentSelection.contains(index); } - if (selected) { - Qt::ItemFlags flags = d->model->flags(index); - return (flags & Qt::ItemIsSelectable); - } + if (selected) + return isSelectableAndEnabled(d->model->flags(index)); return false; } @@ -1466,14 +1469,14 @@ bool QItemSelectionModel::isSelected(const QModelIndex &index) const bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) const { Q_D(const QItemSelectionModel); - if (!d->model) + if (!d->model.value()) return false; if (parent.isValid() && d->model != parent.model()) return false; // return false if row exist in currentSelection (Deselect) - if (d->currentCommand & Deselect && d->currentSelection.count()) { - for (int i=0; i<d->currentSelection.count(); ++i) { + if (d->currentCommand & Deselect && d->currentSelection.size()) { + for (int i=0; i<d->currentSelection.size(); ++i) { if (d->currentSelection.at(i).parent() == parent && row >= d->currentSelection.at(i).top() && row <= d->currentSelection.at(i).bottom()) @@ -1482,19 +1485,18 @@ bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) cons } // return false if ranges in both currentSelection and ranges // intersect and have the same row contained - if (d->currentCommand & Toggle && d->currentSelection.count()) { - for (int i=0; i<d->currentSelection.count(); ++i) + if (d->currentCommand & Toggle && d->currentSelection.size()) { + for (int i=0; i<d->currentSelection.size(); ++i) if (d->currentSelection.at(i).top() <= row && d->currentSelection.at(i).bottom() >= row) - for (int j=0; j<d->ranges.count(); ++j) + for (int j=0; j<d->ranges.size(); ++j) if (d->ranges.at(j).top() <= row && d->ranges.at(j).bottom() >= row && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) return false; } auto isSelectable = [&](int row, int column) { - Qt::ItemFlags flags = d->model->index(row, column, parent).flags(); - return (flags & Qt::ItemIsSelectable); + return isSelectableAndEnabled(d->model->index(row, column, parent).flags()); }; const int colCount = d->model->columnCount(parent); @@ -1502,7 +1504,7 @@ bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) cons // add ranges and currentSelection and check through them all QList<QItemSelectionRange>::const_iterator it; QList<QItemSelectionRange> joined = d->ranges; - if (d->currentSelection.count()) + if (d->currentSelection.size()) joined += d->currentSelection; for (int column = 0; column < colCount; ++column) { if (!isSelectable(row, column)) { @@ -1541,14 +1543,14 @@ bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) cons bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent) const { Q_D(const QItemSelectionModel); - if (!d->model) + if (!d->model.value()) return false; if (parent.isValid() && d->model != parent.model()) return false; // return false if column exist in currentSelection (Deselect) - if (d->currentCommand & Deselect && d->currentSelection.count()) { - for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentCommand & Deselect && d->currentSelection.size()) { + for (int i = 0; i < d->currentSelection.size(); ++i) { if (d->currentSelection.at(i).parent() == parent && column >= d->currentSelection.at(i).left() && column <= d->currentSelection.at(i).right()) @@ -1557,11 +1559,11 @@ bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent } // return false if ranges in both currentSelection and the selection model // intersect and have the same column contained - if (d->currentCommand & Toggle && d->currentSelection.count()) { - for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentCommand & Toggle && d->currentSelection.size()) { + for (int i = 0; i < d->currentSelection.size(); ++i) { if (d->currentSelection.at(i).left() <= column && d->currentSelection.at(i).right() >= column) { - for (int j = 0; j < d->ranges.count(); ++j) { + for (int j = 0; j < d->ranges.size(); ++j) { if (d->ranges.at(j).left() <= column && d->ranges.at(j).right() >= column && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) { return false; @@ -1572,8 +1574,7 @@ bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent } auto isSelectable = [&](int row, int column) { - Qt::ItemFlags flags = d->model->index(row, column, parent).flags(); - return (flags & Qt::ItemIsSelectable); + return isSelectableAndEnabled(d->model->index(row, column, parent).flags()); }; const int rowCount = d->model->rowCount(parent); int unselectable = 0; @@ -1581,7 +1582,7 @@ bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent // add ranges and currentSelection and check through them all QList<QItemSelectionRange>::const_iterator it; QList<QItemSelectionRange> joined = d->ranges; - if (d->currentSelection.count()) + if (d->currentSelection.size()) joined += d->currentSelection; for (int row = 0; row < rowCount; ++row) { if (!isSelectable(row, column)) { @@ -1615,14 +1616,14 @@ bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &parent) const { Q_D(const QItemSelectionModel); - if (!d->model) + if (!d->model.value()) return false; if (parent.isValid() && d->model != parent.model()) return false; QItemSelection sel = d->ranges; sel.merge(d->currentSelection, d->currentCommand); - for (const QItemSelectionRange &range : qAsConst(sel)) { + for (const QItemSelectionRange &range : std::as_const(sel)) { if (range.parent() != parent) return false; int top = range.top(); @@ -1631,8 +1632,7 @@ bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &par int right = range.right(); if (top <= row && bottom >= row) { for (int j = left; j <= right; j++) { - const Qt::ItemFlags flags = d->model->index(row, j, parent).flags(); - if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + if (isSelectableAndEnabled(d->model->index(row, j, parent).flags())) return true; } } @@ -1651,14 +1651,14 @@ bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &par bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelIndex &parent) const { Q_D(const QItemSelectionModel); - if (!d->model) + if (!d->model.value()) return false; if (parent.isValid() && d->model != parent.model()) return false; QItemSelection sel = d->ranges; sel.merge(d->currentSelection, d->currentCommand); - for (const QItemSelectionRange &range : qAsConst(sel)) { + for (const QItemSelectionRange &range : std::as_const(sel)) { if (range.parent() != parent) return false; int top = range.top(); @@ -1667,8 +1667,7 @@ bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelInde int right = range.right(); if (left <= column && right >= column) { for (int j = top; j <= bottom; j++) { - const Qt::ItemFlags flags = d->model->index(j, column, parent).flags(); - if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + if (isSelectableAndEnabled(d->model->index(j, column, parent).flags())) return true; } } @@ -1678,20 +1677,47 @@ bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelInde } /*! + \internal + + Check whether the selection is empty. + In contrast to selection.isEmpty(), this takes into account + whether items are enabled and whether they are selectable. +*/ +static bool selectionIsEmpty(const QItemSelection &selection) +{ + return std::all_of(selection.begin(), selection.end(), + [](const QItemSelectionRange &r) { return r.isEmpty(); }); +} + +/*! \since 4.2 - Returns \c true if the selection model contains any selection ranges; + Returns \c true if the selection model contains any selected item, otherwise returns \c false. */ bool QItemSelectionModel::hasSelection() const { Q_D(const QItemSelectionModel); + + // QTreeModel unfortunately sorts itself lazily. + // When it sorts itself, it emits are layoutChanged signal. + // This layoutChanged signal invalidates d->ranges here. + // So QTreeModel must not sort itself while we are iterating over + // d->ranges here. It sorts itself in executePendingOperations, + // thus preventing the sort to happen inside of selectionIsEmpty below. + // Sad story, read more in QTBUG-94546 + const QAbstractItemModel *model = QItemSelectionModel::model(); + if (model != nullptr) { + auto model_p = static_cast<const QAbstractItemModelPrivate *>(QObjectPrivate::get(model)); + model_p->executePendingOperations(); + } + if (d->currentCommand & (Toggle | Deselect)) { QItemSelection sel = d->ranges; sel.merge(d->currentSelection, d->currentCommand); - return !sel.isEmpty(); + return !selectionIsEmpty(sel); } else { - return !(d->ranges.isEmpty() && d->currentSelection.isEmpty()); + return !(selectionIsEmpty(d->ranges) && selectionIsEmpty(d->currentSelection)); } } @@ -1740,7 +1766,7 @@ QModelIndexList QItemSelectionModel::selectedRows(int column) const QDuplicateTracker<RowOrColumnDefinition> rowsSeen; const QItemSelection ranges = selection(); - for (int i = 0; i < ranges.count(); ++i) { + for (int i = 0; i < ranges.size(); ++i) { const QItemSelectionRange &range = ranges.at(i); QModelIndex parent = range.parent(); for (int row = range.top(); row <= range.bottom(); row++) { @@ -1769,7 +1795,7 @@ QModelIndexList QItemSelectionModel::selectedColumns(int row) const QDuplicateTracker<RowOrColumnDefinition> columnsSeen; const QItemSelection ranges = selection(); - for (int i = 0; i < ranges.count(); ++i) { + for (int i = 0; i < ranges.size(); ++i) { const QItemSelectionRange &range = ranges.at(i); QModelIndex parent = range.parent(); for (int column = range.left(); column <= range.right(); column++) { @@ -1794,10 +1820,7 @@ const QItemSelection QItemSelectionModel::selection() const selected.merge(d->currentSelection, d->currentCommand); // make sure we have no invalid ranges // ### should probably be handled more generic somewhere else - using namespace QtFunctionObjects; - selected.erase(std::remove_if(selected.begin(), selected.end(), - IsNotValid()), - selected.end()); + selected.removeIf(QtFunctionObjects::IsNotValid()); return selected; } @@ -1838,7 +1861,7 @@ const QItemSelection QItemSelectionModel::selection() const */ QAbstractItemModel *QItemSelectionModel::model() { - return d_func()->model; + return d_func()->model.value(); } /*! @@ -1846,7 +1869,12 @@ QAbstractItemModel *QItemSelectionModel::model() */ const QAbstractItemModel *QItemSelectionModel::model() const { - return d_func()->model; + return d_func()->model.value(); +} + +QBindable<QAbstractItemModel *> QItemSelectionModel::bindableModel() +{ + return &d_func()->model; } /*! @@ -1859,11 +1887,11 @@ const QAbstractItemModel *QItemSelectionModel::model() const void QItemSelectionModel::setModel(QAbstractItemModel *model) { Q_D(QItemSelectionModel); - if (d->model == model) + d->model.removeBindingUnlessInWrapper(); + if (d->model.valueBypassingBindings() == model) return; - d->initModel(model); - emit modelChanged(model); + d->model.notify(); } /*! @@ -1889,9 +1917,9 @@ void QItemSelectionModel::emitSelectionChanged(const QItemSelection &newSelectio // remove equal ranges bool advance; - for (int o = 0; o < deselected.count(); ++o) { + for (int o = 0; o < deselected.size(); ++o) { advance = true; - for (int s = 0; s < selected.count() && o < deselected.count();) { + for (int s = 0; s < selected.size() && o < deselected.size();) { if (deselected.at(o) == selected.at(s)) { deselected.removeAt(o); selected.removeAt(s); @@ -1906,17 +1934,17 @@ void QItemSelectionModel::emitSelectionChanged(const QItemSelection &newSelectio // find intersections QItemSelection intersections; - for (int o = 0; o < deselected.count(); ++o) { - for (int s = 0; s < selected.count(); ++s) { + for (int o = 0; o < deselected.size(); ++o) { + for (int s = 0; s < selected.size(); ++s) { if (deselected.at(o).intersects(selected.at(s))) intersections.append(deselected.at(o).intersected(selected.at(s))); } } // compare remaining ranges with intersections and split them to find deselected and selected - for (int i = 0; i < intersections.count(); ++i) { + for (int i = 0; i < intersections.size(); ++i) { // split deselected - for (int o = 0; o < deselected.count();) { + for (int o = 0; o < deselected.size();) { if (deselected.at(o).intersects(intersections.at(i))) { QItemSelection::split(deselected.at(o), intersections.at(i), &deselected); deselected.removeAt(o); @@ -1925,7 +1953,7 @@ void QItemSelectionModel::emitSelectionChanged(const QItemSelection &newSelectio } } // split selected - for (int s = 0; s < selected.count();) { + for (int s = 0; s < selected.size();) { if (selected.at(s).intersects(intersections.at(i))) { QItemSelection::split(selected.at(s), intersections.at(i), &selected); selected.removeAt(s); |