Merge pull request #1 from sandsmark/master
[quassel.git] / src / client / selectionmodelsynchronizer.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2013 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     _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
42 bool 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
58 void 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
82 void SelectionModelSynchronizer::removeSelectionModel(QItemSelectionModel *model)
83 {
84     disconnect(model, 0, this, 0);
85     disconnect(this, 0, model, 0);
86     selectionModelDestroyed(model);
87 }
88
89
90 void 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
105 void 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
120 void 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
150 QModelIndex 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
176 QItemSelection 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
201 QModelIndex 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
218 QItemSelection 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
235 void SelectionModelSynchronizer::setCurrentIndex(const QModelIndex &index)
236 {
237     _selectionModel.setCurrentIndex(index, QItemSelectionModel::Current);
238 }
239
240
241 void SelectionModelSynchronizer::setCurrentSelection(const QItemSelection &selection)
242 {
243     _selectionModel.select(selection, QItemSelectionModel::ClearAndSelect);
244 }
245
246
247 void 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
261 void 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 }