KItemModels

kmodelindexproxymapper.cpp
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
49
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(m_proxyChainUp)) {
95 p->disconnect(q_ptr);
96 }
97 for (auto p : std::as_const(m_proxyChainDown)) {
98 p->disconnect(q_ptr);
99 }
100 m_proxyChainUp.clear();
101 m_proxyChainDown.clear();
102 QPointer<const QAbstractItemModel> targetModel = m_rightModel;
103
105 QPointer<const QAbstractProxyModel> selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(targetModel);
106 while (selectionTargetProxyModel) {
107 proxyChainDown.prepend(selectionTargetProxyModel);
108 QObject::connect(selectionTargetProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] {
109 createProxyChain();
110 });
111
112 selectionTargetProxyModel = qobject_cast<const QAbstractProxyModel *>(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 *>(sourceModel);
123
124 while (sourceProxyModel) {
125 m_proxyChainUp.append(sourceProxyModel);
126 QObject::connect(sourceProxyModel.data(), &QAbstractProxyModel::sourceModelChanged, q_ptr, [this] {
127 createProxyChain();
128 });
129
130 sourceProxyModel = qobject_cast<const QAbstractProxyModel *>(sourceProxyModel->sourceModel());
131
132 const int targetIndex = proxyChainDown.indexOf(sourceProxyModel);
133
134 if (targetIndex != -1) {
135 m_proxyChainDown = proxyChainDown.mid(targetIndex + 1, 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) {
155 mConnected = connected;
156 Q_EMIT q->isConnectedChanged();
157 }
158}
159
161 : QObject(parent)
162 , d_ptr(new KModelIndexProxyMapperPrivate(leftModel, rightModel, this))
163{
164}
165
166KModelIndexProxyMapper::~KModelIndexProxyMapper() = default;
167
169{
170 const QItemSelection selection = mapSelectionLeftToRight(QItemSelection(index, index));
171 if (selection.isEmpty()) {
172 return QModelIndex();
173 }
174
175 return selection.indexes().first();
176}
177
179{
180 const QItemSelection selection = mapSelectionRightToLeft(QItemSelection(index, index));
181 if (selection.isEmpty()) {
182 return QModelIndex();
183 }
184
185 return selection.indexes().first();
186}
187
189{
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));
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(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(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
237{
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(seekSelection);
260
261 Q_ASSERT(d->assertSelectionValid(seekSelection));
262 }
263
265
266 iUp.toBack();
267 while (iUp.hasPrevious()) {
269 if (!proxy) {
270 return QItemSelection();
271 }
272 seekSelection = proxy->mapSelectionFromSource(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
282{
284 return d->mConnected;
285}
286
287#include "moc_kmodelindexproxymapper.cpp"
This class facilitates easy mapping of indexes and selections through proxy models.
QItemSelection mapSelectionLeftToRight(const QItemSelection &selection) const
Maps the selection from the left model to the right model.
bool isConnected
Indicates whether there is a chain that can be followed from leftModel to rightModel.
KModelIndexProxyMapper(const QAbstractItemModel *leftModel, const QAbstractItemModel *rightModel, QObject *parent=nullptr)
Constructor.
QModelIndex mapLeftToRight(const QModelIndex &index) const
Maps the index from the left model to the right model.
QItemSelection mapSelectionRightToLeft(const QItemSelection &selection) const
Maps the selection from the right model to the left model.
QModelIndex mapRightToLeft(const QModelIndex &index) const
Maps the index from the right model to the left model.
QModelIndexList indexes() const const
void append(QList< T > &&value)
void clear()
T & first()
qsizetype indexOf(const AT &value, qsizetype from) const const
bool isEmpty() const const
T & last()
QList< T > mid(qsizetype pos, qsizetype length) const const
void prepend(parameter_type value)
qsizetype size() const const
bool hasNext() const const
bool hasPrevious() const const
const T & next()
const T & previous()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T * data() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:35 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.