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 | |
18 | class 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 | |
92 | void 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 | |
144 | void 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 | |
151 | void KModelIndexProxyMapperPrivate::setConnected(bool connected) |
152 | { |
153 | if (mConnected != connected) { |
154 | Q_Q(KModelIndexProxyMapper); |
155 | mConnected = connected; |
156 | Q_EMIT q->isConnectedChanged(); |
157 | } |
158 | } |
159 | |
160 | KModelIndexProxyMapper::KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent) |
161 | : QObject(parent) |
162 | , d_ptr(new KModelIndexProxyMapperPrivate(leftModel, rightModel, this)) |
163 | { |
164 | } |
165 | |
166 | KModelIndexProxyMapper::~KModelIndexProxyMapper() = default; |
167 | |
168 | QModelIndex 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 | |
178 | QModelIndex 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 | |
188 | QItemSelection 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 | |
236 | QItemSelection 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 | |
281 | bool KModelIndexProxyMapper::isConnected() const |
282 | { |
283 | Q_D(const KModelIndexProxyMapper); |
284 | return d->mConnected; |
285 | } |
286 | |
287 | #include "moc_kmodelindexproxymapper.cpp" |
288 | |