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