d5b8f8bcf77dda3598092ff7aa2138eae3fc1628
[quassel.git] / src / client / selectionmodelsynchronizer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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 {
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 &)));
37 }
38
39
40 bool SelectionModelSynchronizer::checkBaseModel(QItemSelectionModel *selectionModel)
41 {
42     if (!selectionModel)
43         return false;
44
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())
50             break;
51     }
52     return baseModel == model();
53 }
54
55
56 void SelectionModelSynchronizer::synchronizeSelectionModel(QItemSelectionModel *selectionModel)
57 {
58     if (!checkBaseModel(selectionModel)) {
59         qWarning() << "cannot Synchronize SelectionModel" << selectionModel << "which has a different baseModel()";
60         return;
61     }
62
63     if (_selectionModels.contains(selectionModel)) {
64         selectionModel->setCurrentIndex(mapFromSource(currentIndex(), selectionModel), QItemSelectionModel::Current);
65         selectionModel->select(mapSelectionFromSource(currentSelection(), selectionModel), QItemSelectionModel::ClearAndSelect);
66         return;
67     }
68
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)));
73
74     connect(selectionModel, SIGNAL(destroyed(QObject *)), this, SLOT(selectionModelDestroyed(QObject *)));
75
76     _selectionModels << selectionModel;
77 }
78
79
80 void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel *model)
81 {
82     disconnect(model, nullptr, this, nullptr);
83     disconnect(this, nullptr, model, nullptr);
84     selectionModelDestroyed(model);
85 }
86
87
88 void SelectionModelSynchronizer::selectionModelDestroyed(QObject *object)
89 {
90     auto *model = static_cast<QItemSelectionModel *>(object);
91     QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
92     while (iter != _selectionModels.end()) {
93         if (*iter == model) {
94             iter = _selectionModels.erase(iter);
95         }
96         else {
97             ++iter;
98         }
99     }
100 }
101
102
103 void SelectionModelSynchronizer::syncedCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
104 {
105     Q_UNUSED(previous);
106
107     if (!_changeCurrentEnabled)
108         return;
109
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);
115 }
116
117
118 void SelectionModelSynchronizer::syncedSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
119 {
120     Q_UNUSED(selected);
121     Q_UNUSED(deselected);
122
123     if (!_changeSelectionEnabled)
124         return;
125
126     auto *selectionModel = qobject_cast<QItemSelectionModel *>(sender());
127     Q_ASSERT(selectionModel);
128
129     QItemSelection mappedSelection = selectionModel->selection();
130     QItemSelection currentSelectionMapped = mapSelectionFromSource(currentSelection(), selectionModel);
131
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
141             return;
142         }
143     }
144     setCurrentSelection(mapSelectionToSource(mappedSelection, selectionModel));
145 }
146
147
148 QModelIndex SelectionModelSynchronizer::mapFromSource(const QModelIndex &sourceIndex, const QItemSelectionModel *selectionModel)
149 {
150     Q_ASSERT(selectionModel);
151
152     QModelIndex mappedIndex = sourceIndex;
153
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())
160             break;
161         proxyModels << proxyModel;
162         baseModel = proxyModel->sourceModel();
163     }
164
165     // now traverse it;
166     for (int i = proxyModels.count() - 1; i >= 0; i--) {
167         mappedIndex = proxyModels[i]->mapFromSource(mappedIndex);
168     }
169
170     return mappedIndex;
171 }
172
173
174 QItemSelection SelectionModelSynchronizer::mapSelectionFromSource(const QItemSelection &sourceSelection, const QItemSelectionModel *selectionModel)
175 {
176     Q_ASSERT(selectionModel);
177
178     QItemSelection mappedSelection = sourceSelection;
179
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())
186             break;
187         proxyModels << proxyModel;
188         baseModel = proxyModel->sourceModel();
189     }
190
191     // now traverse it;
192     for (int i = proxyModels.count() - 1; i >= 0; i--) {
193         mappedSelection = proxyModels[i]->mapSelectionFromSource(mappedSelection);
194     }
195     return mappedSelection;
196 }
197
198
199 QModelIndex SelectionModelSynchronizer::mapToSource(const QModelIndex &index, QItemSelectionModel *selectionModel)
200 {
201     Q_ASSERT(selectionModel);
202
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())
210             break;
211     }
212     return sourceIndex;
213 }
214
215
216 QItemSelection SelectionModelSynchronizer::mapSelectionToSource(const QItemSelection &selection, QItemSelectionModel *selectionModel)
217 {
218     Q_ASSERT(selectionModel);
219
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())
227             break;
228     }
229     return sourceSelection;
230 }
231
232
233 void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex &index)
234 {
235     _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
236 }
237
238
239 void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection &selection)
240 {
241     _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect);
242 }
243
244
245 void SelectionModelSynchronizer::currentChanged(const QModelIndex &current, const QModelIndex &previous)
246 {
247     Q_UNUSED(previous);
248
249     _changeCurrentEnabled = false;
250     QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
251     while (iter != _selectionModels.end()) {
252         (*iter)->setCurrentIndex(mapFromSource(current, (*iter)), QItemSelectionModel::Current);
253         ++iter;
254     }
255     _changeCurrentEnabled = true;
256
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));
262     }
263 }
264
265
266 void SelectionModelSynchronizer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
267 {
268     Q_UNUSED(selected);
269     Q_UNUSED(deselected);
270
271     _changeSelectionEnabled = false;
272     QSet<QItemSelectionModel *>::iterator iter = _selectionModels.begin();
273     while (iter != _selectionModels.end()) {
274         (*iter)->select(mapSelectionFromSource(currentSelection(), (*iter)), QItemSelectionModel::ClearAndSelect);
275         ++iter;
276     }
277     _changeSelectionEnabled = true;
278 }