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 #ifndef KVIEWSTATESERIALIZER_H
9 #define KVIEWSTATESERIALIZER_H
10 
11 #include <QObject>
12 #include <QPair>
13 #include <QStringList>
14 #include <memory>
15 
16 #include "kwidgetsaddons_export.h"
17 
18 class QAbstractItemView;
19 class QItemSelectionModel;
20 class QAbstractItemModel;
21 class QAbstractScrollArea;
22 class QModelIndex;
23 
24 class KViewStateSerializerPrivate;
25 
26 /**
27   @class KViewStateSerializer kviewstateserializer.h KViewStateSerializer
28 
29   @brief Object for saving and restoring state in QTreeViews and QItemSelectionModels
30 
31   Implement the indexFromConfigString and indexToConfigString methods to handle
32   the model in the view whose state is being saved. These implementations can be
33   quite trivial:
34 
35   @code
36     QModelIndex DynamicTreeStateSaver::indexFromConfigString(const QAbstractItemModel* model,
37                                                              const QString& key) const
38     {
39         QModelIndexList list = model->match(model->index(0, 0),
40                                             DynamicTreeModel::DynamicTreeModelId,
41                                             key.toInt(), 1, Qt::MatchRecursive);
42         if (list.isEmpty()) {
43             return QModelIndex();
44         }
45         return list.first();
46     }
47 
48     QString DynamicTreeStateSaver::indexToConfigString(const QModelIndex& index) const
49     {
50         return index.data(DynamicTreeModel::DynamicTreeModelId).toString();
51     }
52   @endcode
53 
54   It is possible to restore the state of a QTreeView (that is, the expanded state and
55   selected state of all indexes as well as the horizontal and vertical scroll state) by
56   using setTreeView.
57 
58   If there is no tree view state to restore (for example if using QML), the selection
59   state of a QItemSelectionModel can be saved or restored instead.
60 
61   The state of any QAbstractScrollArea can also be saved and restored.
62 
63   A KViewStateSerializer should be created on the stack when saving and on the heap
64   when restoring. The model may be populated dynamically between several event loops,
65   so it may not be immediate for the indexes that should be selected to be in the model.
66   The saver should *not* be persisted as a member. The saver will destroy itself when it
67   has completed the restoration specified in the config group, or a small amount of time
68   has elapsed.
69 
70   @code
71     MyWidget::MyWidget(Qobject *parent)
72       : QWidget(parent)
73     {
74         ...
75 
76         m_view = new QTreeView(splitter);
77         m_view->setModel(model);
78 
79         connect(model, &QAbstractItemModel::modelAboutToBeReset, this, [this]() { saveState(); });
80         connect(model, &QAbstractItemModel::modelReset, [this]() { restoreState(); });
81         connect(qApp, &QApplication::aboutToQuit, this, [this]() { saveState(); });
82 
83         restoreState();
84     }
85 
86     void StateSaverWidget::saveState()
87     {
88         ConcreteStateSaver saver;
89         saver.setTreeView(m_view);
90 
91         KConfigGroup cfg(KSharedConfig::openConfig(), "ExampleViewState");
92         saver.saveState(cfg);
93         cfg.sync();
94     }
95 
96     void StateSaverWidget::restoreState()
97     {
98         // Will delete itself.
99         ConcreteTreeStateSaver *saver = new ConcreteStateSaver();
100         saver->setTreeView(m_view);
101         KConfigGroup cfg(KSharedConfig::openConfig(), "ExampleViewState");
102         saver->restoreState(cfg);
103     }
104   @endcode
105 
106   After creating a saver, the state can be saved using a KConfigGroup.
107 
108   It is also possible to save and restore state directly by using the restoreSelection,
109   restoreExpanded etc methods. Note that the implementation of these methods should return
110   strings that the indexFromConfigString implementation can handle.
111 
112   @code
113     class DynamicTreeStateSaver : public KViewStateSerializer
114     {
115         Q_OBJECT
116     public:
117       // ...
118 
119         void selectItems(const QList<qint64> &items)
120         {
121             QStringList itemStrings;
122             for (qint64 item : items) {
123                 itemStrings << QString::number(item);
124             }
125             restoreSelection(itemStrings);
126         }
127 
128         void expandItems(const QList<qint64> &items)
129         {
130             QStringList itemStrings;
131             for (qint64 item : items) {
132                 itemStrings << QString::number(item);
133             }
134             restoreSelection(itemStrings);
135         }
136     };
137   @endcode
138 
139   Note that a single instance of this class should be used with only one widget.
140   That is don't do this:
141 
142   @code
143     saver->setTreeView(treeView1);
144     saver->setSelectionModel(treeView2->selectionModel());
145     saver->setScrollArea(treeView3);
146   @endcode
147 
148   To save the state of 3 different widgets, use three savers, even if they operate
149   on the same root model.
150 
151   @code
152     saver1->setTreeView(treeView1);
153     saver2->setSelectionModel(treeView2->selectionModel());
154     saver3->setScrollArea(treeView3);
155   @endcode
156 
157   @note The KViewStateSerializer does not take ownership of any widgets set on it.
158 
159   It is recommended to restore the state on application startup and after the
160   model has been reset, and to save the state on application close and before
161   the model has been reset.
162 
163   @see QAbstractItemModel::modelAboutToBeReset QAbstractItemModel::modelReset
164 
165   @author Stephen Kelly <stephen@kdab.com>
166   @since 4.5
167 */
168 class KWIDGETSADDONS_EXPORT KViewStateSerializer : public QObject
169 {
170     Q_OBJECT
171 public:
172     /**
173      * Constructor
174      */
175     explicit KViewStateSerializer(QObject *parent = nullptr);
176 
177     /**
178      * Destructor
179      */
180     ~KViewStateSerializer() override;
181 
182     /**
183      * The view whose state is persisted.
184      */
185     QAbstractItemView *view() const;
186 
187     /**
188      * Sets the view whose state is persisted.
189      */
190     void setView(QAbstractItemView *view);
191 
192     /**
193      * The QItemSelectionModel whose state is persisted.
194      */
195     QItemSelectionModel *selectionModel() const;
196 
197     /**
198      * Sets the QItemSelectionModel whose state is persisted.
199      */
200     void setSelectionModel(QItemSelectionModel *selectionModel);
201 
202     /**
203      * Returns a QStringList describing the selection in the selectionModel.
204      */
205     QStringList selectionKeys() const;
206 
207     /**
208      * Returns a QStringList representing the expanded indexes in the QTreeView.
209      */
210     QStringList expansionKeys() const;
211 
212     /**
213      * Returns a QString describing the current index in the selection model.
214      */
215     QString currentIndexKey() const;
216 
217     /**
218      * Returns the vertical and horizontal scroll of the QAbstractScrollArea.
219      */
220     QPair<int, int> scrollState() const;
221 
222     /**
223      * Select the indexes described by @p indexStrings
224      */
225     void restoreSelection(const QStringList &indexStrings);
226 
227     /**
228      * Make the index described by @p indexString the currentIndex in the selectionModel.
229      */
230     void restoreCurrentItem(const QString &indexString);
231 
232     /**
233      * Expand the indexes described by @p indexStrings in the QTreeView.
234      */
235     void restoreExpanded(const QStringList &indexStrings);
236 
237     /**
238      * Restores the scroll state of the QAbstractScrollArea to the @p verticalScoll
239      * and @p horizontalScroll
240      */
241     void restoreScrollState(int verticalScoll, int horizontalScroll);
242 
243 protected:
244     /**
245      * Reimplement to return an index in the @p model described by the unique key @p key
246      */
247     virtual QModelIndex indexFromConfigString(const QAbstractItemModel *model, const QString &key) const = 0;
248 
249     /**
250      * Reimplement to return a unique string for the @p index.
251      */
252     virtual QString indexToConfigString(const QModelIndex &index) const = 0;
253 
254     void restoreState();
255 
256 private:
257     //@cond PRIVATE
258     Q_DECLARE_PRIVATE(KViewStateSerializer)
259     std::unique_ptr<KViewStateSerializerPrivate> const d_ptr;
260     //@endcond
261 };
262 
263 #endif
264