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 }