From a0e309b4036c2eedafbc32b772f1f49152f390ce Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Wed, 3 Feb 2016 11:38:33 +0100 Subject: [PATCH] Add a property indicating whether the models form a connected chain. Replace existing asserts with a query and notification API. This makes the class more suitable for use in QML. --- autotests/kmodelindexproxymappertest.cpp | 103 +++++++++++++++++++++++ src/kmodelindexproxymapper.cpp | 49 +++++++++-- src/kmodelindexproxymapper.h | 17 ++++ 3 files changed, 160 insertions(+), 9 deletions(-) diff --git a/autotests/kmodelindexproxymappertest.cpp b/autotests/kmodelindexproxymappertest.cpp index 8090ae1..2a307dd 100644 --- a/autotests/kmodelindexproxymappertest.cpp +++ b/autotests/kmodelindexproxymappertest.cpp @@ -35,6 +35,11 @@ private Q_SLOTS: void testIndexMapping(); void testSelectionMapping(); + void selfConnection(); + void connectedChangedSimple(); + void connectedChangedComplex(); + void crossWires(); + void isConnected(); private: QStringListModel baseModel; @@ -101,5 +106,103 @@ void ModelIndexProxyMapperTest::testSelectionMapping() QCOMPARE(mapper.mapSelectionRightToLeft(rightSel), leftSel); } +void ModelIndexProxyMapperTest::selfConnection() +{ + KModelIndexProxyMapper mapper(&baseModel, &baseModel); + QVERIFY(mapper.isConnected()); + + auto idx = baseModel.index(0, 0); + QVERIFY(idx.isValid()); + + QCOMPARE(mapper.mapLeftToRight(idx), idx); +} + +void ModelIndexProxyMapperTest::connectedChangedSimple() +{ + QIdentityProxyModel proxy1; + Q_SET_OBJECT_NAME(proxy1); + + KModelIndexProxyMapper mapper(&proxy1, &baseModel); + + QSignalSpy spy(&mapper, SIGNAL(isConnectedChanged())); + + QVERIFY(!mapper.isConnected()); + proxy1.setSourceModel(&baseModel); + + QVERIFY(mapper.isConnected()); + QCOMPARE(spy.count(), 1); +} + +void ModelIndexProxyMapperTest::connectedChangedComplex() +{ + KModelIndexProxyMapper mapper(&proxy_left3, &proxy_right4); + + QSignalSpy spy(&mapper, SIGNAL(isConnectedChanged())); + + QVERIFY(mapper.isConnected()); + + proxy_right2.setSourceModel(Q_NULLPTR); + + QVERIFY(!mapper.isConnected()); + QCOMPARE(spy.count(), 1); + + proxy_right2.setSourceModel(&proxy_right1); + + QVERIFY(mapper.isConnected()); + QCOMPARE(spy.count(), 2); + + auto leftIdx = proxy_left3.index(0, 0); + QVERIFY(leftIdx.isValid()); + auto rightIdx = mapper.mapLeftToRight(leftIdx); + QVERIFY(rightIdx.isValid()); + QCOMPARE(mapper.mapRightToLeft(rightIdx), leftIdx); + + QIdentityProxyModel replacement_right1; + replacement_right1.setSourceModel(&proxy_right1); + proxy_right2.setSourceModel(&replacement_right1); + + QVERIFY(mapper.isConnected()); + QCOMPARE(spy.count(), 2); +} + +void ModelIndexProxyMapperTest::crossWires() +{ + KModelIndexProxyMapper mapper(&proxy_left3, &proxy_right4); + + QSignalSpy spy(&mapper, SIGNAL(isConnectedChanged())); + + QVERIFY(mapper.isConnected()); + + proxy_left3.setSourceModel(&proxy_right3); + + QVERIFY(mapper.isConnected()); + QCOMPARE(spy.count(), 0); + + { + auto leftIdx = proxy_left3.index(0, 0); + auto rightIdx = proxy_right4.index(0, 0); + QCOMPARE(mapper.mapLeftToRight(leftIdx), rightIdx); + } + + proxy_right4.setSourceModel(&proxy_left2); + + QVERIFY(mapper.isConnected()); + QCOMPARE(spy.count(), 0); + + { + auto leftIdx = proxy_left3.index(0, 0); + auto rightIdx = proxy_right4.index(0, 0); + QCOMPARE(mapper.mapLeftToRight(leftIdx), rightIdx); + } +} + +void ModelIndexProxyMapperTest::isConnected() +{ + KModelIndexProxyMapper mapper1(&proxy_left1, &baseModel); + QVERIFY(mapper1.isConnected()); + KModelIndexProxyMapper mapper2(&baseModel, &proxy_left1); + QVERIFY(mapper2.isConnected()); +} + QTEST_MAIN(ModelIndexProxyMapperTest) #include "kmodelindexproxymappertest.moc" diff --git a/src/kmodelindexproxymapper.cpp b/src/kmodelindexproxymapper.cpp index 7922e85..2fb1858 100644 --- a/src/kmodelindexproxymapper.cpp +++ b/src/kmodelindexproxymapper.cpp @@ -2,6 +2,8 @@ Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net, author Stephen Kelly + Copyright (c) 2016 Ableton AG + Author Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by @@ -30,13 +32,14 @@ class KModelIndexProxyMapperPrivate { KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq) - : q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel) + : q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel), mConnected(false) { createProxyChain(); } void createProxyChain(); - bool assertValid(); + void checkConnected(); + void setConnected(bool connected); bool assertSelectionValid(const QItemSelection &selection) const { @@ -57,6 +60,8 @@ class KModelIndexProxyMapperPrivate QPointer m_leftModel; QPointer m_rightModel; + + bool mConnected; }; /* @@ -97,18 +102,28 @@ class KModelIndexProxyMapperPrivate void KModelIndexProxyMapperPrivate::createProxyChain() { + Q_FOREACH (auto p, m_proxyChainUp) { + p->disconnect(q_ptr); + } + Q_FOREACH (auto p, m_proxyChainDown) { + p->disconnect(q_ptr); + } + m_proxyChainUp.clear(); + m_proxyChainDown.clear(); QPointer targetModel = m_rightModel; QList > proxyChainDown; QPointer selectionTargetProxyModel = qobject_cast(targetModel); while (selectionTargetProxyModel) { proxyChainDown.prepend(selectionTargetProxyModel); + QObject::connect(selectionTargetProxyModel, &QAbstractProxyModel::sourceModelChanged, q_ptr, + [this]{ createProxyChain(); }); selectionTargetProxyModel = qobject_cast(selectionTargetProxyModel->sourceModel()); if (selectionTargetProxyModel == m_leftModel) { m_proxyChainDown = proxyChainDown; - Q_ASSERT(assertValid()); + checkConnected(); return; } } @@ -118,6 +133,8 @@ void KModelIndexProxyMapperPrivate::createProxyChain() while (sourceProxyModel) { m_proxyChainUp.append(sourceProxyModel); + QObject::connect(sourceProxyModel, &QAbstractProxyModel::sourceModelChanged, q_ptr, + [this]{ createProxyChain(); }); sourceProxyModel = qobject_cast(sourceProxyModel->sourceModel()); @@ -125,19 +142,28 @@ void KModelIndexProxyMapperPrivate::createProxyChain() if (targetIndex != -1) { m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size()); - Q_ASSERT(assertValid()); + checkConnected(); return; } } m_proxyChainDown = proxyChainDown; - Q_ASSERT(assertValid()); + checkConnected(); } -bool KModelIndexProxyMapperPrivate::assertValid() +void KModelIndexProxyMapperPrivate::checkConnected() { auto konamiRight = m_proxyChainUp.isEmpty() ? m_leftModel : m_proxyChainUp.last()->sourceModel(); auto konamiLeft = m_proxyChainDown.isEmpty() ? m_rightModel : m_proxyChainDown.first()->sourceModel(); - return konamiLeft && (konamiLeft == konamiRight); + setConnected(konamiLeft && (konamiLeft == konamiRight)); +} + +void KModelIndexProxyMapperPrivate::setConnected(bool connected) +{ + if (mConnected != connected) { + Q_Q(KModelIndexProxyMapper); + mConnected = connected; + Q_EMIT q->isConnectedChanged(); + } } KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent) @@ -197,7 +223,7 @@ QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelect { Q_D(const KModelIndexProxyMapper); - if (selection.isEmpty()) { + if (selection.isEmpty() || !d->mConnected) { return QItemSelection(); } @@ -251,7 +277,7 @@ QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelect { Q_D(const KModelIndexProxyMapper); - if (selection.isEmpty()) { + if (selection.isEmpty() || !d->mConnected) { return QItemSelection(); } @@ -298,3 +324,8 @@ QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelect return seekSelection; } +bool KModelIndexProxyMapper::isConnected() const +{ + Q_D(const KModelIndexProxyMapper); + return d->mConnected; +} diff --git a/src/kmodelindexproxymapper.h b/src/kmodelindexproxymapper.h index 9c4a63d..95510b7 100644 --- a/src/kmodelindexproxymapper.h +++ b/src/kmodelindexproxymapper.h @@ -2,6 +2,8 @@ Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net, author Stephen Kelly + Copyright (c) 2016 Ableton AG + Author Stephen Kelly This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by @@ -73,12 +75,22 @@ class KModelIndexProxyMapperPrivate; * Proxy 2 Proxy 4 * @endverbatim * + * The isConnected property indicates whether there is a + * path from the left side to the right side. + * * @author Stephen Kelly * */ class KITEMMODELS_EXPORT KModelIndexProxyMapper : public QObject { Q_OBJECT + + /** + * Indicates whether there is a chain that can be followed from leftModel to rightModel. + * + * This value can change if the sourceModel of an intermediate proxy is changed. + */ + Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged) public: /** * Constructor @@ -107,6 +119,11 @@ public: */ QItemSelection mapSelectionRightToLeft(const QItemSelection &selection) const; + bool isConnected() const; + +Q_SIGNALS: + void isConnectedChanged(); + private: //@cond PRIVATE Q_DECLARE_PRIVATE(KModelIndexProxyMapper)