1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include "selectionmodelsynchronizer.h"
23 #include <QAbstractItemModel>
24 #include <QAbstractProxyModel>
28 SelectionModelSynchronizer::SelectionModelSynchronizer(QAbstractItemModel *parent)
31 _selectionModel(parent)
33 connect(&_selectionModel, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
34 this, SLOT(currentChanged(const QModelIndex &, const QModelIndex &)));
35 connect(&_selectionModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
36 this, SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
40 bool SelectionModelSynchronizer::checkBaseModel(QItemSelectionModel *selectionModel)
45 const QAbstractItemModel *baseModel = selectionModel->model();
46 const QAbstractProxyModel *proxyModel = nullptr;
47 while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != nullptr) {
48 baseModel = proxyModel->sourceModel();
49 if (baseModel == model())
52 return baseModel == model();
56 void SelectionModelSynchronizer::synchronizeSelectionModel(QItemSelectionModel *selectionModel)
58 if (!checkBaseModel(selectionModel)) {
59 qWarning() << "cannot Synchronize SelectionModel" << selectionModel << "which has a different baseModel()";
63 if (_selectionModels.contains(selectionModel)) {
64 selectionModel->setCurrentIndex(mapFromSource(currentIndex(), selectionModel), QItemSelectionModel::Current);
65 selectionModel->select(mapSelectionFromSource(currentSelection(), selectionModel), QItemSelectionModel::ClearAndSelect);
69 connect(selectionModel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
70 this, SLOT(syncedCurrentChanged(QModelIndex, QModelIndex)));
71 connect(selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
72 this, SLOT(syncedSelectionChanged(QItemSelection, QItemSelection)));
74 connect(selectionModel, SIGNAL(destroyed(QObject *)), this, SLOT(selectionModelDestroyed(QObject *)));
76 _selectionModels << selectionModel;
80 void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel *model)
82 disconnect(model, nullptr, this, nullptr);
83 disconnect(this, nullptr, model, nullptr);
84 selectionModelDestroyed(model);
88 void SelectionModelSynchronizer::selectionModelDestroyed(QObject *object)
90 auto *model = static_cast<QItemSelectionModel *>(object);
91 QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
92 while (iter != _selectionModels.end()) {
94 iter = _selectionModels.erase(iter);
103 void SelectionModelSynchronizer::syncedCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous)
107 if (!_changeCurrentEnabled)
110 auto *selectionModel = qobject_cast<QItemSelectionModel *>(sender());
111 Q_ASSERT(selectionModel);
112 QModelIndex newSourceCurrent = mapToSource(current, selectionModel);
113 if (newSourceCurrent.isValid() && newSourceCurrent != currentIndex())
114 setCurrentIndex(newSourceCurrent);
118 void SelectionModelSynchronizer::syncedSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
121 Q_UNUSED(deselected);
123 if (!_changeSelectionEnabled)
126 auto *selectionModel = qobject_cast<QItemSelectionModel *>(sender());
127 Q_ASSERT(selectionModel);
129 QItemSelection mappedSelection = selectionModel->selection();
130 QItemSelection currentSelectionMapped = mapSelectionFromSource(currentSelection(), selectionModel);
132 QItemSelection checkSelection = currentSelectionMapped;
133 checkSelection.merge(mappedSelection, QItemSelectionModel::Deselect);
134 if (checkSelection.isEmpty()) {
135 // that means the new selection contains the current selection (currentSel - newSel = {})
136 checkSelection = mappedSelection;
137 checkSelection.merge(currentSelectionMapped, QItemSelectionModel::Deselect);
138 if (checkSelection.isEmpty()) {
139 // that means the current selection contains the new selection (newSel - currentSel = {})
140 // -> currentSel == newSel
144 setCurrentSelection(mapSelectionToSource(mappedSelection, selectionModel));
148 QModelIndex SelectionModelSynchronizer::mapFromSource(const QModelIndex &sourceIndex, const QItemSelectionModel *selectionModel)
150 Q_ASSERT(selectionModel);
152 QModelIndex mappedIndex = sourceIndex;
154 // make a list of all involved proxies, wie have to traverse backwards
155 QList<const QAbstractProxyModel *> proxyModels;
156 const QAbstractItemModel *baseModel = selectionModel->model();
157 const QAbstractProxyModel *proxyModel = nullptr;
158 while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != nullptr) {
159 if (baseModel == model())
161 proxyModels << proxyModel;
162 baseModel = proxyModel->sourceModel();
166 for (int i = proxyModels.count() - 1; i >= 0; i--) {
167 mappedIndex = proxyModels[i]->mapFromSource(mappedIndex);
174 QItemSelection SelectionModelSynchronizer::mapSelectionFromSource(const QItemSelection &sourceSelection, const QItemSelectionModel *selectionModel)
176 Q_ASSERT(selectionModel);
178 QItemSelection mappedSelection = sourceSelection;
180 // make a list of all involved proxies, wie have to traverse backwards
181 QList<const QAbstractProxyModel *> proxyModels;
182 const QAbstractItemModel *baseModel = selectionModel->model();
183 const QAbstractProxyModel *proxyModel = nullptr;
184 while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != nullptr) {
185 if (baseModel == model())
187 proxyModels << proxyModel;
188 baseModel = proxyModel->sourceModel();
192 for (int i = proxyModels.count() - 1; i >= 0; i--) {
193 mappedSelection = proxyModels[i]->mapSelectionFromSource(mappedSelection);
195 return mappedSelection;
199 QModelIndex SelectionModelSynchronizer::mapToSource(const QModelIndex &index, QItemSelectionModel *selectionModel)
201 Q_ASSERT(selectionModel);
203 QModelIndex sourceIndex = index;
204 const QAbstractItemModel *baseModel = selectionModel->model();
205 const QAbstractProxyModel *proxyModel = nullptr;
206 while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != nullptr) {
207 sourceIndex = proxyModel->mapToSource(sourceIndex);
208 baseModel = proxyModel->sourceModel();
209 if (baseModel == model())
216 QItemSelection SelectionModelSynchronizer::mapSelectionToSource(const QItemSelection &selection, QItemSelectionModel *selectionModel)
218 Q_ASSERT(selectionModel);
220 QItemSelection sourceSelection = selection;
221 const QAbstractItemModel *baseModel = selectionModel->model();
222 const QAbstractProxyModel *proxyModel = nullptr;
223 while ((proxyModel = qobject_cast<const QAbstractProxyModel *>(baseModel)) != nullptr) {
224 sourceSelection = proxyModel->mapSelectionToSource(sourceSelection);
225 baseModel = proxyModel->sourceModel();
226 if (baseModel == model())
229 return sourceSelection;
233 void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex &index)
235 _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
239 void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection &selection)
241 _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect);
245 void SelectionModelSynchronizer::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
249 _changeCurrentEnabled = false;
250 QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
251 while (iter != _selectionModels.end()) {
252 (*iter)->setCurrentIndex(mapFromSource(current, (*iter)), QItemSelectionModel::Current);
255 _changeCurrentEnabled = true;
257 // Trigger a dataChanged() signal from the base model to update all proxy models (e.g. filters).
258 // Since signals are protected, we have to use invokeMethod for faking signal emission.
259 if (previous.isValid()) {
260 QMetaObject::invokeMethod(model(), "dataChanged", Qt::DirectConnection,
261 Q_ARG(QModelIndex, previous), Q_ARG(QModelIndex, previous));
266 void SelectionModelSynchronizer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
269 Q_UNUSED(deselected);
271 _changeSelectionEnabled = false;
272 QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
273 while (iter != _selectionModels.end()) {
274 (*iter)->select(mapSelectionFromSource(currentSelection(), (*iter)), QItemSelectionModel::ClearAndSelect);
277 _changeSelectionEnabled = true;