src: Yearly copyright bump
[quassel.git] / src / client / selectionmodelsynchronizer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 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 #include <QDebug>
26
27 SelectionModelSynchronizer::SelectionModelSynchronizer(QAbstractItemModel* parent)
28     : QObject(parent)
29     , _model(parent)
30     , _selectionModel(parent)
31 {
32     connect(&_selectionModel, &QItemSelectionModel::currentChanged, this, &SelectionModelSynchronizer::currentChanged);
33     connect(&_selectionModel, &QItemSelectionModel::selectionChanged, this, &SelectionModelSynchronizer::selectionChanged);
34 }
35
36 bool SelectionModelSynchronizer::checkBaseModel(QItemSelectionModel* selectionModel)
37 {
38     if (!selectionModel)
39         return false;
40
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())
46             break;
47     }
48     return baseModel == model();
49 }
50
51 void SelectionModelSynchronizer::synchronizeSelectionModel(QItemSelectionModel* selectionModel)
52 {
53     if (!checkBaseModel(selectionModel)) {
54         qWarning() << "cannot Synchronize SelectionModel" << selectionModel << "which has a different baseModel()";
55         return;
56     }
57
58     if (_selectionModels.contains(selectionModel)) {
59         selectionModel->setCurrentIndex(mapFromSource(currentIndex(), selectionModel), QItemSelectionModel::Current);
60         selectionModel->select(mapSelectionFromSource(currentSelection(), selectionModel), QItemSelectionModel::ClearAndSelect);
61         return;
62     }
63
64     connect(selectionModel, &QItemSelectionModel::currentChanged, this, &SelectionModelSynchronizer::syncedCurrentChanged);
65     connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &SelectionModelSynchronizer::syncedSelectionChanged);
66
67     connect(selectionModel, &QObject::destroyed, this, &SelectionModelSynchronizer::selectionModelDestroyed);
68
69     _selectionModels << selectionModel;
70 }
71
72 void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel* model)
73 {
74     disconnect(model, nullptr, this, nullptr);
75     disconnect(this, nullptr, model, nullptr);
76     selectionModelDestroyed(model);
77 }
78
79 void SelectionModelSynchronizer::selectionModelDestroyed(QObject* object)
80 {
81     auto* model = static_cast<QItemSelectionModel*>(object);
82     QSet<QItemSelectionModel*>::iterator iter = _selectionModels.begin();
83     while (iter != _selectionModels.end()) {
84         if (*iter == model) {
85             iter = _selectionModels.erase(iter);
86         }
87         else {
88             ++iter;
89         }
90     }
91 }
92
93 void SelectionModelSynchronizer::syncedCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
94 {
95     Q_UNUSED(previous);
96
97     if (!_changeCurrentEnabled)
98         return;
99
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);
105 }
106
107 void SelectionModelSynchronizer::syncedSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
108 {
109     Q_UNUSED(selected);
110     Q_UNUSED(deselected);
111
112     if (!_changeSelectionEnabled)
113         return;
114
115     auto* selectionModel = qobject_cast<QItemSelectionModel*>(sender());
116     Q_ASSERT(selectionModel);
117
118     QItemSelection mappedSelection = selectionModel->selection();
119     QItemSelection currentSelectionMapped = mapSelectionFromSource(currentSelection(), selectionModel);
120
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
130             return;
131         }
132     }
133     setCurrentSelection(mapSelectionToSource(mappedSelection, selectionModel));
134 }
135
136 QModelIndex SelectionModelSynchronizer::mapFromSource(const QModelIndex& sourceIndex, const QItemSelectionModel* selectionModel)
137 {
138     Q_ASSERT(selectionModel);
139
140     QModelIndex mappedIndex = sourceIndex;
141
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())
148             break;
149         proxyModels << proxyModel;
150         baseModel = proxyModel->sourceModel();
151     }
152
153     // now traverse it;
154     for (int i = proxyModels.count() - 1; i >= 0; i--) {
155         mappedIndex = proxyModels[i]->mapFromSource(mappedIndex);
156     }
157
158     return mappedIndex;
159 }
160
161 QItemSelection SelectionModelSynchronizer::mapSelectionFromSource(const QItemSelection& sourceSelection,
162                                                                   const QItemSelectionModel* selectionModel)
163 {
164     Q_ASSERT(selectionModel);
165
166     QItemSelection mappedSelection = sourceSelection;
167
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())
174             break;
175         proxyModels << proxyModel;
176         baseModel = proxyModel->sourceModel();
177     }
178
179     // now traverse it;
180     for (int i = proxyModels.count() - 1; i >= 0; i--) {
181         mappedSelection = proxyModels[i]->mapSelectionFromSource(mappedSelection);
182     }
183     return mappedSelection;
184 }
185
186 QModelIndex SelectionModelSynchronizer::mapToSource(const QModelIndex& index, QItemSelectionModel* selectionModel)
187 {
188     Q_ASSERT(selectionModel);
189
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())
197             break;
198     }
199     return sourceIndex;
200 }
201
202 QItemSelection SelectionModelSynchronizer::mapSelectionToSource(const QItemSelection& selection, QItemSelectionModel* selectionModel)
203 {
204     Q_ASSERT(selectionModel);
205
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())
213             break;
214     }
215     return sourceSelection;
216 }
217
218 void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex& index)
219 {
220     _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
221 }
222
223 void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection& selection)
224 {
225     _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect);
226 }
227
228 void SelectionModelSynchronizer::currentChanged(const QModelIndex& current, const QModelIndex& previous)
229 {
230     Q_UNUSED(previous);
231
232     _changeCurrentEnabled = false;
233     QSet<QItemSelectionModel*>::iterator iter = _selectionModels.begin();
234     while (iter != _selectionModels.end()) {
235         (*iter)->setCurrentIndex(mapFromSource(current, (*iter)), QItemSelectionModel::Current);
236         ++iter;
237     }
238     _changeCurrentEnabled = true;
239
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));
244     }
245 }
246
247 void SelectionModelSynchronizer::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
248 {
249     Q_UNUSED(selected);
250     Q_UNUSED(deselected);
251
252     _changeSelectionEnabled = false;
253     QSet<QItemSelectionModel*>::iterator iter = _selectionModels.begin();
254     while (iter != _selectionModels.end()) {
255         (*iter)->select(mapSelectionFromSource(currentSelection(), (*iter)), QItemSelectionModel::ClearAndSelect);
256         ++iter;
257     }
258     _changeSelectionEnabled = true;
259 }