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
28SelectionModelSynchronizer::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
42bool 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
58void 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
82void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel *model)
83{
84 disconnect(model, 0, this, 0);
85 disconnect(this, 0, model, 0);
86 selectionModelDestroyed(model);
87}
88
89
90void 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
105void SelectionModelSynchronizer::syncedCurrentChanged(const QModelIndex &current, 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
120void 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
150QModelIndex 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
176QItemSelection 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
201QModelIndex 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
218QItemSelection 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
235void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex &index)
236{
237 _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
238}
239
240
241void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection &selection)
242{
243 _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect);
244}
245
246
247void SelectionModelSynchronizer::currentChanged(const QModelIndex &current, 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
261void 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