1/*
2 Copyright (c) 2009 Volker Krause <vkrause@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "entitytreeviewstatesaver.h"
21
22#include <akonadi/collection.h>
23#include <akonadi/entitytreemodel.h>
24#include <akonadi/item.h>
25
26#include <KConfigGroup>
27#include <KDebug>
28
29#include <QScrollBar>
30#include <QTimer>
31#include <QTreeView>
32
33namespace Akonadi {
34
35struct State
36{
37 State()
38 : selected(false)
39 , expanded(false)
40 , currentIndex(false)
41 {
42 }
43 bool selected;
44 bool expanded;
45 bool currentIndex;
46};
47
48class EntityTreeViewStateSaverPrivate
49{
50public:
51 explicit EntityTreeViewStateSaverPrivate(EntityTreeViewStateSaver *parent)
52 : q(parent)
53 , view(0)
54 , horizontalScrollBarValue(-1)
55 , verticalScrollBarValue(-1)
56 {
57 }
58
59 inline bool hasChanges() const
60 {
61 return !pendingCollectionChanges.isEmpty() || !pendingItemChanges.isEmpty();
62 }
63
64 static inline QString key(const QModelIndex &index)
65 {
66 if (!index.isValid()) {
67 return QLatin1String("x-1");
68 }
69 const Collection c = index.data(EntityTreeModel::CollectionRole).value<Collection>();
70 if (c.isValid()) {
71 return QString::fromLatin1("c%1").arg(c.id());
72 }
73 return QString::fromLatin1("i%1").arg(index.data(EntityTreeModel::ItemIdRole).value<Entity::Id>());
74 }
75
76 void saveState(const QModelIndex &index, QStringList &selection, QStringList &expansion)
77 {
78 const QString cfgKey = key(index);
79 if (view->selectionModel()->isSelected(index)) {
80 selection.append(cfgKey);
81 }
82 if (view->isExpanded(index)) {
83 expansion.append(cfgKey);
84 }
85 for (int i = 0; i < view->model()->rowCount(index); ++i) {
86 const QModelIndex child = view->model()->index(i, 0, index);
87 saveState(child, selection, expansion);
88 }
89 }
90
91 inline void restoreState(const QModelIndex &index, const State &state)
92 {
93 if (state.selected) {
94 view->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
95 }
96 if (state.expanded) {
97 view->setExpanded(index, true);
98 }
99 if (state.currentIndex) {
100 view->setCurrentIndex(index);
101 }
102 QTimer::singleShot(0, q, SLOT(restoreScrollBarState()));
103 }
104
105 void restoreState(const QModelIndex &index)
106 {
107 const Collection c = index.data(EntityTreeModel::CollectionRole).value<Collection>();
108 if (c.isValid()) {
109 if (pendingCollectionChanges.contains(c.id())) {
110 restoreState(index, pendingCollectionChanges.value(c.id()));
111 pendingCollectionChanges.remove(c.id());
112 }
113 } else {
114 Entity::Id itemId = index.data(EntityTreeModel::ItemIdRole).value<Entity::Id>();
115 if (pendingItemChanges.contains(itemId)) {
116 restoreState(index, pendingItemChanges.value(itemId));
117 pendingItemChanges.remove(itemId);
118 }
119 }
120 for (int i = 0; i < view->model()->rowCount(index) && hasChanges(); ++i) {
121 const QModelIndex child = view->model()->index(i, 0, index);
122 restoreState(child);
123 }
124 }
125
126 inline void restoreScrollBarState()
127 {
128 if (horizontalScrollBarValue >= 0 && horizontalScrollBarValue <= view->horizontalScrollBar()->maximum()) {
129 view->horizontalScrollBar()->setValue(horizontalScrollBarValue);
130 horizontalScrollBarValue = -1;
131 }
132 if (verticalScrollBarValue >= 0 && verticalScrollBarValue <= view->verticalScrollBar()->maximum()) {
133 view->verticalScrollBar()->setValue(verticalScrollBarValue);
134 verticalScrollBarValue = -1;
135 }
136 }
137
138 void rowsInserted(const QModelIndex &index, int start, int end)
139 {
140 if (!hasChanges()) {
141 QObject::disconnect(view->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
142 q, SLOT(rowsInserted(QModelIndex,int,int)));
143 return;
144 }
145
146 for (int i = start; i <= end && hasChanges(); ++i) {
147 const QModelIndex child = view->model()->index(i, 0, index);;
148 restoreState(child);
149 }
150 }
151
152 EntityTreeViewStateSaver *q;
153 QTreeView *view;
154 QHash<Entity::Id, State> pendingCollectionChanges, pendingItemChanges;
155 int horizontalScrollBarValue, verticalScrollBarValue;
156};
157
158EntityTreeViewStateSaver::EntityTreeViewStateSaver(QTreeView *view)
159 : QObject(view)
160 , d(new EntityTreeViewStateSaverPrivate(this))
161{
162 d->view = view;
163}
164
165EntityTreeViewStateSaver::~EntityTreeViewStateSaver()
166{
167 delete d;
168}
169
170void EntityTreeViewStateSaver::saveState(KConfigGroup &configGroup) const
171{
172 if (!d->view->model()) {
173 return;
174 }
175
176 configGroup.deleteGroup();
177 QStringList selection, expansion;
178 const int rowCount = d->view->model()->rowCount();
179 for (int i = 0; i < rowCount; ++i) {
180 const QModelIndex index = d->view->model()->index(i, 0);
181 d->saveState(index, selection, expansion);
182 }
183
184 const QString currentIndex = d->key(d->view->selectionModel()->currentIndex());
185
186 configGroup.writeEntry("Selection", selection);
187 configGroup.writeEntry("Expansion", expansion);
188 configGroup.writeEntry("CurrentIndex", currentIndex);
189 configGroup.writeEntry("ScrollBarHorizontal", d->view->horizontalScrollBar()->value());
190 configGroup.writeEntry("ScrollBarVertical", d->view->verticalScrollBar()->value());
191}
192
193void EntityTreeViewStateSaver::restoreState(const KConfigGroup &configGroup) const
194{
195 if (!d->view->model()) {
196 return;
197 }
198
199 const QStringList selection = configGroup.readEntry("Selection", QStringList());
200 foreach (const QString &key, selection) {
201 Entity::Id id = key.mid(1).toLongLong();
202 if (id < 0) {
203 continue;
204 }
205 if (key.startsWith(QLatin1Char('c'))) {
206 d->pendingCollectionChanges[id].selected = true;
207 } else if (key.startsWith(QLatin1Char('i'))) {
208 d->pendingItemChanges[id].selected = true;
209 }
210 }
211
212 const QStringList expansion = configGroup.readEntry("Expansion", QStringList());
213 foreach (const QString &key, expansion) {
214 Entity::Id id = key.mid(1).toLongLong();
215 if (id < 0) {
216 continue;
217 }
218 if (key.startsWith(QLatin1Char('c'))) {
219 d->pendingCollectionChanges[id].expanded = true;
220 } else if (key.startsWith(QLatin1Char('i'))) {
221 d->pendingItemChanges[id].expanded = true;
222 }
223 }
224
225 const QString key = configGroup.readEntry("CurrentIndex", QString());
226 const Entity::Id id = key.mid(1).toLongLong();
227 if (id >= 0) {
228 if (key.startsWith(QLatin1Char('c'))) {
229 d->pendingCollectionChanges[id].currentIndex = true;
230 } else if (key.startsWith(QLatin1Char('i'))) {
231 d->pendingItemChanges[id].currentIndex = true;
232 }
233 }
234
235 d->horizontalScrollBarValue = configGroup.readEntry("ScrollBarHorizontal", -1);
236 d->verticalScrollBarValue = configGroup.readEntry("ScrollBarVertical", -1);
237
238 // initial restore run, for everything already loaded
239 for (int i = 0; i < d->view->model()->rowCount() && d->hasChanges(); ++i) {
240 const QModelIndex index = d->view->model()->index(i, 0);
241 d->restoreState(index);
242 }
243 d->restoreScrollBarState();
244
245 // watch the model for stuff coming in delayed
246 if (d->hasChanges()) {
247 connect(d->view->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
248 SLOT(rowsInserted(QModelIndex,int,int)));
249 }
250}
251
252} // namespace Akonadi
253
254#include "moc_entitytreeviewstatesaver.cpp"
255