File indexing completed on 2024-05-05 03:56:42
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"