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
16class KViewStateSerializerPrivate
17{
18public:
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
96KViewStateSerializer::KViewStateSerializer(QObject *parent)
97 : QObject(nullptr)
98 , d_ptr(new KViewStateSerializerPrivate(this))
99{
100 Q_UNUSED(parent);
101 qRegisterMetaType<QModelIndex>(typeName: "QModelIndex");
102}
103
104KViewStateSerializer::~KViewStateSerializer() = default;
105
106void 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
120QAbstractItemView *KViewStateSerializer::view() const
121{
122 Q_D(const KViewStateSerializer);
123 return d->m_view;
124}
125
126QItemSelectionModel *KViewStateSerializer::selectionModel() const
127{
128 Q_D(const KViewStateSerializer);
129 return d->m_selectionModel;
130}
131
132void KViewStateSerializer::setSelectionModel(QItemSelectionModel *selectionModel)
133{
134 Q_D(KViewStateSerializer);
135 d->m_selectionModel = selectionModel;
136}
137
138void 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
158void 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
168QStringList 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
187void 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
202void 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
220void 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
236void 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
255void 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
271void 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
287void 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
307QString 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
316QStringList 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
326QStringList 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
343QPair<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
349void 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

source code of kwidgetsaddons/src/kviewstateserializer.cpp