Salome HOME
28bc187ab3fcb98c7d21ba76c4c367a5196c7ac4
[modules/hexablock.git] / src / HEXABLOCKGUI / kmodelindexproxymapper.cxx
1 /*
2     Copyright (C) 2010 Klarälvdalens Datakonsult AB,
3         a KDAB Group company, info@kdab.net,
4         author Stephen Kelly <stephen@kdab.com>
5
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.
10
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.
15
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
19     02110-1301, USA.
20 */
21
22 #include "kmodelindexproxymapper.hxx"
23
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>
29 #else 
30 #include <QtCore/QAbstractProxyModel>
31 #include <QtCore/QItemSelectionModel>
32 #endif
33
34 // #include "kdebug.h"
35
36 class KModelIndexProxyMapperPrivate
37 {
38   KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq)
39     : q_ptr(qq), m_leftModel(leftModel), m_rightModel(rightModel)
40   {
41     createProxyChain();
42   }
43
44   void createProxyChain();
45   bool assertValid();
46
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;
51       }
52       Q_ASSERT(range.isValid());
53     }
54     return true;
55   }
56
57   Q_DECLARE_PUBLIC(KModelIndexProxyMapper)
58   KModelIndexProxyMapper * const q_ptr;
59
60   QList<QWeakPointer<const QAbstractProxyModel> > m_proxyChainUp;
61   QList<QWeakPointer<const QAbstractProxyModel> > m_proxyChainDown;
62
63   QWeakPointer<const QAbstractItemModel> m_leftModel;
64   QWeakPointer<const QAbstractItemModel> m_rightModel;
65 };
66
67
68 /*
69
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.
72
73   Example 1:
74
75      Root model
76           |
77         /    \
78     Proxy 1   Proxy 3
79        |       |
80     Proxy 2   Proxy 4
81
82   Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other.
83
84   Example 2:
85
86      Root model
87           |
88         Proxy 1
89           |
90         Proxy 2
91         /     \
92     Proxy 3   Proxy 6
93        |       |
94     Proxy 4   Proxy 7
95        |
96     Proxy 5
97
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.
100
101   Stephen Kelly, 30 March 2010.
102 */
103
104 void KModelIndexProxyMapperPrivate::createProxyChain()
105 {
106   QWeakPointer<const QAbstractItemModel> targetModel = m_rightModel;
107
108   if (!targetModel)
109     return;
110
111   if (m_leftModel == targetModel)
112     return;
113
114   QList<QWeakPointer<const QAbstractProxyModel> > proxyChainDown;
115   QWeakPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel*>(targetModel.data());
116   while( selectionTargetProxyModel )
117   {
118     proxyChainDown.prepend( selectionTargetProxyModel );
119
120     selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel*>(selectionTargetProxyModel.data()->sourceModel());
121
122     if (selectionTargetProxyModel.data() == m_leftModel.data())
123     {
124       m_proxyChainDown = proxyChainDown;
125       return;
126     }
127   }
128
129   QWeakPointer<const QAbstractItemModel> sourceModel = m_leftModel;
130   QWeakPointer<const QAbstractProxyModel> sourceProxyModel = qobject_cast<const QAbstractProxyModel*>(sourceModel.data());
131
132   while(sourceProxyModel)
133   {
134     m_proxyChainUp.append(sourceProxyModel);
135
136     sourceProxyModel = qobject_cast<const QAbstractProxyModel*>(sourceProxyModel.data()->sourceModel());
137
138     const int targetIndex = proxyChainDown.indexOf(sourceProxyModel);
139
140     if (targetIndex != -1)
141     {
142       m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, proxyChainDown.size());
143       return;
144     }
145   }
146   m_proxyChainDown = proxyChainDown;
147   Q_ASSERT(assertValid());
148 }
149
150 bool KModelIndexProxyMapperPrivate::assertValid()
151 {
152   if ( m_proxyChainDown.isEmpty())
153   {
154     Q_ASSERT( !m_proxyChainUp.isEmpty() );
155     Q_ASSERT( m_proxyChainUp.last().data()->sourceModel() == m_rightModel.data() );
156   }
157   else if ( m_proxyChainUp.isEmpty())
158   {
159     Q_ASSERT( !m_proxyChainDown.isEmpty() );
160     Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_leftModel.data() );
161   } else {
162     Q_ASSERT( m_proxyChainDown.first().data()->sourceModel() == m_proxyChainUp.last().data()->sourceModel() );
163   }
164   return true;
165 }
166
167 KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel* leftModel, const QAbstractItemModel* rightModel, QObject* parent)
168   : QObject(parent), d_ptr( new KModelIndexProxyMapperPrivate(leftModel, rightModel, this) )
169 {
170
171 }
172
173 KModelIndexProxyMapper::~KModelIndexProxyMapper()
174 {
175   delete d_ptr;
176 }
177
178 QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex& index) const
179 {
180   const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index));
181   if (selection.isEmpty())
182     return QModelIndex();
183
184   return selection.indexes().first();
185 }
186
187 QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex& index) const
188 {
189   const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index));
190   if (selection.isEmpty())
191     return QModelIndex();
192
193   return selection.indexes().first();
194 }
195
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
202 #endif
203
204 #ifdef RANGE_FIX_HACK
205 static QItemSelection removeInvalidRanges(const QItemSelection &selection)
206 {
207   QItemSelection result;
208   Q_FOREACH(const QItemSelectionRange &range, selection)
209   {
210     if (!range.isValid())
211       continue;
212     result << range;
213   }
214   return result;
215 }
216 #endif
217
218 QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection& selection) const
219 {
220   Q_D(const KModelIndexProxyMapper);
221
222   if (selection.isEmpty())
223     return QItemSelection();
224
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());
228
229   QItemSelection seekSelection = selection;
230   Q_ASSERT(d->assertSelectionValid(seekSelection));
231   QListIterator<QWeakPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp);
232
233   while (iUp.hasNext())
234   {
235     const QWeakPointer<const QAbstractProxyModel> proxy = iUp.next();
236     if (!proxy.data())
237       return QItemSelection();
238     seekSelection = proxy.data()->mapSelectionToSource(seekSelection);
239
240 #ifdef RANGE_FIX_HACK
241     seekSelection = removeInvalidRanges(seekSelection);
242 #endif
243     Q_ASSERT(d->assertSelectionValid(seekSelection));
244   }
245
246   QListIterator<QWeakPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown);
247
248   while (iDown.hasNext())
249   {
250     const QWeakPointer<const QAbstractProxyModel> proxy = iDown.next();
251     if (!proxy.data())
252       return QItemSelection();
253     seekSelection = proxy.data()->mapSelectionFromSource(seekSelection);
254
255 #ifdef RANGE_FIX_HACK
256     seekSelection = removeInvalidRanges(seekSelection);
257 #endif
258     Q_ASSERT(d->assertSelectionValid(seekSelection));
259   }
260
261   Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel.data() ) || true );
262   return seekSelection;
263 }
264
265 QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection& selection) const
266 {
267   Q_D(const KModelIndexProxyMapper);
268
269   if (selection.isEmpty())
270     return QItemSelection();
271
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());
275
276   QItemSelection seekSelection = selection;
277   Q_ASSERT(d->assertSelectionValid(seekSelection));
278   QListIterator<QWeakPointer<const QAbstractProxyModel> > iDown(d->m_proxyChainDown);
279
280   iDown.toBack();
281   while (iDown.hasPrevious())
282   {
283     const QWeakPointer<const QAbstractProxyModel> proxy = iDown.previous();
284     if (!proxy.data())
285       return QItemSelection();
286     seekSelection = proxy.data()->mapSelectionToSource(seekSelection);
287
288 #ifdef RANGE_FIX_HACK
289     seekSelection = removeInvalidRanges(seekSelection);
290 #endif
291     Q_ASSERT(d->assertSelectionValid(seekSelection));
292   }
293
294   QListIterator<QWeakPointer<const QAbstractProxyModel> > iUp(d->m_proxyChainUp);
295
296   iUp.toBack();
297   while (iUp.hasPrevious())
298   {
299     const QWeakPointer<const QAbstractProxyModel> proxy = iUp.previous();
300     if (!proxy.data())
301       return QItemSelection();
302     seekSelection = proxy.data()->mapSelectionFromSource(seekSelection);
303
304 #ifdef RANGE_FIX_HACK
305     seekSelection = removeInvalidRanges(seekSelection);
306 #endif
307     Q_ASSERT(d->assertSelectionValid(seekSelection));
308   }
309
310   Q_ASSERT( ( !seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel.data() ) || true );
311   return seekSelection;
312 }
313
314 // #include "kmodelindexproxymapper_moc.cxx"
315 // #include "kmodelindexproxymapper.moc"