File indexing completed on 2024-05-19 05:47:27

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