1/*
2 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
3 SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
4 SPDX-FileCopyrightText: 2016 Ableton AG <info@ableton.com>
5 SPDX-FileContributor: Stephen Kelly <stephen.kelly@ableton.com>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kmodelindexproxymapper.h"
11#include "kitemmodels_debug.h"
12
13#include <QAbstractItemModel>
14#include <QAbstractProxyModel>
15#include <QItemSelectionModel>
16#include <QPointer>
17
18class KModelIndexProxyMapperPrivate
19{
20 KModelIndexProxyMapperPrivate(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, KModelIndexProxyMapper *qq)
21 : q_ptr(qq)
22 , m_leftModel(leftModel)
23 , m_rightModel(rightModel)
24 , mConnected(false)
25 {
26 createProxyChain();
27 }
28
29 void createProxyChain();
30 void checkConnected();
31 void setConnected(bool connected);
32
33 bool assertSelectionValid(const QItemSelection &selection) const
34 {
35 for (const QItemSelectionRange &range : selection) {
36 if (!range.isValid()) {
37 qCDebug(KITEMMODELS_LOG) << selection << m_leftModel << m_rightModel << m_proxyChainDown << m_proxyChainUp;
38 }
39 Q_ASSERT(range.isValid());
40 }
41 return true;
42 }
43
44 Q_DECLARE_PUBLIC(KModelIndexProxyMapper)
45 KModelIndexProxyMapper *const q_ptr;
46
47 QList<QPointer<const QAbstractProxyModel>> m_proxyChainUp;
48 QList<QPointer<const QAbstractProxyModel>> m_proxyChainDown;
49
50 QPointer<const QAbstractItemModel> m_leftModel;
51 QPointer<const QAbstractItemModel> m_rightModel;
52
53 bool mConnected;
54};
55
56/*
57
58 The idea here is that <tt>this</tt> selection model and proxySelectionModel might be in different parts of the
59 proxy chain. We need to build up to two chains of proxy models to create mappings between them.
60
61 Example 1:
62
63 Root model
64 |
65 / \
66 Proxy 1 Proxy 3
67 | |
68 Proxy 2 Proxy 4
69
70 Need Proxy 1 and Proxy 2 in one chain, and Proxy 3 and 4 in the other.
71
72 Example 2:
73
74 Root model
75 |
76 Proxy 1
77 |
78 Proxy 2
79 / \
80 Proxy 3 Proxy 6
81 | |
82 Proxy 4 Proxy 7
83 |
84 Proxy 5
85
86 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
87 already in the first chain.
88
89 Stephen Kelly, 30 March 2010.
90*/
91
92void KModelIndexProxyMapperPrivate::createProxyChain()
93{
94 for (auto p : std::as_const(t&: m_proxyChainUp)) {
95 p->disconnect(receiver: q_ptr);
96 }
97 for (auto p : std::as_const(t&: m_proxyChainDown)) {
98 p->disconnect(receiver: q_ptr);
99 }
100 m_proxyChainUp.clear();
101 m_proxyChainDown.clear();
102 QPointer<const QAbstractItemModel> targetModel = m_rightModel;
103
104 QList<QPointer<const QAbstractProxyModel>> proxyChainDown;
105 QPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(object: targetModel);
106 while (selectionTargetProxyModel) {
107 proxyChainDown.prepend(t: selectionTargetProxyModel);
108 QObject::connect(sender: selectionTargetProxyModel.data(), signal: &QAbstractProxyModel::sourceModelChanged, context: q_ptr, slot: [this] {
109 createProxyChain();
110 });
111
112 selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(object: selectionTargetProxyModel->sourceModel());
113
114 if (selectionTargetProxyModel == m_leftModel) {
115 m_proxyChainDown = proxyChainDown;
116 checkConnected();
117 return;
118 }
119 }
120
121 QPointer<const QAbstractItemModel> sourceModel = m_leftModel;
122 QPointer<const QAbstractProxyModel> sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(object: sourceModel);
123
124 while (sourceProxyModel) {
125 m_proxyChainUp.append(t: sourceProxyModel);
126 QObject::connect(sender: sourceProxyModel.data(), signal: &QAbstractProxyModel::sourceModelChanged, context: q_ptr, slot: [this] {
127 createProxyChain();
128 });
129
130 sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(object: sourceProxyModel->sourceModel());
131
132 const int targetIndex = proxyChainDown.indexOf(t: sourceProxyModel);
133
134 if (targetIndex != -1) {
135 m_proxyChainDown = proxyChainDown.mid(pos: targetIndex + 1, len: proxyChainDown.size());
136 checkConnected();
137 return;
138 }
139 }
140 m_proxyChainDown = proxyChainDown;
141 checkConnected();
142}
143
144void KModelIndexProxyMapperPrivate::checkConnected()
145{
146 auto konamiRight = m_proxyChainUp.isEmpty() ? m_leftModel : m_proxyChainUp.last()->sourceModel();
147 auto konamiLeft = m_proxyChainDown.isEmpty() ? m_rightModel : m_proxyChainDown.first()->sourceModel();
148 setConnected(konamiLeft && (konamiLeft == konamiRight));
149}
150
151void KModelIndexProxyMapperPrivate::setConnected(bool connected)
152{
153 if (mConnected != connected) {
154 Q_Q(KModelIndexProxyMapper);
155 mConnected = connected;
156 Q_EMIT q->isConnectedChanged();
157 }
158}
159
160KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent)
161 : QObject(parent)
162 , d_ptr(new KModelIndexProxyMapperPrivate(leftModel, rightModel, this))
163{
164}
165
166KModelIndexProxyMapper::~KModelIndexProxyMapper() = default;
167
168QModelIndex KModelIndexProxyMapper::mapLeftToRight(const QModelIndex &index) const
169{
170 const QItemSelection selection = mapSelectionLeftToRight(selection: QItemSelection(index, index));
171 if (selection.isEmpty()) {
172 return QModelIndex();
173 }
174
175 return selection.indexes().first();
176}
177
178QModelIndex KModelIndexProxyMapper::mapRightToLeft(const QModelIndex &index) const
179{
180 const QItemSelection selection = mapSelectionRightToLeft(selection: QItemSelection(index, index));
181 if (selection.isEmpty()) {
182 return QModelIndex();
183 }
184
185 return selection.indexes().first();
186}
187
188QItemSelection KModelIndexProxyMapper::mapSelectionLeftToRight(const QItemSelection &selection) const
189{
190 Q_D(const KModelIndexProxyMapper);
191
192 if (selection.isEmpty() || !d->mConnected) {
193 return QItemSelection();
194 }
195
196 if (selection.first().model() != d->m_leftModel) {
197 qCDebug(KITEMMODELS_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel;
198 }
199 Q_ASSERT(selection.first().model() == d->m_leftModel);
200
201 QItemSelection seekSelection = selection;
202 Q_ASSERT(d->assertSelectionValid(seekSelection));
203 QListIterator<QPointer<const QAbstractProxyModel>> iUp(d->m_proxyChainUp);
204
205 while (iUp.hasNext()) {
206 const QPointer<const QAbstractProxyModel> proxy = iUp.next();
207 if (!proxy) {
208 return QItemSelection();
209 }
210
211 Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy);
212 seekSelection = proxy->mapSelectionToSource(selection: seekSelection);
213 Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel());
214
215 Q_ASSERT(d->assertSelectionValid(seekSelection));
216 }
217
218 QListIterator<QPointer<const QAbstractProxyModel>> iDown(d->m_proxyChainDown);
219
220 while (iDown.hasNext()) {
221 const QPointer<const QAbstractProxyModel> proxy = iDown.next();
222 if (!proxy) {
223 return QItemSelection();
224 }
225 Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy->sourceModel());
226 seekSelection = proxy->mapSelectionFromSource(selection: seekSelection);
227 Q_ASSERT(seekSelection.isEmpty() || seekSelection.first().model() == proxy);
228
229 Q_ASSERT(d->assertSelectionValid(seekSelection));
230 }
231
232 Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_rightModel) || true);
233 return seekSelection;
234}
235
236QItemSelection KModelIndexProxyMapper::mapSelectionRightToLeft(const QItemSelection &selection) const
237{
238 Q_D(const KModelIndexProxyMapper);
239
240 if (selection.isEmpty() || !d->mConnected) {
241 return QItemSelection();
242 }
243
244 if (selection.first().model() != d->m_rightModel) {
245 qCDebug(KITEMMODELS_LOG) << "FAIL" << selection.first().model() << d->m_leftModel << d->m_rightModel;
246 }
247 Q_ASSERT(selection.first().model() == d->m_rightModel);
248
249 QItemSelection seekSelection = selection;
250 Q_ASSERT(d->assertSelectionValid(seekSelection));
251 QListIterator<QPointer<const QAbstractProxyModel>> iDown(d->m_proxyChainDown);
252
253 iDown.toBack();
254 while (iDown.hasPrevious()) {
255 const QPointer<const QAbstractProxyModel> proxy = iDown.previous();
256 if (!proxy) {
257 return QItemSelection();
258 }
259 seekSelection = proxy->mapSelectionToSource(selection: seekSelection);
260
261 Q_ASSERT(d->assertSelectionValid(seekSelection));
262 }
263
264 QListIterator<QPointer<const QAbstractProxyModel>> iUp(d->m_proxyChainUp);
265
266 iUp.toBack();
267 while (iUp.hasPrevious()) {
268 const QPointer<const QAbstractProxyModel> proxy = iUp.previous();
269 if (!proxy) {
270 return QItemSelection();
271 }
272 seekSelection = proxy->mapSelectionFromSource(selection: seekSelection);
273
274 Q_ASSERT(d->assertSelectionValid(seekSelection));
275 }
276
277 Q_ASSERT((!seekSelection.isEmpty() && seekSelection.first().model() == d->m_leftModel) || true);
278 return seekSelection;
279}
280
281bool KModelIndexProxyMapper::isConnected() const
282{
283 Q_D(const KModelIndexProxyMapper);
284 return d->mConnected;
285}
286
287#include "moc_kmodelindexproxymapper.cpp"
288

source code of kitemmodels/src/core/kmodelindexproxymapper.cpp