File indexing completed on 2024-07-21 06:38:22

0001 /*
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0003     SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
0004     SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
0005     SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kmodelindexproxymapper.h"
0011 #include "kitemmodels_debug.h"
0012 
0013 #include <QAbstractItemModel>
0014 #include <QAbstractProxyModel>
0015 #include <QItemSelectionModel>
0016 #include <QPointer>
0017 
0018 class KModelIndexProxyMapperPrivate
0019 {
0020     KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq)
0021         : q_ptr(qq)
0022         , m_leftModel(leftModel)
0023         , m_rightModel(rightModel)
0024         , mConnected(false)
0025     {
0026         createProxyChain();
0027     }
0028 
0029     void createProxyChain();
0030     void checkConnected();
0031     void setConnected(bool connected);
0032 
0033     bool assertSelectionValid(const QItemSelection &selection) const
0034     {
0035         for (const QItemSelectionRange &range : selection) {
0036             if (!range.isValid()) {
0037                 qCDebug(KITEMMODELS_LOG) << selection << m_leftModel << m_rightModel << m_proxyChainDown << m_proxyChainUp;
0038             }
0039             Q_ASSERT(range.isValid());
0040         }
0041         return true;
0042     }
0043 
0044     Q_DECLARE_PUBLIC(KModelIndexProxyMapper)
0045     KModelIndexProxyMapper *const q_ptr;
0046 
0047     QList<QPointer<const QAbstractProxyModel>> m_proxyChainUp;
0048     QList<QPointer<const QAbstractProxyModel>> m_proxyChainDown;
0049 
0050     QPointer<const QAbstractItemModel> m_leftModel;
0051     QPointer<const QAbstractItemModel> m_rightModel;
0052 
0053     bool mConnected;
0054 };
0055 
0056 /*
0057 
0058   The idea here is that <tt>this</tt> selection model and proxySelectionModel might be in different parts of the
0059   proxy chain. We need to build up to two chains of proxy models to create mappings between them.
0060 
0061   Example 1:
0062 
0063      Root model
0064           |
0065         /    \
0066     Proxy 1   Proxy 3
0067        |       |
0068     Proxy 2   Proxy 4
0069 
0070   Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other.
0071 
0072   Example 2:
0073 
0074      Root model
0075           |
0076         Proxy 1
0077           |
0078         Proxy 2
0079         /     \
0080     Proxy 3   Proxy 6
0081        |       |
0082     Proxy 4   Proxy 7
0083        |
0084     Proxy 5
0085 
0086   We first build the chain from 1 to 5, then start building the chain from 7 to 1. We stop when we find that proxy 2 is
0087   already in the first chain.
0088 
0089   Stephen Kelly, 30 March 2010.
0090 */
0091 
0092 void KModelIndexProxyMapperPrivate::createProxyChain()
0093 {
0094     for (auto p : std::as_const(m_proxyChainUp)) {
0095         p->disconnect(q_ptr);
0096     }
0097     for (auto p : std::as_const(m_proxyChainDown)) {
0098         p->disconnect(q_ptr);
0099     }
0100     m_proxyChainUp.clear();
0101     m_proxyChainDown.clear();
0102     QPointer<const QAbstractItemModel> targetModel = m_rightModel;
0103 
0104     QList<QPointer<const QAbstractProxyModel>> proxyChainDown;
0105     QPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(targetModel);
0106     while (selectionTargetProxyModel) {
0107         proxyChainDown.prepend(selectionTargetProxyModel);
0108         QObject::connect(selectionTargetProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] {
0109             createProxyChain();
0110         });
0111 
0112         selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(selectionTargetProxyModel->sourceModel());
0113 
0114         if (selectionTargetProxyModel == m_leftModel) {
0115             m_proxyChainDown = proxyChainDown;
0116             checkConnected();
0117             return;
0118         }
0119     }
0120 
0121     QPointer<const QAbstractItemModel> sourceModel = m_leftModel;
0122     QPointer<const QAbstractProxyModel> sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(sourceModel);
0123 
0124     while (sourceProxyModel) {
0125         m_proxyChainUp.append(sourceProxyModel);
0126         QObject::connect(sourceProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] {
0127             createProxyChain();
0128         });
0129 
0130         sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(sourceProxyModel->sourceModel());
0131 
0132         const int targetIndex = proxyChainDown.indexOf(sourceProxyModel);
0133 
0134         if (targetIndex != -1) {
0135             m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size());
0136             checkConnected();
0137             return;
0138         }
0139     }
0140     m_proxyChainDown = proxyChainDown;
0141     checkConnected();
0142 }
0143 
0144 void KModelIndexProxyMapperPrivate::checkConnected()
0145 {
0146     auto konamiRight = m_proxyChainUp.isEmpty() ? m_leftModel : m_proxyChainUp.last()->sourceModel();
0147     auto konamiLeft = m_proxyChainDown.isEmpty() ? m_rightModel : m_proxyChainDown.first()->sourceModel();
0148     setConnected(konamiLeft && (konamiLeft == konamiRight));
0149 }
0150 
0151 void KModelIndexProxyMapperPrivate::setConnected(bool connected)
0152 {
0153     if (mConnected != connected) {
0154         Q_Q(KModelIndexProxyMapper);
0155         mConnected = connected;
0156         Q_EMIT q->isConnectedChanged();
0157     }
0158 }
0159 
0160 KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent)
0161     : QObject(parent)
0162     , d_ptr(new KModelIndexProxyMapperPrivate(leftModel, rightModel, this))
0163 {
0164 }
0165 
0166 KModelIndexProxyMapper::~KModelIndexProxyMapper() = default;
0167 
0168 QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex &index) const
0169 {
0170     const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index));
0171     if (selection.isEmpty()) {
0172         return QModelIndex();
0173     }
0174 
0175     return selection.indexes().first();
0176 }
0177 
0178 QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex &index) const
0179 {
0180     const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index));
0181     if (selection.isEmpty()) {
0182         return QModelIndex();
0183     }
0184 
0185     return selection.indexes().first();
0186 }
0187 
0188 QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection &selection) const
0189 {
0190     Q_D(const KModelIndexProxyMapper);
0191 
0192     if (selection.isEmpty() || !d->mConnected) {
0193         return QItemSelection();
0194     }
0195 
0196     if (selection.first().model() != d->m_leftModel) {
0197         qCDebug(KITEMMODELS_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel;
0198     }
0199     Q_ASSERT(selection.first().model() == d->m_leftModel);
0200 
0201     QItemSelection seekSelection = selection;
0202     Q_ASSERT(d->assertSelectionValid(seekSelection));
0203     QListIterator<QPointer<const QAbstractProxyModel>> iUp(d->m_proxyChainUp);
0204 
0205     while (iUp.hasNext()) {
0206         const QPointer<const QAbstractProxyModel> proxy = iUp.next();
0207         if (!proxy) {
0208             return QItemSelection();
0209         }
0210 
0211         Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy);
0212         seekSelection = proxy->mapSelectionToSource(seekSelection);
0213         Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel());
0214 
0215         Q_ASSERT(d->assertSelectionValid(seekSelection));
0216     }
0217 
0218     QListIterator<QPointer<const QAbstractProxyModel>> iDown(d->m_proxyChainDown);
0219 
0220     while (iDown.hasNext()) {
0221         const QPointer<const QAbstractProxyModel> proxy = iDown.next();
0222         if (!proxy) {
0223             return QItemSelection();
0224         }
0225         Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel());
0226         seekSelection = proxy->mapSelectionFromSource(seekSelection);
0227         Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy);
0228 
0229         Q_ASSERT(d->assertSelectionValid(seekSelection));
0230     }
0231 
0232     Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel) || true);
0233     return seekSelection;
0234 }
0235 
0236 QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection &selection) const
0237 {
0238     Q_D(const KModelIndexProxyMapper);
0239 
0240     if (selection.isEmpty() || !d->mConnected) {
0241         return QItemSelection();
0242     }
0243 
0244     if (selection.first().model() != d->m_rightModel) {
0245         qCDebug(KITEMMODELS_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel;
0246     }
0247     Q_ASSERT(selection.first().model() == d->m_rightModel);
0248 
0249     QItemSelection seekSelection = selection;
0250     Q_ASSERT(d->assertSelectionValid(seekSelection));
0251     QListIterator<QPointer<const QAbstractProxyModel>> iDown(d->m_proxyChainDown);
0252 
0253     iDown.toBack();
0254     while (iDown.hasPrevious()) {
0255         const QPointer<const QAbstractProxyModel> proxy = iDown.previous();
0256         if (!proxy) {
0257             return QItemSelection();
0258         }
0259         seekSelection = proxy->mapSelectionToSource(seekSelection);
0260 
0261         Q_ASSERT(d->assertSelectionValid(seekSelection));
0262     }
0263 
0264     QListIterator<QPointer<const QAbstractProxyModel>> iUp(d->m_proxyChainUp);
0265 
0266     iUp.toBack();
0267     while (iUp.hasPrevious()) {
0268         const QPointer<const QAbstractProxyModel> proxy = iUp.previous();
0269         if (!proxy) {
0270             return QItemSelection();
0271         }
0272         seekSelection = proxy->mapSelectionFromSource(seekSelection);
0273 
0274         Q_ASSERT(d->assertSelectionValid(seekSelection));
0275     }
0276 
0277     Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel) || true);
0278     return seekSelection;
0279 }
0280 
0281 bool KModelIndexProxyMapper::isConnected() const
0282 {
0283     Q_D(const KModelIndexProxyMapper);
0284     return d->mConnected;
0285 }
0286 
0287 #include "moc_kmodelindexproxymapper.cpp"