2 Copyright (C) 2010 Klarälvdalens Datakonsult AB,
3 a KDAB Group company, info@kdab.net,
4 author Stephen Kelly <stephen@kdab.com>
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Library General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 #include "kmodelindexproxymapper.hxx"
24 #include <QAbstractItemModel>
25 #include <QWeakPointer>
26 #include <QAbstractProxyModel>
27 #include <QItemSelectionModel>
29 // #include "kdebug.h"
31 class KModelIndexProxyMapperPrivate
33 KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq)
34 : q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel)
39 void createProxyChain();
42 bool assertSelectionValid(const QItemSelection &selection) const {
43 foreach(const QItemSelectionRange &range, selection) {
44 if (!range.isValid()) {
45 // kDebug() << selection << m_leftModel << m_rightModel << m_proxyChainDown << m_proxyChainUp;
47 Q_ASSERT(range.isValid());
52 Q_DECLARE_PUBLIC(KModelIndexProxyMapper)
53 KModelIndexProxyMapper * const q_ptr;
55 QList<QWeakPointer<const QAbstractProxyModel> > m_proxyChainUp;
56 QList<QWeakPointer<const QAbstractProxyModel> > m_proxyChainDown;
58 QWeakPointer<const QAbstractItemModel> m_leftModel;
59 QWeakPointer<const QAbstractItemModel> m_rightModel;
65 The idea here is that <tt>this</tt> selection model and proxySelectionModel might be in different parts of the
66 proxy chain. We need to build up to two chains of proxy models to create mappings between them.
77 Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other.
93 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
94 already in the first chain.
96 Stephen Kelly, 30 March 2010.
99 void KModelIndexProxyMapperPrivate::createProxyChain()
101 QWeakPointer<const QAbstractItemModel> targetModel = m_rightModel;
106 if (m_leftModel == targetModel)
109 QList<QWeakPointer<const QAbstractProxyModel> > proxyChainDown;
110 QWeakPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel*>(targetModel.data());
111 while( selectionTargetProxyModel )
113 proxyChainDown.prepend( selectionTargetProxyModel );
115 selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel*>(selectionTargetProxyModel.data()->sourceModel());
117 if (selectionTargetProxyModel.data() == m_leftModel.data())
119 m_proxyChainDown = proxyChainDown;
124 QWeakPointer<const QAbstractItemModel> sourceModel = m_leftModel;
125 QWeakPointer<const QAbstractProxyModel> sourceProxyModel = qobject_cast<const QAbstractProxyModel*>(sourceModel.data());
127 while(sourceProxyModel)
129 m_proxyChainUp.append(sourceProxyModel);
131 sourceProxyModel = qobject_cast<const QAbstractProxyModel*>(sourceProxyModel.data()->sourceModel());
133 const int targetIndex = proxyChainDown.indexOf(sourceProxyModel);
135 if (targetIndex != -1)
137 m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size());
141 m_proxyChainDown = proxyChainDown;
142 Q_ASSERT(assertValid());
145 bool KModelIndexProxyMapperPrivate::assertValid()
147 if ( m_proxyChainDown.isEmpty())
149 Q_ASSERT( !m_proxyChainUp.isEmpty() );
150 Q_ASSERT( m_proxyChainUp.last().data()->sourceModel() == m_rightModel.data() );
152 else if ( m_proxyChainUp.isEmpty())
154 Q_ASSERT( !m_proxyChainDown.isEmpty() );
155 Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_leftModel.data() );
157 Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_proxyChainUp.last().data()->sourceModel() );
162 KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel* leftModel, const QAbstractItemModel* rightModel, QObject* parent)
163 : QObject(parent), d_ptr( new KModelIndexProxyMapperPrivate(leftModel, rightModel, this) )
168 KModelIndexProxyMapper::~KModelIndexProxyMapper()
173 QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex& index) const
175 const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index));
176 if (selection.isEmpty())
177 return QModelIndex();
179 return selection.indexes().first();
182 QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex& index) const
184 const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index));
185 if (selection.isEmpty())
186 return QModelIndex();
188 return selection.indexes().first();
191 // QAbstractProxyModel::mapSelectionFromSource creates invalid ranges to we filter
192 // those out manually in a loop. Hopefully fixed in Qt 4.7.2, so we ifdef it out.
193 // http://qt.gitorious.org/qt/qt/merge_requests/2474
194 // http://qt.gitorious.org/qt/qt/merge_requests/831
195 #if QT_VERSION < 0x040702
196 #define RANGE_FIX_HACK
199 #ifdef RANGE_FIX_HACK
200 static QItemSelection removeInvalidRanges(const QItemSelection &selection)
202 QItemSelection result;
203 Q_FOREACH(const QItemSelectionRange &range, selection)
205 if (!range.isValid())
213 QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection& selection) const
215 Q_D(const KModelIndexProxyMapper);
217 if (selection.isEmpty())
218 return QItemSelection();
220 // if (selection.first().model() != d->m_leftModel.data())
221 // kDebug() << "FAIL" << selection.first().model() << d->m_leftModel.data() << d->m_rightModel.data();
222 Q_ASSERT(selection.first().model() == d->m_leftModel.data());
224 QItemSelection seekSelection = selection;
225 Q_ASSERT(d->assertSelectionValid(seekSelection));
226 QListIterator<QWeakPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp);
228 while (iUp.hasNext())
230 const QWeakPointer<const QAbstractProxyModel> proxy = iUp.next();
232 return QItemSelection();
233 seekSelection = proxy.data()->mapSelectionToSource(seekSelection);
235 #ifdef RANGE_FIX_HACK
236 seekSelection = removeInvalidRanges(seekSelection);
238 Q_ASSERT(d->assertSelectionValid(seekSelection));
241 QListIterator<QWeakPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown);
243 while (iDown.hasNext())
245 const QWeakPointer<const QAbstractProxyModel> proxy = iDown.next();
247 return QItemSelection();
248 seekSelection = proxy.data()->mapSelectionFromSource(seekSelection);
250 #ifdef RANGE_FIX_HACK
251 seekSelection = removeInvalidRanges(seekSelection);
253 Q_ASSERT(d->assertSelectionValid(seekSelection));
256 Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel.data() ) || true );
257 return seekSelection;
260 QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection& selection) const
262 Q_D(const KModelIndexProxyMapper);
264 if (selection.isEmpty())
265 return QItemSelection();
267 // if (selection.first().model() != d->m_rightModel.data())
268 // kDebug() << "FAIL" << selection.first().model() << d->m_leftModel.data() << d->m_rightModel.data();
269 Q_ASSERT(selection.first().model() == d->m_rightModel.data());
271 QItemSelection seekSelection = selection;
272 Q_ASSERT(d->assertSelectionValid(seekSelection));
273 QListIterator<QWeakPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown);
276 while (iDown.hasPrevious())
278 const QWeakPointer<const QAbstractProxyModel> proxy = iDown.previous();
280 return QItemSelection();
281 seekSelection = proxy.data()->mapSelectionToSource(seekSelection);
283 #ifdef RANGE_FIX_HACK
284 seekSelection = removeInvalidRanges(seekSelection);
286 Q_ASSERT(d->assertSelectionValid(seekSelection));
289 QListIterator<QWeakPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp);
292 while (iUp.hasPrevious())
294 const QWeakPointer<const QAbstractProxyModel> proxy = iUp.previous();
296 return QItemSelection();
297 seekSelection = proxy.data()->mapSelectionFromSource(seekSelection);
299 #ifdef RANGE_FIX_HACK
300 seekSelection = removeInvalidRanges(seekSelection);
302 Q_ASSERT(d->assertSelectionValid(seekSelection));
305 Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel.data() ) || true );
306 return seekSelection;
309 // #include "kmodelindexproxymapper_moc.cxx"
310 // #include "kmodelindexproxymapper.moc"