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 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "kviewstateserializer.h" |
9 | |
10 | #include <QAbstractScrollArea> |
11 | #include <QPointer> |
12 | #include <QScrollBar> |
13 | #include <QTimer> |
14 | #include <QTreeView> |
15 | |
16 | class KViewStateSerializerPrivate |
17 | { |
18 | public: |
19 | KViewStateSerializerPrivate(KViewStateSerializer *qq) |
20 | : q_ptr(qq) |
21 | , m_treeView(nullptr) |
22 | , m_view(nullptr) |
23 | , m_selectionModel(nullptr) |
24 | , m_scrollArea(nullptr) |
25 | , m_horizontalScrollBarValue(-1) |
26 | , m_verticalScrollBarValue(-1) |
27 | { |
28 | } |
29 | |
30 | Q_DECLARE_PUBLIC(KViewStateSerializer) |
31 | KViewStateSerializer *const q_ptr; |
32 | |
33 | QStringList getExpandedItems(const QModelIndex &index) const; |
34 | |
35 | void listenToPendingChanges(); |
36 | void processPendingChanges(); |
37 | |
38 | inline void restoreScrollBarState() |
39 | { |
40 | if (!m_scrollArea || !m_scrollArea->horizontalScrollBar() || !m_scrollArea->verticalScrollBar()) { |
41 | return; |
42 | } |
43 | if (m_horizontalScrollBarValue >= 0 && m_horizontalScrollBarValue <= m_scrollArea->horizontalScrollBar()->maximum()) { |
44 | m_scrollArea->horizontalScrollBar()->setValue(m_horizontalScrollBarValue); |
45 | m_horizontalScrollBarValue = -1; |
46 | } |
47 | if (m_verticalScrollBarValue >= 0 && m_verticalScrollBarValue <= m_scrollArea->verticalScrollBar()->maximum()) { |
48 | m_scrollArea->verticalScrollBar()->setValue(m_verticalScrollBarValue); |
49 | m_verticalScrollBarValue = -1; |
50 | } |
51 | } |
52 | |
53 | void restoreSelection(); |
54 | void restoreCurrentItem(); |
55 | void restoreExpanded(); |
56 | |
57 | inline bool hasPendingChanges() const |
58 | { |
59 | return !m_pendingCurrent.isEmpty() || !m_pendingExpansions.isEmpty() || !m_pendingSelections.isEmpty(); |
60 | } |
61 | |
62 | const QAbstractItemModel *getModel() |
63 | { |
64 | if (m_selectionModel && m_selectionModel->model()) { |
65 | return m_selectionModel->model(); |
66 | } else if (m_view && m_view->model()) { |
67 | return m_view->model(); |
68 | } |
69 | return nullptr; |
70 | } |
71 | |
72 | void rowsInserted(const QModelIndex & /*index*/, int /*start*/, int /*end*/) |
73 | { |
74 | Q_Q(KViewStateSerializer); |
75 | processPendingChanges(); |
76 | |
77 | if (!hasPendingChanges()) { |
78 | q->disconnect(m_rowsInsertedConnection); |
79 | q->deleteLater(); |
80 | } |
81 | } |
82 | |
83 | QTreeView *m_treeView; |
84 | QAbstractItemView *m_view; |
85 | QItemSelectionModel *m_selectionModel; |
86 | QPointer<QAbstractScrollArea> m_scrollArea; |
87 | |
88 | int m_horizontalScrollBarValue; |
89 | int m_verticalScrollBarValue; |
90 | QSet<QString> m_pendingSelections; |
91 | QSet<QString> m_pendingExpansions; |
92 | QString m_pendingCurrent; |
93 | QMetaObject::Connection m_rowsInsertedConnection; |
94 | }; |
95 | |
96 | KViewStateSerializer::KViewStateSerializer(QObject *parent) |
97 | : QObject(nullptr) |
98 | , d_ptr(new KViewStateSerializerPrivate(this)) |
99 | { |
100 | Q_UNUSED(parent); |
101 | qRegisterMetaType<QModelIndex>(typeName: "QModelIndex"); |
102 | } |
103 | |
104 | KViewStateSerializer::~KViewStateSerializer() = default; |
105 | |
106 | void KViewStateSerializer::setView(QAbstractItemView *view) |
107 | { |
108 | Q_D(KViewStateSerializer); |
109 | d->m_scrollArea = view; |
110 | if (view) { |
111 | d->m_selectionModel = view->selectionModel(); |
112 | d->m_treeView = qobject_cast<QTreeView *>(object: view); |
113 | } else { |
114 | d->m_selectionModel = nullptr; |
115 | d->m_treeView = nullptr; |
116 | } |
117 | d->m_view = view; |
118 | } |
119 | |
120 | QAbstractItemView *KViewStateSerializer::view() const |
121 | { |
122 | Q_D(const KViewStateSerializer); |
123 | return d->m_view; |
124 | } |
125 | |
126 | QItemSelectionModel *KViewStateSerializer::selectionModel() const |
127 | { |
128 | Q_D(const KViewStateSerializer); |
129 | return d->m_selectionModel; |
130 | } |
131 | |
132 | void KViewStateSerializer::setSelectionModel(QItemSelectionModel *selectionModel) |
133 | { |
134 | Q_D(KViewStateSerializer); |
135 | d->m_selectionModel = selectionModel; |
136 | } |
137 | |
138 | void KViewStateSerializerPrivate::listenToPendingChanges() |
139 | { |
140 | Q_Q(KViewStateSerializer); |
141 | // watch the model for stuff coming in delayed |
142 | if (hasPendingChanges()) { |
143 | const QAbstractItemModel *model = getModel(); |
144 | if (model) { |
145 | q->disconnect(m_rowsInsertedConnection); |
146 | m_rowsInsertedConnection = q->connect(sender: model, signal: &QAbstractItemModel::rowsInserted, context: q, slot: [this](const QModelIndex &parent, int first, int last) { |
147 | rowsInserted(parent, first, last); |
148 | }); |
149 | return; |
150 | } else { |
151 | q->deleteLater(); |
152 | } |
153 | } else { |
154 | q->deleteLater(); |
155 | } |
156 | } |
157 | |
158 | void KViewStateSerializerPrivate::processPendingChanges() |
159 | { |
160 | Q_Q(KViewStateSerializer); |
161 | |
162 | q->restoreCurrentItem(indexString: m_pendingCurrent); |
163 | q->restoreSelection(indexStrings: m_pendingSelections.values()); |
164 | q->restoreExpanded(indexStrings: m_pendingExpansions.values()); |
165 | q->restoreScrollState(verticalScoll: m_verticalScrollBarValue, horizontalScroll: m_horizontalScrollBarValue); |
166 | } |
167 | |
168 | QStringList KViewStateSerializerPrivate::getExpandedItems(const QModelIndex &index) const |
169 | { |
170 | Q_Q(const KViewStateSerializer); |
171 | |
172 | QStringList expansion; |
173 | for (int i = 0; i < m_treeView->model()->rowCount(parent: index); ++i) { |
174 | const QModelIndex child = m_treeView->model()->index(row: i, column: 0, parent: index); |
175 | |
176 | // http://bugreports.qt.nokia.com/browse/QTBUG-18039 |
177 | if (m_treeView->model()->hasChildren(parent: child)) { |
178 | if (m_treeView->isExpanded(index: child)) { |
179 | expansion << q->indexToConfigString(index: child); |
180 | } |
181 | expansion << getExpandedItems(index: child); |
182 | } |
183 | } |
184 | return expansion; |
185 | } |
186 | |
187 | void KViewStateSerializerPrivate::restoreCurrentItem() |
188 | { |
189 | Q_Q(KViewStateSerializer); |
190 | |
191 | QModelIndex currentIndex = q->indexFromConfigString(model: m_selectionModel->model(), key: m_pendingCurrent); |
192 | if (currentIndex.isValid()) { |
193 | if (m_treeView) { |
194 | m_treeView->setCurrentIndex(currentIndex); |
195 | } else { |
196 | m_selectionModel->setCurrentIndex(index: currentIndex, command: QItemSelectionModel::NoUpdate); |
197 | } |
198 | m_pendingCurrent.clear(); |
199 | } |
200 | } |
201 | |
202 | void KViewStateSerializer::restoreCurrentItem(const QString &indexString) |
203 | { |
204 | Q_D(KViewStateSerializer); |
205 | if (!d->m_selectionModel || !d->m_selectionModel->model()) { |
206 | return; |
207 | } |
208 | |
209 | if (indexString.isEmpty()) { |
210 | return; |
211 | } |
212 | d->m_pendingCurrent = indexString; |
213 | d->restoreCurrentItem(); |
214 | |
215 | if (d->hasPendingChanges()) { |
216 | d->listenToPendingChanges(); |
217 | } |
218 | } |
219 | |
220 | void KViewStateSerializerPrivate::restoreExpanded() |
221 | { |
222 | Q_Q(KViewStateSerializer); |
223 | |
224 | QSet<QString>::iterator it = m_pendingExpansions.begin(); |
225 | for (; it != m_pendingExpansions.end();) { |
226 | QModelIndex idx = q->indexFromConfigString(model: m_treeView->model(), key: *it); |
227 | if (idx.isValid()) { |
228 | m_treeView->expand(index: idx); |
229 | it = m_pendingExpansions.erase(i: it); |
230 | } else { |
231 | ++it; |
232 | } |
233 | } |
234 | } |
235 | |
236 | void KViewStateSerializer::restoreExpanded(const QStringList &indexStrings) |
237 | { |
238 | Q_D(KViewStateSerializer); |
239 | if (!d->m_treeView || !d->m_treeView->model()) { |
240 | return; |
241 | } |
242 | |
243 | if (indexStrings.isEmpty()) { |
244 | return; |
245 | } |
246 | |
247 | d->m_pendingExpansions.unite(other: QSet<QString>(indexStrings.begin(), indexStrings.end())); |
248 | |
249 | d->restoreExpanded(); |
250 | if (d->hasPendingChanges()) { |
251 | d->listenToPendingChanges(); |
252 | } |
253 | } |
254 | |
255 | void KViewStateSerializer::restoreScrollState(int verticalScoll, int horizontalScroll) |
256 | { |
257 | Q_D(KViewStateSerializer); |
258 | |
259 | if (!d->m_scrollArea) { |
260 | return; |
261 | } |
262 | |
263 | d->m_verticalScrollBarValue = verticalScoll; |
264 | d->m_horizontalScrollBarValue = horizontalScroll; |
265 | |
266 | QTimer::singleShot(interval: 0, receiver: this, slot: [d]() { |
267 | d->restoreScrollBarState(); |
268 | }); |
269 | } |
270 | |
271 | void KViewStateSerializerPrivate::restoreSelection() |
272 | { |
273 | Q_Q(KViewStateSerializer); |
274 | |
275 | QSet<QString>::iterator it = m_pendingSelections.begin(); |
276 | for (; it != m_pendingSelections.end();) { |
277 | QModelIndex idx = q->indexFromConfigString(model: m_selectionModel->model(), key: *it); |
278 | if (idx.isValid()) { |
279 | m_selectionModel->select(index: idx, command: QItemSelectionModel::Select); |
280 | it = m_pendingSelections.erase(i: it); |
281 | } else { |
282 | ++it; |
283 | } |
284 | } |
285 | } |
286 | |
287 | void KViewStateSerializer::restoreSelection(const QStringList &indexStrings) |
288 | { |
289 | Q_D(KViewStateSerializer); |
290 | |
291 | if (!d->m_selectionModel || !d->m_selectionModel->model()) { |
292 | return; |
293 | } |
294 | |
295 | if (indexStrings.isEmpty()) { |
296 | return; |
297 | } |
298 | |
299 | d->m_pendingSelections.unite(other: QSet<QString>(indexStrings.begin(), indexStrings.end())); |
300 | |
301 | d->restoreSelection(); |
302 | if (d->hasPendingChanges()) { |
303 | d->listenToPendingChanges(); |
304 | } |
305 | } |
306 | |
307 | QString KViewStateSerializer::currentIndexKey() const |
308 | { |
309 | Q_D(const KViewStateSerializer); |
310 | if (!d->m_selectionModel) { |
311 | return QString(); |
312 | } |
313 | return indexToConfigString(index: d->m_selectionModel->currentIndex()); |
314 | } |
315 | |
316 | QStringList KViewStateSerializer::expansionKeys() const |
317 | { |
318 | Q_D(const KViewStateSerializer); |
319 | if (!d->m_treeView || !d->m_treeView->model()) { |
320 | return QStringList(); |
321 | } |
322 | |
323 | return d->getExpandedItems(index: QModelIndex()); |
324 | } |
325 | |
326 | QStringList KViewStateSerializer::selectionKeys() const |
327 | { |
328 | Q_D(const KViewStateSerializer); |
329 | if (!d->m_selectionModel) { |
330 | return QStringList(); |
331 | } |
332 | |
333 | const QModelIndexList selectedIndexes = d->m_selectionModel->selectedRows(); |
334 | QStringList selection; |
335 | selection.reserve(asize: selectedIndexes.count()); |
336 | for (const QModelIndex &index : selectedIndexes) { |
337 | selection << indexToConfigString(index); |
338 | } |
339 | |
340 | return selection; |
341 | } |
342 | |
343 | QPair<int, int> KViewStateSerializer::scrollState() const |
344 | { |
345 | Q_D(const KViewStateSerializer); |
346 | return qMakePair(value1: d->m_scrollArea->verticalScrollBar()->value(), value2: d->m_scrollArea->horizontalScrollBar()->value()); |
347 | } |
348 | |
349 | void KViewStateSerializer::restoreState() |
350 | { |
351 | Q_D(KViewStateSerializer); |
352 | // Delete myself if not finished after 60 seconds |
353 | QTimer::singleShot(interval: 60000, receiver: this, slot: &KViewStateSerializer::deleteLater); |
354 | |
355 | d->processPendingChanges(); |
356 | if (d->hasPendingChanges()) { |
357 | d->listenToPendingChanges(); |
358 | } |
359 | } |
360 | |
361 | #include "moc_kviewstateserializer.cpp" |
362 |
Definitions
- KViewStateSerializerPrivate
- KViewStateSerializerPrivate
- restoreScrollBarState
- hasPendingChanges
- getModel
- rowsInserted
- KViewStateSerializer
- ~KViewStateSerializer
- setView
- view
- selectionModel
- setSelectionModel
- listenToPendingChanges
- processPendingChanges
- getExpandedItems
- restoreCurrentItem
- restoreCurrentItem
- restoreExpanded
- restoreExpanded
- restoreScrollState
- restoreSelection
- restoreSelection
- currentIndexKey
- expansionKeys
- selectionKeys
- scrollState
Start learning QML with our Intro Training
Find out more