1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 by the Quassel Project * |
3 | * devel@quassel-irc.org * |
4 | * * |
5 | * This program is free software; you can redistribute it and/or modify * |
6 | * it under the terms of the GNU General Public License as published by * |
7 | * the Free Software Foundation; either version 2 of the License, or * |
8 | * (at your option) version 3. * |
9 | * * |
10 | * This program is distributed in the hope that it will be useful, * |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
13 | * GNU General Public License for more details. * |
14 | * * |
15 | * You should have received a copy of the GNU General Public License * |
16 | * along with this program; if not, write to the * |
17 | * Free Software Foundation, Inc., * |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
19 | ***************************************************************************/ |
20 | |
21 | #include "selectionmodelsynchronizer.h" |
22 | |
23 | #include <QAbstractItemModel> |
24 | #include <QAbstractProxyModel> |
25 | |
26 | #include <QDebug> |
27 | |
28 | SelectionModelSynchronizer::SelectionModelSynchronizer(QAbstractItemModel *parent) |
29 | : QObject(parent), |
30 | _model(parent), |
31 | _selectionModel(parent), |
32 | _changeCurrentEnabled(true), |
33 | _changeSelectionEnabled(true) |
34 | { |
35 | connect(&_selectionModel, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), |
36 | this, SLOT(currentChanged(const QModelIndex &, const QModelIndex &))); |
37 | connect(&_selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), |
38 | this, SLOT(selectionChanged(const QItemSelection &, const QItemSelection &))); |
39 | } |
40 | |
41 | |
42 | bool SelectionModelSynchronizer::checkBaseModel(QItemSelectionModel *selectionModel) |
43 | { |
44 | if (!selectionModel) |
45 | return false; |
46 | |
47 | const QAbstractItemModel *baseModel = selectionModel->model(); |
48 | const QAbstractProxyModel *proxyModel = 0; |
49 | while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) { |
50 | baseModel = proxyModel->sourceModel(); |
51 | if (baseModel == model()) |
52 | break; |
53 | } |
54 | return baseModel == model(); |
55 | } |
56 | |
57 | |
58 | void SelectionModelSynchronizer::synchronizeSelectionModel(QItemSelectionModel *selectionModel) |
59 | { |
60 | if (!checkBaseModel(selectionModel)) { |
61 | qWarning() << "cannot Synchronize SelectionModel" << selectionModel << "which has a different baseModel()" ; |
62 | return; |
63 | } |
64 | |
65 | if (_selectionModels.contains(selectionModel)) { |
66 | selectionModel->setCurrentIndex(mapFromSource(currentIndex(), selectionModel), QItemSelectionModel::Current); |
67 | selectionModel->select(mapSelectionFromSource(currentSelection(), selectionModel), QItemSelectionModel::ClearAndSelect); |
68 | return; |
69 | } |
70 | |
71 | connect(selectionModel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), |
72 | this, SLOT(syncedCurrentChanged(QModelIndex, QModelIndex))); |
73 | connect(selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), |
74 | this, SLOT(syncedSelectionChanged(QItemSelection, QItemSelection))); |
75 | |
76 | connect(selectionModel, SIGNAL(destroyed(QObject *)), this, SLOT(selectionModelDestroyed(QObject *))); |
77 | |
78 | _selectionModels << selectionModel; |
79 | } |
80 | |
81 | |
82 | void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel *model) |
83 | { |
84 | disconnect(model, 0, this, 0); |
85 | disconnect(this, 0, model, 0); |
86 | selectionModelDestroyed(model); |
87 | } |
88 | |
89 | |
90 | void SelectionModelSynchronizer::selectionModelDestroyed(QObject *object) |
91 | { |
92 | QItemSelectionModel *model = static_cast<QItemSelectionModel *>(object); |
93 | QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin(); |
94 | while (iter != _selectionModels.end()) { |
95 | if (*iter == model) { |
96 | iter = _selectionModels.erase(iter); |
97 | } |
98 | else { |
99 | iter++; |
100 | } |
101 | } |
102 | } |
103 | |
104 | |
105 | void SelectionModelSynchronizer::syncedCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
106 | { |
107 | Q_UNUSED(previous); |
108 | |
109 | if (!_changeCurrentEnabled) |
110 | return; |
111 | |
112 | QItemSelectionModel *selectionModel = qobject_cast<QItemSelectionModel *>(sender()); |
113 | Q_ASSERT(selectionModel); |
114 | QModelIndex newSourceCurrent = mapToSource(current, selectionModel); |
115 | if (newSourceCurrent.isValid() && newSourceCurrent != currentIndex()) |
116 | setCurrentIndex(newSourceCurrent); |
117 | } |
118 | |
119 | |
120 | void SelectionModelSynchronizer::syncedSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) |
121 | { |
122 | Q_UNUSED(selected); |
123 | Q_UNUSED(deselected); |
124 | |
125 | if (!_changeSelectionEnabled) |
126 | return; |
127 | |
128 | QItemSelectionModel *selectionModel = qobject_cast<QItemSelectionModel *>(sender()); |
129 | Q_ASSERT(selectionModel); |
130 | |
131 | QItemSelection mappedSelection = selectionModel->selection(); |
132 | QItemSelection currentSelectionMapped = mapSelectionFromSource(currentSelection(), selectionModel); |
133 | |
134 | QItemSelection checkSelection = currentSelectionMapped; |
135 | checkSelection.merge(mappedSelection, QItemSelectionModel::Deselect); |
136 | if (checkSelection.isEmpty()) { |
137 | // that means the new selection contains the current selection (currentSel - newSel = {}) |
138 | checkSelection = mappedSelection; |
139 | checkSelection.merge(currentSelectionMapped, QItemSelectionModel::Deselect); |
140 | if (checkSelection.isEmpty()) { |
141 | // that means the current selection contains the new selection (newSel - currentSel = {}) |
142 | // -> currentSel == newSel |
143 | return; |
144 | } |
145 | } |
146 | setCurrentSelection(mapSelectionToSource(mappedSelection, selectionModel)); |
147 | } |
148 | |
149 | |
150 | QModelIndex SelectionModelSynchronizer::mapFromSource(const QModelIndex &sourceIndex, const QItemSelectionModel *selectionModel) |
151 | { |
152 | Q_ASSERT(selectionModel); |
153 | |
154 | QModelIndex mappedIndex = sourceIndex; |
155 | |
156 | // make a list of all involved proxies, wie have to traverse backwards |
157 | QList<const QAbstractProxyModel *> proxyModels; |
158 | const QAbstractItemModel *baseModel = selectionModel->model(); |
159 | const QAbstractProxyModel *proxyModel = 0; |
160 | while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) { |
161 | if (baseModel == model()) |
162 | break; |
163 | proxyModels << proxyModel; |
164 | baseModel = proxyModel->sourceModel(); |
165 | } |
166 | |
167 | // now traverse it; |
168 | for (int i = proxyModels.count() - 1; i >= 0; i--) { |
169 | mappedIndex = proxyModels[i]->mapFromSource(mappedIndex); |
170 | } |
171 | |
172 | return mappedIndex; |
173 | } |
174 | |
175 | |
176 | QItemSelection SelectionModelSynchronizer::mapSelectionFromSource(const QItemSelection &sourceSelection, const QItemSelectionModel *selectionModel) |
177 | { |
178 | Q_ASSERT(selectionModel); |
179 | |
180 | QItemSelection mappedSelection = sourceSelection; |
181 | |
182 | // make a list of all involved proxies, wie have to traverse backwards |
183 | QList<const QAbstractProxyModel *> proxyModels; |
184 | const QAbstractItemModel *baseModel = selectionModel->model(); |
185 | const QAbstractProxyModel *proxyModel = 0; |
186 | while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) { |
187 | if (baseModel == model()) |
188 | break; |
189 | proxyModels << proxyModel; |
190 | baseModel = proxyModel->sourceModel(); |
191 | } |
192 | |
193 | // now traverse it; |
194 | for (int i = proxyModels.count() - 1; i >= 0; i--) { |
195 | mappedSelection = proxyModels[i]->mapSelectionFromSource(mappedSelection); |
196 | } |
197 | return mappedSelection; |
198 | } |
199 | |
200 | |
201 | QModelIndex SelectionModelSynchronizer::mapToSource(const QModelIndex &index, QItemSelectionModel *selectionModel) |
202 | { |
203 | Q_ASSERT(selectionModel); |
204 | |
205 | QModelIndex sourceIndex = index; |
206 | const QAbstractItemModel *baseModel = selectionModel->model(); |
207 | const QAbstractProxyModel *proxyModel = 0; |
208 | while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) { |
209 | sourceIndex = proxyModel->mapToSource(sourceIndex); |
210 | baseModel = proxyModel->sourceModel(); |
211 | if (baseModel == model()) |
212 | break; |
213 | } |
214 | return sourceIndex; |
215 | } |
216 | |
217 | |
218 | QItemSelection SelectionModelSynchronizer::mapSelectionToSource(const QItemSelection &selection, QItemSelectionModel *selectionModel) |
219 | { |
220 | Q_ASSERT(selectionModel); |
221 | |
222 | QItemSelection sourceSelection = selection; |
223 | const QAbstractItemModel *baseModel = selectionModel->model(); |
224 | const QAbstractProxyModel *proxyModel = 0; |
225 | while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != 0) { |
226 | sourceSelection = proxyModel->mapSelectionToSource(sourceSelection); |
227 | baseModel = proxyModel->sourceModel(); |
228 | if (baseModel == model()) |
229 | break; |
230 | } |
231 | return sourceSelection; |
232 | } |
233 | |
234 | |
235 | void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex &index) |
236 | { |
237 | _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current); |
238 | } |
239 | |
240 | |
241 | void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection &selection) |
242 | { |
243 | _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect); |
244 | } |
245 | |
246 | |
247 | void SelectionModelSynchronizer::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
248 | { |
249 | Q_UNUSED(previous); |
250 | |
251 | _changeCurrentEnabled = false; |
252 | QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin(); |
253 | while (iter != _selectionModels.end()) { |
254 | (*iter)->setCurrentIndex(mapFromSource(current, (*iter)), QItemSelectionModel::Current); |
255 | iter++; |
256 | } |
257 | _changeCurrentEnabled = true; |
258 | } |
259 | |
260 | |
261 | void SelectionModelSynchronizer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) |
262 | { |
263 | Q_UNUSED(selected); |
264 | Q_UNUSED(deselected); |
265 | |
266 | _changeSelectionEnabled = false; |
267 | QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin(); |
268 | while (iter != _selectionModels.end()) { |
269 | (*iter)->select(mapSelectionFromSource(currentSelection(), (*iter)), QItemSelectionModel::ClearAndSelect); |
270 | iter++; |
271 | } |
272 | _changeSelectionEnabled = true; |
273 | } |
274 | |