1 /* This file is part of Clementine.
2 Copyright 2010-2012, David Sansome <me@davidsansome.com>
3 Copyright 2011, Arnaud Bienner <arnaud.bienner@gmail.com>
4 Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
5 Copyright 2014, John Maguire <john.maguire@gmail.com>
6
7 Clementine is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 Clementine is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Clementine. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "mergedproxymodel.h"
22 #include "core/logging.h"
23
24 #include <QStringList>
25
26 #include <functional>
27 #include <limits>
28 #include <functional> // for std::placeholders
29
30 // boost::multi_index still relies on these being in the global namespace.
31 using std::placeholders::_1;
32 using std::placeholders::_2;
33
34 #include <boost/multi_index_container.hpp>
35 #include <boost/multi_index/member.hpp>
36 #include <boost/multi_index/hashed_index.hpp>
37 #include <boost/multi_index/ordered_index.hpp>
38
39 using boost::multi_index::hashed_unique;
40 using boost::multi_index::identity;
41 using boost::multi_index::indexed_by;
42 using boost::multi_index::member;
43 using boost::multi_index::multi_index_container;
44 using boost::multi_index::ordered_unique;
45 using boost::multi_index::tag;
46
hash_value(const QModelIndex & index)47 std::size_t hash_value(const QModelIndex& index) { return qHash(index); }
48
49 namespace {
50
51 struct Mapping {
Mapping__anon3d3432f60111::Mapping52 explicit Mapping(const QModelIndex& _source_index) :
53 source_index(_source_index) {}
54
55 QModelIndex source_index;
56 };
57
58 struct tag_by_source {};
59 struct tag_by_pointer {};
60
61 } // namespace
62
63 class MergedProxyModelPrivate {
64 private:
65 typedef multi_index_container<
66 Mapping*,
67 indexed_by<
68 hashed_unique<tag<tag_by_source>,
69 member<Mapping, QModelIndex, &Mapping::source_index> >,
70 ordered_unique<tag<tag_by_pointer>, identity<Mapping*> > > >
71 MappingContainer;
72
73 public:
74 MappingContainer mappings_;
75 };
76
MergedProxyModel(QObject * parent)77 MergedProxyModel::MergedProxyModel(QObject* parent)
78 : QAbstractProxyModel(parent),
79 resetting_model_(nullptr),
80 p_(new MergedProxyModelPrivate) {}
81
~MergedProxyModel()82 MergedProxyModel::~MergedProxyModel() { DeleteAllMappings(); }
83
DeleteAllMappings()84 void MergedProxyModel::DeleteAllMappings() {
85 const auto& begin = p_->mappings_.get<tag_by_pointer>().begin();
86 const auto& end = p_->mappings_.get<tag_by_pointer>().end();
87 qDeleteAll(begin, end);
88 }
89
AddSubModel(const QModelIndex & source_parent,QAbstractItemModel * submodel)90 void MergedProxyModel::AddSubModel(const QModelIndex& source_parent,
91 QAbstractItemModel* submodel) {
92 connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset()));
93 connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this,
94 SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
95 connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this,
96 SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
97 connect(submodel, SIGNAL(rowsInserted(QModelIndex, int, int)), this,
98 SLOT(RowsInserted(QModelIndex, int, int)));
99 connect(submodel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
100 SLOT(RowsRemoved(QModelIndex, int, int)));
101 connect(submodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this,
102 SLOT(DataChanged(QModelIndex, QModelIndex)));
103
104 QModelIndex proxy_parent = mapFromSource(source_parent);
105 const int rows = submodel->rowCount();
106
107 if (rows) beginInsertRows(proxy_parent, 0, rows - 1);
108
109 merge_points_.insert(submodel, source_parent);
110
111 if (rows) endInsertRows();
112 }
113
RemoveSubModel(const QModelIndex & source_parent)114 void MergedProxyModel::RemoveSubModel(const QModelIndex& source_parent) {
115 // Find the submodel that the parent corresponded to
116 QAbstractItemModel* submodel = merge_points_.key(source_parent);
117 merge_points_.remove(submodel);
118
119 // The submodel might have been deleted already so we must be careful not
120 // to dereference it.
121
122 // Remove all the children of the item that got deleted
123 QModelIndex proxy_parent = mapFromSource(source_parent);
124
125 // We can't know how many children it had, since we can't dereference it
126 resetting_model_ = submodel;
127 beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
128 endRemoveRows();
129 resetting_model_ = nullptr;
130
131 // Delete all the mappings that reference the submodel
132 auto it = p_->mappings_.get<tag_by_pointer>().begin();
133 auto end = p_->mappings_.get<tag_by_pointer>().end();
134 while (it != end) {
135 if ((*it)->source_index.model() == submodel) {
136 delete *it;
137 it = p_->mappings_.get<tag_by_pointer>().erase(it);
138 } else {
139 ++it;
140 }
141 }
142 }
143
setSourceModel(QAbstractItemModel * source_model)144 void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) {
145 if (sourceModel()) {
146 disconnect(sourceModel(), SIGNAL(modelReset()), this,
147 SLOT(SourceModelReset()));
148 disconnect(sourceModel(),
149 SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this,
150 SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
151 disconnect(sourceModel(),
152 SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this,
153 SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
154 disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this,
155 SLOT(RowsInserted(QModelIndex, int, int)));
156 disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
157 SLOT(RowsRemoved(QModelIndex, int, int)));
158 disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)),
159 this, SLOT(DataChanged(QModelIndex, QModelIndex)));
160 disconnect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this,
161 SLOT(LayoutAboutToBeChanged()));
162 disconnect(sourceModel(), SIGNAL(layoutChanged()), this,
163 SLOT(LayoutChanged()));
164 }
165
166 QAbstractProxyModel::setSourceModel(source_model);
167
168 connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
169 connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)),
170 this, SLOT(RowsAboutToBeInserted(QModelIndex, int, int)));
171 connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)),
172 this, SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
173 connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this,
174 SLOT(RowsInserted(QModelIndex, int, int)));
175 connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this,
176 SLOT(RowsRemoved(QModelIndex, int, int)));
177 connect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this,
178 SLOT(DataChanged(QModelIndex, QModelIndex)));
179 connect(sourceModel(), SIGNAL(layoutAboutToBeChanged()), this,
180 SLOT(LayoutAboutToBeChanged()));
181 connect(sourceModel(), SIGNAL(layoutChanged()), this, SLOT(LayoutChanged()));
182 }
183
SourceModelReset()184 void MergedProxyModel::SourceModelReset() {
185 // Delete all mappings
186 DeleteAllMappings();
187
188 // Reset the proxy
189 beginResetModel();
190
191 // Clear the containers
192 p_->mappings_.clear();
193 merge_points_.clear();
194
195 endResetModel();
196 }
197
SubModelReset()198 void MergedProxyModel::SubModelReset() {
199 QAbstractItemModel* submodel = static_cast<QAbstractItemModel*>(sender());
200
201 // TODO(David Sansome): When we require Qt 4.6, use beginResetModel() and
202 // endResetModel() in LibraryModel and catch those here - that will let
203 // us do away with this std::numeric_limits<int>::max() hack.
204
205 // Remove all the children of the item that got deleted
206 QModelIndex source_parent = merge_points_.value(submodel);
207 QModelIndex proxy_parent = mapFromSource(source_parent);
208
209 // We can't know how many children it had, since it's already disappeared...
210 resetting_model_ = submodel;
211 beginRemoveRows(proxy_parent, 0, std::numeric_limits<int>::max() - 1);
212 endRemoveRows();
213 resetting_model_ = nullptr;
214
215 // Delete all the mappings that reference the submodel
216 auto it = p_->mappings_.get<tag_by_pointer>().begin();
217 auto end = p_->mappings_.get<tag_by_pointer>().end();
218 while (it != end) {
219 if ((*it)->source_index.model() == submodel) {
220 delete *it;
221 it = p_->mappings_.get<tag_by_pointer>().erase(it);
222 } else {
223 ++it;
224 }
225 }
226
227 // "Insert" items from the newly reset submodel
228 int count = submodel->rowCount();
229 if (count) {
230 beginInsertRows(proxy_parent, 0, count - 1);
231 endInsertRows();
232 }
233
234 emit SubModelReset(proxy_parent, submodel);
235 }
236
GetActualSourceParent(const QModelIndex & source_parent,QAbstractItemModel * model) const237 QModelIndex MergedProxyModel::GetActualSourceParent(
238 const QModelIndex& source_parent, QAbstractItemModel* model) const {
239 if (!source_parent.isValid() && model != sourceModel())
240 return merge_points_.value(model);
241 return source_parent;
242 }
243
RowsAboutToBeInserted(const QModelIndex & source_parent,int start,int end)244 void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent,
245 int start, int end) {
246 beginInsertRows(
247 mapFromSource(GetActualSourceParent(
248 source_parent, static_cast<QAbstractItemModel*>(sender()))),
249 start, end);
250 }
251
RowsInserted(const QModelIndex &,int,int)252 void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) {
253 endInsertRows();
254 }
255
RowsAboutToBeRemoved(const QModelIndex & source_parent,int start,int end)256 void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent,
257 int start, int end) {
258 beginRemoveRows(
259 mapFromSource(GetActualSourceParent(
260 source_parent, static_cast<QAbstractItemModel*>(sender()))),
261 start, end);
262 }
263
RowsRemoved(const QModelIndex &,int,int)264 void MergedProxyModel::RowsRemoved(const QModelIndex&, int, int) {
265 endRemoveRows();
266 }
267
mapToSource(const QModelIndex & proxy_index) const268 QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index)
269 const {
270 if (!proxy_index.isValid()) return QModelIndex();
271
272 Mapping* mapping = static_cast<Mapping*>(proxy_index.internalPointer());
273 if (p_->mappings_.get<tag_by_pointer>().find(mapping) ==
274 p_->mappings_.get<tag_by_pointer>().end())
275 return QModelIndex();
276 if (mapping->source_index.model() == resetting_model_) return QModelIndex();
277
278 return mapping->source_index;
279 }
280
mapFromSource(const QModelIndex & source_index) const281 QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index)
282 const {
283 if (!source_index.isValid()) return QModelIndex();
284 if (source_index.model() == resetting_model_) return QModelIndex();
285
286 // Add a mapping if we don't have one already
287 const auto& it = p_->mappings_.get<tag_by_source>().find(source_index);
288 Mapping* mapping;
289 if (it != p_->mappings_.get<tag_by_source>().end()) {
290 mapping = *it;
291 } else {
292 mapping = new Mapping(source_index);
293 const_cast<MergedProxyModel*>(this)->p_->mappings_.insert(mapping);
294 }
295
296 return createIndex(source_index.row(), source_index.column(), mapping);
297 }
298
index(int row,int column,const QModelIndex & parent) const299 QModelIndex MergedProxyModel::index(int row, int column,
300 const QModelIndex& parent) const {
301 QModelIndex source_index;
302
303 if (!parent.isValid()) {
304 source_index = sourceModel()->index(row, column, QModelIndex());
305 } else {
306 QModelIndex source_parent = mapToSource(parent);
307 const QAbstractItemModel* child_model = merge_points_.key(source_parent);
308
309 if (child_model)
310 source_index = child_model->index(row, column, QModelIndex());
311 else
312 source_index = source_parent.model()->index(row, column, source_parent);
313 }
314
315 return mapFromSource(source_index);
316 }
317
parent(const QModelIndex & child) const318 QModelIndex MergedProxyModel::parent(const QModelIndex& child) const {
319 QModelIndex source_child = mapToSource(child);
320 if (source_child.model() == sourceModel())
321 return mapFromSource(source_child.parent());
322
323 if (!IsKnownModel(source_child.model())) return QModelIndex();
324
325 if (!source_child.parent().isValid())
326 return mapFromSource(merge_points_.value(GetModel(source_child)));
327 return mapFromSource(source_child.parent());
328 }
329
rowCount(const QModelIndex & parent) const330 int MergedProxyModel::rowCount(const QModelIndex& parent) const {
331 if (!parent.isValid()) return sourceModel()->rowCount(QModelIndex());
332
333 QModelIndex source_parent = mapToSource(parent);
334 if (!IsKnownModel(source_parent.model())) return 0;
335
336 const QAbstractItemModel* child_model = merge_points_.key(source_parent);
337 if (child_model) {
338 // Query the source model but disregard what it says, so it gets a chance
339 // to lazy load
340 source_parent.model()->rowCount(source_parent);
341
342 return child_model->rowCount(QModelIndex());
343 }
344
345 return source_parent.model()->rowCount(source_parent);
346 }
347
columnCount(const QModelIndex & parent) const348 int MergedProxyModel::columnCount(const QModelIndex& parent) const {
349 if (!parent.isValid()) return sourceModel()->columnCount(QModelIndex());
350
351 QModelIndex source_parent = mapToSource(parent);
352 if (!IsKnownModel(source_parent.model())) return 0;
353
354 const QAbstractItemModel* child_model = merge_points_.key(source_parent);
355 if (child_model) return child_model->columnCount(QModelIndex());
356 return source_parent.model()->columnCount(source_parent);
357 }
358
hasChildren(const QModelIndex & parent) const359 bool MergedProxyModel::hasChildren(const QModelIndex& parent) const {
360 if (!parent.isValid()) return sourceModel()->hasChildren(QModelIndex());
361
362 QModelIndex source_parent = mapToSource(parent);
363 if (!IsKnownModel(source_parent.model())) return false;
364
365 const QAbstractItemModel* child_model = merge_points_.key(source_parent);
366
367 if (child_model)
368 return child_model->hasChildren(QModelIndex()) ||
369 source_parent.model()->hasChildren(source_parent);
370 return source_parent.model()->hasChildren(source_parent);
371 }
372
data(const QModelIndex & proxyIndex,int role) const373 QVariant MergedProxyModel::data(const QModelIndex& proxyIndex, int role) const {
374 QModelIndex source_index = mapToSource(proxyIndex);
375 if (!IsKnownModel(source_index.model())) return QVariant();
376
377 return source_index.model()->data(source_index, role);
378 }
379
itemData(const QModelIndex & proxy_index) const380 QMap<int, QVariant> MergedProxyModel::itemData(const QModelIndex& proxy_index)
381 const {
382 QModelIndex source_index = mapToSource(proxy_index);
383
384 if (!source_index.isValid()) return sourceModel()->itemData(QModelIndex());
385 return source_index.model()->itemData(source_index);
386 }
387
flags(const QModelIndex & index) const388 Qt::ItemFlags MergedProxyModel::flags(const QModelIndex& index) const {
389 QModelIndex source_index = mapToSource(index);
390
391 if (!source_index.isValid()) return sourceModel()->flags(QModelIndex());
392 return source_index.model()->flags(source_index);
393 }
394
setData(const QModelIndex & index,const QVariant & value,int role)395 bool MergedProxyModel::setData(const QModelIndex& index, const QVariant& value,
396 int role) {
397 QModelIndex source_index = mapToSource(index);
398
399 if (!source_index.isValid())
400 return sourceModel()->setData(index, value, role);
401 return GetModel(index)->setData(index, value, role);
402 }
403
mimeTypes() const404 QStringList MergedProxyModel::mimeTypes() const {
405 QStringList ret;
406 ret << sourceModel()->mimeTypes();
407
408 for (const QAbstractItemModel* model : merge_points_.keys()) {
409 ret << model->mimeTypes();
410 }
411
412 return ret;
413 }
414
mimeData(const QModelIndexList & indexes) const415 QMimeData* MergedProxyModel::mimeData(const QModelIndexList& indexes) const {
416 if (indexes.isEmpty()) return 0;
417
418 // Only ask the first index's model
419 const QAbstractItemModel* model = mapToSource(indexes[0]).model();
420 if (!model) {
421 return 0;
422 }
423
424 // Only ask about the indexes that are actually in that model
425 QModelIndexList indexes_in_model;
426
427 for (const QModelIndex& proxy_index : indexes) {
428 QModelIndex source_index = mapToSource(proxy_index);
429 if (source_index.model() != model) continue;
430 indexes_in_model << source_index;
431 }
432
433 return model->mimeData(indexes_in_model);
434 }
435
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)436 bool MergedProxyModel::dropMimeData(const QMimeData* data,
437 Qt::DropAction action, int row, int column,
438 const QModelIndex& parent) {
439 if (!parent.isValid()) {
440 return false;
441 }
442
443 return sourceModel()->dropMimeData(data, action, row, column, parent);
444 }
445
FindSourceParent(const QModelIndex & proxy_index) const446 QModelIndex MergedProxyModel::FindSourceParent(const QModelIndex& proxy_index)
447 const {
448 if (!proxy_index.isValid()) return QModelIndex();
449
450 QModelIndex source_index = mapToSource(proxy_index);
451 if (source_index.model() == sourceModel()) return source_index;
452 return merge_points_.value(GetModel(source_index));
453 }
454
canFetchMore(const QModelIndex & parent) const455 bool MergedProxyModel::canFetchMore(const QModelIndex& parent) const {
456 QModelIndex source_index = mapToSource(parent);
457
458 if (!source_index.isValid())
459 return sourceModel()->canFetchMore(QModelIndex());
460 return source_index.model()->canFetchMore(source_index);
461 }
462
fetchMore(const QModelIndex & parent)463 void MergedProxyModel::fetchMore(const QModelIndex& parent) {
464 QModelIndex source_index = mapToSource(parent);
465
466 if (!source_index.isValid())
467 sourceModel()->fetchMore(QModelIndex());
468 else
469 GetModel(source_index)->fetchMore(source_index);
470 }
471
GetModel(const QModelIndex & source_index) const472 QAbstractItemModel* MergedProxyModel::GetModel(const QModelIndex& source_index)
473 const {
474 // This is essentially const_cast<QAbstractItemModel*>(source_index.model()),
475 // but without the const_cast
476 const QAbstractItemModel* const_model = source_index.model();
477 if (const_model == sourceModel()) return sourceModel();
478 for (QAbstractItemModel* submodel : merge_points_.keys()) {
479 if (submodel == const_model) return submodel;
480 }
481 return nullptr;
482 }
483
DataChanged(const QModelIndex & top_left,const QModelIndex & bottom_right)484 void MergedProxyModel::DataChanged(const QModelIndex& top_left,
485 const QModelIndex& bottom_right) {
486 emit dataChanged(mapFromSource(top_left), mapFromSource(bottom_right));
487 }
488
LayoutAboutToBeChanged()489 void MergedProxyModel::LayoutAboutToBeChanged() {
490 old_merge_points_.clear();
491 for (QAbstractItemModel* key : merge_points_.keys()) {
492 old_merge_points_[key] = merge_points_.value(key);
493 }
494 }
495
LayoutChanged()496 void MergedProxyModel::LayoutChanged() {
497 for (QAbstractItemModel* key : merge_points_.keys()) {
498 if (!old_merge_points_.contains(key)) continue;
499
500 const int old_row = old_merge_points_[key].row();
501 const int new_row = merge_points_[key].row();
502
503 if (old_row != new_row) {
504 beginResetModel();
505 endResetModel();
506 return;
507 }
508 }
509 }
510
IsKnownModel(const QAbstractItemModel * model) const511 bool MergedProxyModel::IsKnownModel(const QAbstractItemModel* model) const {
512 return model == this || model == sourceModel() ||
513 merge_points_.contains(const_cast<QAbstractItemModel*>(model));
514 }
515
mapFromSource(const QModelIndexList & source_indexes) const516 QModelIndexList MergedProxyModel::mapFromSource(
517 const QModelIndexList& source_indexes) const {
518 QModelIndexList ret;
519 for (const QModelIndex& index : source_indexes) {
520 ret << mapFromSource(index);
521 }
522 return ret;
523 }
524
mapToSource(const QModelIndexList & proxy_indexes) const525 QModelIndexList MergedProxyModel::mapToSource(
526 const QModelIndexList& proxy_indexes) const {
527 QModelIndexList ret;
528 for (const QModelIndex& index : proxy_indexes) {
529 ret << mapToSource(index);
530 }
531 return ret;
532 }
533