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 | |
33 | namespace Akonadi { |
34 | |
35 | struct 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 | |
48 | class EntityTreeViewStateSaverPrivate |
49 | { |
50 | public: |
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 | |
158 | EntityTreeViewStateSaver::EntityTreeViewStateSaver(QTreeView *view) |
159 | : QObject(view) |
160 | , d(new EntityTreeViewStateSaverPrivate(this)) |
161 | { |
162 | d->view = view; |
163 | } |
164 | |
165 | EntityTreeViewStateSaver::~EntityTreeViewStateSaver() |
166 | { |
167 | delete d; |
168 | } |
169 | |
170 | void 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 | |
193 | void 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 | |