Port of QItemSelectionModel::model to new property system

The property 'model' is ported to a bindable property.
The properties hasSelection, selection, selectedIndexes,
and currentIndex are left for later patches.

Task-number: QTBUG-85520
Change-Id: Ia424ce99fc80c3d807c634c21d161a3ad94b27d2
Reviewed-by: Sona Kurazyan <sona.kurazyan@qt.io>
This commit is contained in:
Andreas Buhr 2020-12-14 19:27:10 +01:00
parent 001e9c6a19
commit 62f5a6ca42
5 changed files with 114 additions and 25 deletions

View File

@ -38,6 +38,8 @@
****************************************************************************/ ****************************************************************************/
#include "qitemselectionmodel.h" #include "qitemselectionmodel.h"
#include "qitemselectionmodel_p.h"
#include <private/qitemselectionmodel_p.h> #include <private/qitemselectionmodel_p.h>
#include <private/qduplicatetracker_p.h> #include <private/qduplicatetracker_p.h>
#include <qdebug.h> #include <qdebug.h>
@ -603,6 +605,8 @@ void QItemSelectionModelPrivate::initModel(QAbstractItemModel *m)
SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)) }, SLOT(_q_layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)) },
{ SIGNAL(modelReset()), { SIGNAL(modelReset()),
SLOT(reset()) }, SLOT(reset()) },
{ SIGNAL(destroyed(QObject*)),
SLOT(_q_modelDestroyed()) },
{ nullptr, nullptr } { nullptr, nullptr }
}; };
@ -610,15 +614,18 @@ void QItemSelectionModelPrivate::initModel(QAbstractItemModel *m)
return; return;
Q_Q(QItemSelectionModel); Q_Q(QItemSelectionModel);
if (model) { if (model.value()) {
for (const Cx *cx = &connections[0]; cx->signal; cx++) for (const Cx *cx = &connections[0]; cx->signal; cx++)
QObject::disconnect(model, cx->signal, q, cx->slot); QObject::disconnect(model.value(), cx->signal, q, cx->slot);
q->reset(); q->reset();
} }
model = m;
if (model) { // Caller has to call notify(), unless calling during construction (the common case).
model.setValueBypassingBindings(m);
if (model.value()) {
for (const Cx *cx = &connections[0]; cx->signal; cx++) for (const Cx *cx = &connections[0]; cx->signal; cx++)
QObject::connect(model, cx->signal, q, cx->slot); QObject::connect(model.value(), cx->signal, q, cx->slot);
} }
} }
@ -674,12 +681,16 @@ void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &pare
if (currentIndex.isValid() && parent == currentIndex.parent() if (currentIndex.isValid() && parent == currentIndex.parent()
&& currentIndex.row() >= start && currentIndex.row() <= end) { && currentIndex.row() >= start && currentIndex.row() <= end) {
QModelIndex old = currentIndex; 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); 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); 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(); currentIndex = QModelIndex();
}
emit q->currentChanged(currentIndex, old); emit q->currentChanged(currentIndex, old);
emit q->currentRowChanged(currentIndex, old); emit q->currentRowChanged(currentIndex, old);
if (currentIndex.column() != old.column()) if (currentIndex.column() != old.column())
@ -744,12 +755,16 @@ void QItemSelectionModelPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &p
if (currentIndex.isValid() && parent == currentIndex.parent() if (currentIndex.isValid() && parent == currentIndex.parent()
&& currentIndex.column() >= start && currentIndex.column() <= end) { && currentIndex.column() >= start && currentIndex.column() <= end) {
QModelIndex old = currentIndex; 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); 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); 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(); currentIndex = QModelIndex();
}
emit q->currentChanged(currentIndex, old); emit q->currentChanged(currentIndex, old);
if (currentIndex.row() != old.row()) if (currentIndex.row() != old.row())
emit q->currentRowChanged(currentIndex, old); emit q->currentRowChanged(currentIndex, old);
@ -1050,6 +1065,39 @@ 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::_q_modelDestroyed()
{
model.setValueBypassingBindings(nullptr);
model.notify();
}
/*! /*!
\class QItemSelectionModel \class QItemSelectionModel
\inmodule QtCore \inmodule QtCore
@ -1238,7 +1286,7 @@ struct IsNotValid {
void QItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) void QItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command)
{ {
Q_D(QItemSelectionModel); 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."); qWarning("QItemSelectionModel: Selecting when no model has been set will result in a no-op.");
return; return;
} }
@ -1343,7 +1391,7 @@ void QItemSelectionModel::clearSelection()
void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
{ {
Q_D(QItemSelectionModel); 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."); qWarning("QItemSelectionModel: Setting the current index when no model has been set will result in a no-op.");
return; return;
} }
@ -1425,7 +1473,7 @@ bool QItemSelectionModel::isSelected(const QModelIndex &index) const
bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) const bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) const
{ {
Q_D(const QItemSelectionModel); Q_D(const QItemSelectionModel);
if (!d->model) if (!d->model.value())
return false; return false;
if (parent.isValid() && d->model != parent.model()) if (parent.isValid() && d->model != parent.model())
return false; return false;
@ -1500,7 +1548,7 @@ bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) cons
bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent) const bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent) const
{ {
Q_D(const QItemSelectionModel); Q_D(const QItemSelectionModel);
if (!d->model) if (!d->model.value())
return false; return false;
if (parent.isValid() && d->model != parent.model()) if (parent.isValid() && d->model != parent.model())
return false; return false;
@ -1574,7 +1622,7 @@ bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent
bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &parent) const bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &parent) const
{ {
Q_D(const QItemSelectionModel); Q_D(const QItemSelectionModel);
if (!d->model) if (!d->model.value())
return false; return false;
if (parent.isValid() && d->model != parent.model()) if (parent.isValid() && d->model != parent.model())
return false; return false;
@ -1610,7 +1658,7 @@ bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &par
bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelIndex &parent) const bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelIndex &parent) const
{ {
Q_D(const QItemSelectionModel); Q_D(const QItemSelectionModel);
if (!d->model) if (!d->model.value())
return false; return false;
if (parent.isValid() && d->model != parent.model()) if (parent.isValid() && d->model != parent.model())
return false; return false;
@ -1794,7 +1842,7 @@ const QItemSelection QItemSelectionModel::selection() const
*/ */
QAbstractItemModel *QItemSelectionModel::model() QAbstractItemModel *QItemSelectionModel::model()
{ {
return d_func()->model; return d_func()->model.value();
} }
/*! /*!
@ -1802,7 +1850,12 @@ QAbstractItemModel *QItemSelectionModel::model()
*/ */
const QAbstractItemModel *QItemSelectionModel::model() const const QAbstractItemModel *QItemSelectionModel::model() const
{ {
return d_func()->model; return d_func()->model.value();
}
QBindable<QAbstractItemModel *> QItemSelectionModel::bindableModel()
{
return &d_func()->model;
} }
/*! /*!
@ -1815,11 +1868,12 @@ const QAbstractItemModel *QItemSelectionModel::model() const
void QItemSelectionModel::setModel(QAbstractItemModel *model) void QItemSelectionModel::setModel(QAbstractItemModel *model)
{ {
Q_D(QItemSelectionModel); Q_D(QItemSelectionModel);
d->model.removeBindingUnlessInWrapper();
if (d->model == model) if (d->model == model)
return; return;
d->initModel(model); d->initModel(model);
emit modelChanged(model); d->model.notify();
} }
/*! /*!

View File

@ -120,7 +120,7 @@ class QItemSelectionModelPrivate;
class Q_CORE_EXPORT QItemSelectionModel : public QObject class Q_CORE_EXPORT QItemSelectionModel : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged BINDABLE bindableModel)
Q_PROPERTY(bool hasSelection READ hasSelection NOTIFY selectionChanged STORED false DESIGNABLE false) Q_PROPERTY(bool hasSelection READ hasSelection NOTIFY selectionChanged STORED false DESIGNABLE false)
Q_PROPERTY(QModelIndex currentIndex READ currentIndex NOTIFY currentChanged STORED false DESIGNABLE false) Q_PROPERTY(QModelIndex currentIndex READ currentIndex NOTIFY currentChanged STORED false DESIGNABLE false)
Q_PROPERTY(QItemSelection selection READ selection NOTIFY selectionChanged STORED false DESIGNABLE false) Q_PROPERTY(QItemSelection selection READ selection NOTIFY selectionChanged STORED false DESIGNABLE false)
@ -169,6 +169,7 @@ public:
const QAbstractItemModel *model() const; const QAbstractItemModel *model() const;
QAbstractItemModel *model(); QAbstractItemModel *model();
QBindable<QAbstractItemModel *> bindableModel();
void setModel(QAbstractItemModel *model); void setModel(QAbstractItemModel *model);
@ -201,6 +202,7 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeInserted(const QModelIndex&, int, int)) Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeInserted(const QModelIndex&, int, int))
Q_PRIVATE_SLOT(d_func(), void _q_layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoHint)) Q_PRIVATE_SLOT(d_func(), void _q_layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoHint))
Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoHint)) Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoHint))
Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed())
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(QItemSelectionModel::SelectionFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(QItemSelectionModel::SelectionFlags)

View File

@ -52,6 +52,7 @@
// //
#include "private/qobject_p.h" #include "private/qobject_p.h"
#include "private/qproperty_p.h"
QT_REQUIRE_CONFIG(itemmodel); QT_REQUIRE_CONFIG(itemmodel);
@ -62,8 +63,7 @@ class QItemSelectionModelPrivate: public QObjectPrivate
Q_DECLARE_PUBLIC(QItemSelectionModel) Q_DECLARE_PUBLIC(QItemSelectionModel)
public: public:
QItemSelectionModelPrivate() QItemSelectionModelPrivate()
: model(nullptr), : currentCommand(QItemSelectionModel::NoUpdate),
currentCommand(QItemSelectionModel::NoUpdate),
tableSelected(false), tableColCount(0), tableRowCount(0) {} tableSelected(false), tableColCount(0), tableRowCount(0) {}
QItemSelection expandSelection(const QItemSelection &selection, QItemSelection expandSelection(const QItemSelection &selection,
@ -77,6 +77,7 @@ public:
void _q_columnsAboutToBeInserted(const QModelIndex &parent, int start, int end); void _q_columnsAboutToBeInserted(const QModelIndex &parent, int start, int end);
void _q_layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint); void _q_layoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint);
void _q_layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint); void _q_layoutChanged(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>(), QAbstractItemModel::LayoutChangeHint hint = QAbstractItemModel::NoLayoutChangeHint);
void _q_modelDestroyed();
inline void remove(QList<QItemSelectionRange> &r) inline void remove(QList<QItemSelectionRange> &r)
{ {
@ -92,7 +93,12 @@ public:
currentSelection.clear(); currentSelection.clear();
} }
QPointer<QAbstractItemModel> model; void setModel(QAbstractItemModel *mod) { q_func()->setModel(mod); }
void modelChanged(QAbstractItemModel *mod) { q_func()->modelChanged(mod); }
Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QItemSelectionModelPrivate, QAbstractItemModel *, model,
&QItemSelectionModelPrivate::setModel,
&QItemSelectionModelPrivate::modelChanged, nullptr)
QItemSelection ranges; QItemSelection ranges;
QItemSelection currentSelection; QItemSelection currentSelection;
QPersistentModelIndex currentIndex; QPersistentModelIndex currentIndex;

View File

@ -7,6 +7,7 @@
qt_internal_add_test(tst_qitemselectionmodel qt_internal_add_test(tst_qitemselectionmodel
SOURCES SOURCES
tst_qitemselectionmodel.cpp tst_qitemselectionmodel.cpp
PUBLIC_LIBRARIES LIBRARIES
Qt::Gui Qt::Gui
Qt::TestPrivate
) )

View File

@ -27,6 +27,7 @@
****************************************************************************/ ****************************************************************************/
#include <QTest> #include <QTest>
#include <QtTest/private/qpropertytesthelper_p.h>
#include <QSignalSpy> #include <QSignalSpy>
#include <QtGui/QtGui> #include <QtGui/QtGui>
@ -85,6 +86,8 @@ private slots:
void deselectRemovedMiddleRange(); void deselectRemovedMiddleRange();
void setModel(); void setModel();
void bindableModel();
void testDifferentModels(); void testDifferentModels();
void testValidRangesInSelectionsAfterReset(); void testValidRangesInSelectionsAfterReset();
@ -2435,6 +2438,29 @@ void tst_QItemSelectionModel::setModel()
QVERIFY(sel.selection().isEmpty()); QVERIFY(sel.selection().isEmpty());
} }
void tst_QItemSelectionModel::bindableModel()
{
QItemSelectionModel sel;
QVERIFY(!sel.model());
std::unique_ptr<QStringListModel> firstModel(
new QStringListModel(QStringList { "Some", "random", "content" }));
std::unique_ptr<QStringListModel> changedModel(
new QStringListModel(QStringList { "Other", "random", "content" }));
QTestPrivate::testReadWritePropertyBasics<QItemSelectionModel, QAbstractItemModel *>(
sel, firstModel.get(), changedModel.get(), "model");
if (QTest::currentTestFailed()) {
qDebug("Failed property test for QItemSelectionModel::model");
return;
}
// check that model is set to nullptr when the object pointed to is deleted:
sel.setModel(firstModel.get());
firstModel.reset();
QCOMPARE(sel.model(), nullptr);
}
void tst_QItemSelectionModel::testDifferentModels() void tst_QItemSelectionModel::testDifferentModels()
{ {
QStandardItemModel model1; QStandardItemModel model1;