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