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:
KViewStateSerializerPrivate(KViewStateSerializer * qq)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
restoreScrollBarState()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
hasPendingChanges() const57 inline bool hasPendingChanges() const
58 {
59 return !m_pendingCurrent.isEmpty() || !m_pendingExpansions.isEmpty() || !m_pendingSelections.isEmpty();
60 }
61
getModel()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
rowsInserted(const QModelIndex &,int,int)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
KViewStateSerializer(QObject * parent)96 KViewStateSerializer::KViewStateSerializer(QObject *parent)
97 : QObject(nullptr)
98 , d_ptr(new KViewStateSerializerPrivate(this))
99 {
100 Q_UNUSED(parent);
101 qRegisterMetaType<QModelIndex>("QModelIndex");
102 }
103
104 KViewStateSerializer::~KViewStateSerializer() = default;
105
setView(QAbstractItemView * view)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 *>(view);
113 } else {
114 d->m_selectionModel = nullptr;
115 d->m_treeView = nullptr;
116 }
117 d->m_view = view;
118 }
119
view() const120 QAbstractItemView *KViewStateSerializer::view() const
121 {
122 Q_D(const KViewStateSerializer);
123 return d->m_view;
124 }
125
selectionModel() const126 QItemSelectionModel *KViewStateSerializer::selectionModel() const
127 {
128 Q_D(const KViewStateSerializer);
129 return d->m_selectionModel;
130 }
131
setSelectionModel(QItemSelectionModel * selectionModel)132 void KViewStateSerializer::setSelectionModel(QItemSelectionModel *selectionModel)
133 {
134 Q_D(KViewStateSerializer);
135 d->m_selectionModel = selectionModel;
136 }
137
listenToPendingChanges()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(model, &QAbstractItemModel::rowsInserted, q, [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
processPendingChanges()158 void KViewStateSerializerPrivate::processPendingChanges()
159 {
160 Q_Q(KViewStateSerializer);
161
162 q->restoreCurrentItem(m_pendingCurrent);
163 q->restoreSelection(m_pendingSelections.values());
164 q->restoreExpanded(m_pendingExpansions.values());
165 q->restoreScrollState(m_verticalScrollBarValue, m_horizontalScrollBarValue);
166 }
167
getExpandedItems(const QModelIndex & index) const168 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(index); ++i) {
174 const QModelIndex child = m_treeView->model()->index(i, 0, index);
175
176 // http://bugreports.qt.nokia.com/browse/QTBUG-18039
177 if (m_treeView->model()->hasChildren(child)) {
178 if (m_treeView->isExpanded(child)) {
179 expansion << q->indexToConfigString(child);
180 }
181 expansion << getExpandedItems(child);
182 }
183 }
184 return expansion;
185 }
186
restoreCurrentItem()187 void KViewStateSerializerPrivate::restoreCurrentItem()
188 {
189 Q_Q(KViewStateSerializer);
190
191 QModelIndex currentIndex = q->indexFromConfigString(m_selectionModel->model(), m_pendingCurrent);
192 if (currentIndex.isValid()) {
193 if (m_treeView) {
194 m_treeView->setCurrentIndex(currentIndex);
195 } else {
196 m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
197 }
198 m_pendingCurrent.clear();
199 }
200 }
201
restoreCurrentItem(const QString & indexString)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
restoreExpanded()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(m_treeView->model(), *it);
227 if (idx.isValid()) {
228 m_treeView->expand(idx);
229 it = m_pendingExpansions.erase(it);
230 } else {
231 ++it;
232 }
233 }
234 }
235
restoreExpanded(const QStringList & indexStrings)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(QSet<QString>(indexStrings.begin(), indexStrings.end()));
248
249 d->restoreExpanded();
250 if (d->hasPendingChanges()) {
251 d->listenToPendingChanges();
252 }
253 }
254
restoreScrollState(int verticalScoll,int horizontalScroll)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(0, this, [d]() {
267 d->restoreScrollBarState();
268 });
269 }
270
restoreSelection()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(m_selectionModel->model(), *it);
278 if (idx.isValid()) {
279 m_selectionModel->select(idx, QItemSelectionModel::Select);
280 it = m_pendingSelections.erase(it);
281 } else {
282 ++it;
283 }
284 }
285 }
286
restoreSelection(const QStringList & indexStrings)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(QSet<QString>(indexStrings.begin(), indexStrings.end()));
300
301 d->restoreSelection();
302 if (d->hasPendingChanges()) {
303 d->listenToPendingChanges();
304 }
305 }
306
currentIndexKey() const307 QString KViewStateSerializer::currentIndexKey() const
308 {
309 Q_D(const KViewStateSerializer);
310 if (!d->m_selectionModel) {
311 return QString();
312 }
313 return indexToConfigString(d->m_selectionModel->currentIndex());
314 }
315
expansionKeys() const316 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(QModelIndex());
324 }
325
selectionKeys() const326 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(selectedIndexes.count());
336 for (const QModelIndex &index : selectedIndexes) {
337 selection << indexToConfigString(index);
338 }
339
340 return selection;
341 }
342
scrollState() const343 QPair<int, int> KViewStateSerializer::scrollState() const
344 {
345 Q_D(const KViewStateSerializer);
346 return qMakePair(d->m_scrollArea->verticalScrollBar()->value(), d->m_scrollArea->horizontalScrollBar()->value());
347 }
348
restoreState()349 void KViewStateSerializer::restoreState()
350 {
351 Q_D(KViewStateSerializer);
352 // Delete myself if not finished after 60 seconds
353 QTimer::singleShot(60000, this, &KViewStateSerializer::deleteLater);
354
355 d->processPendingChanges();
356 if (d->hasPendingChanges()) {
357 d->listenToPendingChanges();
358 }
359 }
360
361 #include "moc_kviewstateserializer.cpp"
362