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 <QtCore/QAbstractItemModel>
25 #include <QtCore/QWeakPointer>
26 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
27 #include <QtGui/QAbstractProxyModel>
28 #include <QtGui/QItemSelectionModel>
30 #include <QtCore/QAbstractProxyModel>
31 #include <QtCore/QItemSelectionModel>
34 // #include "kdebug.h"
36 class KModelIndexProxyMapperPrivate
38 KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq)
39 : q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel)
44 void createProxyChain();
47 bool assertSelectionValid(const QItemSelection &selection) const {
48 foreach(const QItemSelectionRange &range, selection) {
49 if (!range.isValid()) {
50 // kDebug() << selection << m_leftModel << m_rightModel << m_proxyChainDown << m_proxyChainUp;
52 Q_ASSERT(range.isValid());
57 Q_DECLARE_PUBLIC(KModelIndexProxyMapper)
58 KModelIndexProxyMapper * const q_ptr;
60 QList<QWeakPointer<const QAbstractProxyModel> > m_proxyChainUp;
61 QList<QWeakPointer<const QAbstractProxyModel> > m_proxyChainDown;
63 QWeakPointer<const QAbstractItemModel> m_leftModel;
64 QWeakPointer<const QAbstractItemModel> m_rightModel;
70 The idea here is that <tt>this</tt> selection model and proxySelectionModel might be in different parts of the
71 proxy chain. We need to build up to two chains of proxy models to create mappings between them.
82 Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other.
98 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
99 already in the first chain.
101 Stephen Kelly, 30 March 2010.
104 void KModelIndexProxyMapperPrivate::createProxyChain()
106 QWeakPointer<const QAbstractItemModel> targetModel = m_rightModel;
111 if (m_leftModel == targetModel)
114 QList<QWeakPointer<const QAbstractProxyModel> > proxyChainDown;
115 QWeakPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel*>(targetModel.data());
116 while( selectionTargetProxyModel )
118 proxyChainDown.prepend( selectionTargetProxyModel );
120 selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel*>(selectionTargetProxyModel.data()->sourceModel());
122 if (selectionTargetProxyModel.data() == m_leftModel.data())
124 m_proxyChainDown = proxyChainDown;
129 QWeakPointer<const QAbstractItemModel> sourceModel = m_leftModel;
130 QWeakPointer<const QAbstractProxyModel> sourceProxyModel = qobject_cast<const QAbstractProxyModel*>(sourceModel.data());
132 while(sourceProxyModel)
134 m_proxyChainUp.append(sourceProxyModel);
136 sourceProxyModel = qobject_cast<const QAbstractProxyModel*>(sourceProxyModel.data()->sourceModel());
138 const int targetIndex = proxyChainDown.indexOf(sourceProxyModel);
140 if (targetIndex != -1)
142 m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size());
146 m_proxyChainDown = proxyChainDown;
147 Q_ASSERT(assertValid());
150 bool KModelIndexProxyMapperPrivate::assertValid()
152 if ( m_proxyChainDown.isEmpty())
154 Q_ASSERT( !m_proxyChainUp.isEmpty() );
155 Q_ASSERT( m_proxyChainUp.last().data()->sourceModel() == m_rightModel.data() );
157 else if ( m_proxyChainUp.isEmpty())
159 Q_ASSERT( !m_proxyChainDown.isEmpty() );
160 Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_leftModel.data() );
162 Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_proxyChainUp.last().data()->sourceModel() );
167 KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel* leftModel, const QAbstractItemModel* rightModel, QObject* parent)
168 : QObject(parent), d_ptr( new KModelIndexProxyMapperPrivate(leftModel, rightModel, this) )
173 KModelIndexProxyMapper::~KModelIndexProxyMapper()
178 QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex& index) const
180 const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index));
181 if (selection.isEmpty())
182 return QModelIndex();
184 return selection.indexes().first();
187 QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex& index) const
189 const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index));
190 if (selection.isEmpty())
191 return QModelIndex();
193 return selection.indexes().first();
196 // QAbstractProxyModel::mapSelectionFromSource creates invalid ranges to we filter
197 // those out manually in a loop. Hopefully fixed in Qt 4.7.2, so we ifdef it out.
198 // http://qt.gitorious.org/qt/qt/merge_requests/2474
199 // http://qt.gitorious.org/qt/qt/merge_requests/831
200 #if QT_VERSION < 0x040702
201 #define RANGE_FIX_HACK
204 #ifdef RANGE_FIX_HACK
205 static QItemSelection removeInvalidRanges(const QItemSelection &selection)
207 QItemSelection result;
208 Q_FOREACH(const QItemSelectionRange &range, selection)
210 if (!range.isValid())
218 QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection& selection) const
220 Q_D(const KModelIndexProxyMapper);
222 if (selection.isEmpty())
223 return QItemSelection();
225 // if (selection.first().model() != d->m_leftModel.data())
226 // kDebug() << "FAIL" << selection.first().model() << d->m_leftModel.data() << d->m_rightModel.data();
227 Q_ASSERT(selection.first().model() == d->m_leftModel.data());
229 QItemSelection seekSelection = selection;
230 Q_ASSERT(d->assertSelectionValid(seekSelection));
231 QListIterator<QWeakPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp);
233 while (iUp.hasNext())
235 const QWeakPointer<const QAbstractProxyModel> proxy = iUp.next();
237 return QItemSelection();
238 seekSelection = proxy.data()->mapSelectionToSource(seekSelection);
240 #ifdef RANGE_FIX_HACK
241 seekSelection = removeInvalidRanges(seekSelection);
243 Q_ASSERT(d->assertSelectionValid(seekSelection));
246 QListIterator<QWeakPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown);
248 while (iDown.hasNext())
250 const QWeakPointer<const QAbstractProxyModel> proxy = iDown.next();
252 return QItemSelection();
253 seekSelection = proxy.data()->mapSelectionFromSource(seekSelection);
255 #ifdef RANGE_FIX_HACK
256 seekSelection = removeInvalidRanges(seekSelection);
258 Q_ASSERT(d->assertSelectionValid(seekSelection));
261 Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel.data() ) || true );
262 return seekSelection;
265 QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection& selection) const
267 Q_D(const KModelIndexProxyMapper);
269 if (selection.isEmpty())
270 return QItemSelection();
272 // if (selection.first().model() != d->m_rightModel.data())
273 // kDebug() << "FAIL" << selection.first().model() << d->m_leftModel.data() << d->m_rightModel.data();
274 Q_ASSERT(selection.first().model() == d->m_rightModel.data());
276 QItemSelection seekSelection = selection;
277 Q_ASSERT(d->assertSelectionValid(seekSelection));
278 QListIterator<QWeakPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown);
281 while (iDown.hasPrevious())
283 const QWeakPointer<const QAbstractProxyModel> proxy = iDown.previous();
285 return QItemSelection();
286 seekSelection = proxy.data()->mapSelectionToSource(seekSelection);
288 #ifdef RANGE_FIX_HACK
289 seekSelection = removeInvalidRanges(seekSelection);
291 Q_ASSERT(d->assertSelectionValid(seekSelection));
294 QListIterator<QWeakPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp);
297 while (iUp.hasPrevious())
299 const QWeakPointer<const QAbstractProxyModel> proxy = iUp.previous();
301 return QItemSelection();
302 seekSelection = proxy.data()->mapSelectionFromSource(seekSelection);
304 #ifdef RANGE_FIX_HACK
305 seekSelection = removeInvalidRanges(seekSelection);
307 Q_ASSERT(d->assertSelectionValid(seekSelection));
310 Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel.data() ) || true );
311 return seekSelection;
314 // #include "kmodelindexproxymapper_moc.cxx"
315 // #include "kmodelindexproxymapper.moc"