1 /***************************************************************************
2 * Copyright (C) 2005-2019 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>
27 SelectionModelSynchronizer::SelectionModelSynchronizer(QAbstractItemModel* parent)
30 , _selectionModel(parent)
32 connect(&_selectionModel, &QItemSelectionModel::currentChanged, this, &SelectionModelSynchronizer::currentChanged);
33 connect(&_selectionModel, &QItemSelectionModel::selectionChanged, this, &SelectionModelSynchronizer::selectionChanged);
36 bool SelectionModelSynchronizer::checkBaseModel(QItemSelectionModel* selectionModel)
41 const QAbstractItemModel* baseModel = selectionModel->model();
42 const QAbstractProxyModel* proxyModel = nullptr;
43 while ((proxyModel = qobject_cast<const QAbstractProxyModel*>(baseModel)) != nullptr) {
44 baseModel = proxyModel->sourceModel();
45 if (baseModel == model())
48 return baseModel == model();
51 void SelectionModelSynchronizer::synchronizeSelectionModel(QItemSelectionModel* selectionModel)
53 if (!checkBaseModel(selectionModel)) {
54 qWarning() << "cannot Synchronize SelectionModel" << selectionModel << "which has a different baseModel()";
58 if (_selectionModels.contains(selectionModel)) {
59 selectionModel->setCurrentIndex(mapFromSource(currentIndex(), selectionModel), QItemSelectionModel::Current);
60 selectionModel->select(mapSelectionFromSource(currentSelection(), selectionModel), QItemSelectionModel::ClearAndSelect);
64 connect(selectionModel, &QItemSelectionModel::currentChanged, this, &SelectionModelSynchronizer::syncedCurrentChanged);
65 connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &SelectionModelSynchronizer::syncedSelectionChanged);
67 connect(selectionModel, &QObject::destroyed, this, &SelectionModelSynchronizer::selectionModelDestroyed);
69 _selectionModels << selectionModel;
72 void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel* model)
74 disconnect(model, nullptr, this, nullptr);
75 disconnect(this, nullptr, model, nullptr);
76 selectionModelDestroyed(model);
79 void SelectionModelSynchronizer::selectionModelDestroyed(QObject* object)
81 auto* model = static_cast<QItemSelectionModel*>(object);
82 QSet<QItemSelectionModel*>::iterator iter = _selectionModels.begin();
83 while (iter != _selectionModels.end()) {
85 iter = _selectionModels.erase(iter);
93 void SelectionModelSynchronizer::syncedCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
97 if (!_changeCurrentEnabled)
100 auto* selectionModel = qobject_cast<QItemSelectionModel*>(sender());
101 Q_ASSERT(selectionModel);
102 QModelIndex newSourceCurrent = mapToSource(current, selectionModel);
103 if (newSourceCurrent.isValid() && newSourceCurrent != currentIndex())
104 setCurrentIndex(newSourceCurrent);
107 void SelectionModelSynchronizer::syncedSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
110 Q_UNUSED(deselected);
112 if (!_changeSelectionEnabled)
115 auto* selectionModel = qobject_cast<QItemSelectionModel*>(sender());
116 Q_ASSERT(selectionModel);
118 QItemSelection mappedSelection = selectionModel->selection();
119 QItemSelection currentSelectionMapped = mapSelectionFromSource(currentSelection(), selectionModel);
121 QItemSelection checkSelection = currentSelectionMapped;
122 checkSelection.merge(mappedSelection, QItemSelectionModel::Deselect);
123 if (checkSelection.isEmpty()) {
124 // that means the new selection contains the current selection (currentSel - newSel = {})
125 checkSelection = mappedSelection;
126 checkSelection.merge(currentSelectionMapped, QItemSelectionModel::Deselect);
127 if (checkSelection.isEmpty()) {
128 // that means the current selection contains the new selection (newSel - currentSel = {})
129 // -> currentSel == newSel
133 setCurrentSelection(mapSelectionToSource(mappedSelection, selectionModel));
136 QModelIndex SelectionModelSynchronizer::mapFromSource(const QModelIndex& sourceIndex, const QItemSelectionModel* selectionModel)
138 Q_ASSERT(selectionModel);
140 QModelIndex mappedIndex = sourceIndex;
142 // make a list of all involved proxies, wie have to traverse backwards
143 QList<const QAbstractProxyModel*> proxyModels;
144 const QAbstractItemModel* baseModel = selectionModel->model();
145 const QAbstractProxyModel* proxyModel = nullptr;
146 while ((proxyModel = qobject_cast<const QAbstractProxyModel*>(baseModel)) != nullptr) {
147 if (baseModel == model())
149 proxyModels << proxyModel;
150 baseModel = proxyModel->sourceModel();
154 for (int i = proxyModels.count() - 1; i >= 0; i--) {
155 mappedIndex = proxyModels[i]->mapFromSource(mappedIndex);
161 QItemSelection SelectionModelSynchronizer::mapSelectionFromSource(const QItemSelection& sourceSelection,
162 const QItemSelectionModel* selectionModel)
164 Q_ASSERT(selectionModel);
166 QItemSelection mappedSelection = sourceSelection;
168 // make a list of all involved proxies, wie have to traverse backwards
169 QList<const QAbstractProxyModel*> proxyModels;
170 const QAbstractItemModel* baseModel = selectionModel->model();
171 const QAbstractProxyModel* proxyModel = nullptr;
172 while ((proxyModel = qobject_cast<const QAbstractProxyModel*>(baseModel)) != nullptr) {
173 if (baseModel == model())
175 proxyModels << proxyModel;
176 baseModel = proxyModel->sourceModel();
180 for (int i = proxyModels.count() - 1; i >= 0; i--) {
181 mappedSelection = proxyModels[i]->mapSelectionFromSource(mappedSelection);
183 return mappedSelection;
186 QModelIndex SelectionModelSynchronizer::mapToSource(const QModelIndex& index, QItemSelectionModel* selectionModel)
188 Q_ASSERT(selectionModel);
190 QModelIndex sourceIndex = index;
191 const QAbstractItemModel* baseModel = selectionModel->model();
192 const QAbstractProxyModel* proxyModel = nullptr;
193 while ((proxyModel = qobject_cast<const QAbstractProxyModel*>(baseModel)) != nullptr) {
194 sourceIndex = proxyModel->mapToSource(sourceIndex);
195 baseModel = proxyModel->sourceModel();
196 if (baseModel == model())
202 QItemSelection SelectionModelSynchronizer::mapSelectionToSource(const QItemSelection& selection, QItemSelectionModel* selectionModel)
204 Q_ASSERT(selectionModel);
206 QItemSelection sourceSelection = selection;
207 const QAbstractItemModel* baseModel = selectionModel->model();
208 const QAbstractProxyModel* proxyModel = nullptr;
209 while ((proxyModel = qobject_cast<const QAbstractProxyModel*>(baseModel)) != nullptr) {
210 sourceSelection = proxyModel->mapSelectionToSource(sourceSelection);
211 baseModel = proxyModel->sourceModel();
212 if (baseModel == model())
215 return sourceSelection;
218 void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex& index)
220 _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
223 void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection& selection)
225 _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect);
228 void SelectionModelSynchronizer::currentChanged(const QModelIndex& current, const QModelIndex& previous)
232 _changeCurrentEnabled = false;
233 QSet<QItemSelectionModel*>::iterator iter = _selectionModels.begin();
234 while (iter != _selectionModels.end()) {
235 (*iter)->setCurrentIndex(mapFromSource(current, (*iter)), QItemSelectionModel::Current);
238 _changeCurrentEnabled = true;
240 // Trigger a dataChanged() signal from the base model to update all proxy models (e.g. filters).
241 // Since signals are protected, we have to use invokeMethod for faking signal emission.
242 if (previous.isValid()) {
243 QMetaObject::invokeMethod(model(), "dataChanged", Qt::DirectConnection, Q_ARG(QModelIndex, previous), Q_ARG(QModelIndex, previous));
247 void SelectionModelSynchronizer::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
250 Q_UNUSED(deselected);
252 _changeSelectionEnabled = false;
253 QSet<QItemSelectionModel*>::iterator iter = _selectionModels.begin();
254 while (iter != _selectionModels.end()) {
255 (*iter)->select(mapSelectionFromSource(currentSelection(), (*iter)), QItemSelectionModel::ClearAndSelect);
258 _changeSelectionEnabled = true;