1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2012-21-06
7  * Description : Qt filter model for import items
8  *
9  * Copyright (C) 2012 by Islam Wazery <wazery at ubuntu dot com>
10  *
11  * This program is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU General
13  * Public License as published by the Free Software Foundation;
14  * either version 2, or (at your option)
15  * any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * ============================================================ */
23 
24 #include "importfiltermodel.h"
25 
26 // Local includes
27 
28 #include "camiteminfo.h"
29 #include "filter.h"
30 #include "importimagemodel.h"
31 
32 namespace Digikam
33 {
34 
ImportSortFilterModel(QObject * const parent)35 ImportSortFilterModel::ImportSortFilterModel(QObject* const parent)
36     : DCategorizedSortFilterProxyModel(parent),
37       m_chainedModel                  (nullptr)
38 {
39 }
40 
~ImportSortFilterModel()41 ImportSortFilterModel::~ImportSortFilterModel()
42 {
43 }
44 
setSourceImportModel(ImportItemModel * const sourceModel)45 void ImportSortFilterModel::setSourceImportModel(ImportItemModel* const sourceModel)
46 {
47     if (m_chainedModel)
48     {
49         m_chainedModel->setSourceImportModel(sourceModel);
50     }
51     else
52     {
53         setDirectSourceImportModel(sourceModel);
54     }
55 }
56 
sourceImportModel() const57 ImportItemModel* ImportSortFilterModel::sourceImportModel() const
58 {
59     if (m_chainedModel)
60     {
61         return m_chainedModel->sourceImportModel();
62     }
63 
64     return static_cast<ImportItemModel*>(sourceModel());
65 }
66 
setSourceFilterModel(ImportSortFilterModel * const sourceModel)67 void ImportSortFilterModel::setSourceFilterModel(ImportSortFilterModel* const sourceModel)
68 {
69     if (sourceModel)
70     {
71         ImportItemModel* const model = sourceImportModel();
72 
73         if (model)
74         {
75             sourceModel->setSourceImportModel(model);
76         }
77     }
78 
79     m_chainedModel = sourceModel;
80     setSourceModel(sourceModel);
81 }
82 
sourceFilterModel() const83 ImportSortFilterModel* ImportSortFilterModel::sourceFilterModel() const
84 {
85     return m_chainedModel;
86 }
87 
mapToSourceImportModel(const QModelIndex & proxyIndex) const88 QModelIndex ImportSortFilterModel::mapToSourceImportModel(const QModelIndex& proxyIndex) const
89 {
90     if (!proxyIndex.isValid())
91     {
92         return QModelIndex();
93     }
94 
95     if (m_chainedModel)
96     {
97         return m_chainedModel->mapToSourceImportModel(mapToSource(proxyIndex));
98     }
99 
100     return mapToSource(proxyIndex);
101 }
102 
mapFromSourceImportModel(const QModelIndex & importModelIndex) const103 QModelIndex ImportSortFilterModel::mapFromSourceImportModel(const QModelIndex& importModelIndex) const
104 {
105     if (!importModelIndex.isValid())
106     {
107         return QModelIndex();
108     }
109 
110     if (m_chainedModel)
111     {
112         return mapFromSource(m_chainedModel->mapFromSourceImportModel(importModelIndex));
113     }
114 
115     return mapFromSource(importModelIndex);
116 }
117 
mapFromDirectSourceToSourceImportModel(const QModelIndex & sourceModelIndex) const118 QModelIndex ImportSortFilterModel::mapFromDirectSourceToSourceImportModel(const QModelIndex& sourceModelIndex) const
119 {
120     if (!sourceModelIndex.isValid())
121     {
122         return QModelIndex();
123     }
124 
125     if (m_chainedModel)
126     {
127         return m_chainedModel->mapToSourceImportModel(sourceModelIndex);
128     }
129 
130     return sourceModelIndex;
131 }
132 
mapListToSource(const QList<QModelIndex> & indexes) const133 QList<QModelIndex> ImportSortFilterModel::mapListToSource(const QList<QModelIndex>& indexes) const
134 {
135     QList<QModelIndex> sourceIndexes;
136 
137     foreach (const QModelIndex& index, indexes)
138     {
139         sourceIndexes << mapToSourceImportModel(index);
140     }
141 
142     return sourceIndexes;
143 }
144 
mapListFromSource(const QList<QModelIndex> & sourceIndexes) const145 QList<QModelIndex> ImportSortFilterModel::mapListFromSource(const QList<QModelIndex>& sourceIndexes) const
146 {
147     QList<QModelIndex> indexes;
148 
149     foreach (const QModelIndex& index, sourceIndexes)
150     {
151         indexes << mapFromSourceImportModel(index);
152     }
153 
154     return indexes;
155 }
156 
camItemInfo(const QModelIndex & index) const157 CamItemInfo ImportSortFilterModel::camItemInfo(const QModelIndex& index) const
158 {
159     return sourceImportModel()->camItemInfo(mapToSourceImportModel(index));
160 }
161 
camItemId(const QModelIndex & index) const162 qlonglong ImportSortFilterModel::camItemId(const QModelIndex& index) const
163 {
164     return sourceImportModel()->camItemId(mapToSourceImportModel(index));
165 }
166 
camItemInfos(const QList<QModelIndex> & indexes) const167 QList<CamItemInfo> ImportSortFilterModel::camItemInfos(const QList<QModelIndex>& indexes) const
168 {
169     QList<CamItemInfo> infos;
170 
171     foreach (const QModelIndex& index, indexes)
172     {
173         infos << camItemInfo(index);
174     }
175 
176     return infos;
177 }
178 
camItemIds(const QList<QModelIndex> & indexes) const179 QList<qlonglong> ImportSortFilterModel::camItemIds(const QList<QModelIndex>& indexes) const
180 {
181     QList<qlonglong> ids;
182 
183     foreach (const QModelIndex& index, indexes)
184     {
185         ids << camItemId(index);
186     }
187 
188     return ids;
189 }
190 
indexForPath(const QString & filePath) const191 QModelIndex ImportSortFilterModel::indexForPath(const QString& filePath) const
192 {
193     QUrl fileUrl = QUrl::fromLocalFile(filePath);
194 
195     return mapFromSourceImportModel(sourceImportModel()->indexForUrl(fileUrl));
196 }
197 
indexForCamItemInfo(const CamItemInfo & info) const198 QModelIndex ImportSortFilterModel::indexForCamItemInfo(const CamItemInfo& info) const
199 {
200     return mapFromSourceImportModel(sourceImportModel()->indexForCamItemInfo(info));
201 }
202 
indexForCamItemId(qlonglong id) const203 QModelIndex ImportSortFilterModel::indexForCamItemId(qlonglong id) const
204 {
205     return mapFromSourceImportModel(sourceImportModel()->indexForCamItemId(id));
206 }
207 
camItemInfosSorted() const208 QList<CamItemInfo> ImportSortFilterModel::camItemInfosSorted() const
209 {
210     QList<CamItemInfo> infos;
211     const int          size = rowCount();
212 
213     for (int i = 0 ; i < size ; ++i)
214     {
215         infos << camItemInfo(index(i, 0));
216     }
217 
218     return infos;
219 }
220 
importFilterModel() const221 ImportFilterModel* ImportSortFilterModel::importFilterModel() const
222 {
223     if (m_chainedModel)
224     {
225         return m_chainedModel->importFilterModel();
226     }
227 
228     return nullptr;
229 }
230 
setSourceModel(QAbstractItemModel * sourceModel)231 void ImportSortFilterModel::setSourceModel(QAbstractItemModel* sourceModel)
232 {
233     DCategorizedSortFilterProxyModel::setSourceModel(sourceModel);
234 }
235 
setDirectSourceImportModel(ImportItemModel * const sourceModel)236 void ImportSortFilterModel::setDirectSourceImportModel(ImportItemModel* const sourceModel)
237 {
238     setSourceModel(sourceModel);
239 }
240 
241 //--- ImportFilterModel methods ---------------------------------
242 
243 class Q_DECL_HIDDEN ImportFilterModel::ImportFilterModelPrivate : public QObject
244 {
245     Q_OBJECT
246 
247 public:
248 
ImportFilterModelPrivate()249     ImportFilterModelPrivate()
250       : q              (nullptr),
251         importItemModel(nullptr),
252         filter         (nullptr)
253     {
254     }
255 
256     void init(ImportFilterModel* const _q);
257 
258 Q_SIGNALS:
259 
260     void reAddCamItemInfos(const QList<CamItemInfo>&);
261     void reAddingFinished();
262 
263 public:
264 
265     ImportFilterModel*  q;
266     ImportItemModel*    importItemModel;
267     CamItemSortSettings sorter;
268     Filter*             filter;
269 
270 private:
271 
272     // Disable
273     ImportFilterModelPrivate(QObject*);
274 };
275 
init(ImportFilterModel * const _q)276 void ImportFilterModel::ImportFilterModelPrivate::init(ImportFilterModel* const _q)
277 {
278     q = _q;
279 }
280 
ImportFilterModel(QObject * const parent)281 ImportFilterModel::ImportFilterModel(QObject* const parent)
282     : ImportSortFilterModel(parent),
283       d_ptr(new ImportFilterModelPrivate)
284 {
285     d_ptr->init(this);
286 }
287 
~ImportFilterModel()288 ImportFilterModel::~ImportFilterModel()
289 {
290     Q_D(ImportFilterModel);
291     delete d;
292 }
293 
data(const QModelIndex & index,int role) const294 QVariant ImportFilterModel::data(const QModelIndex& index, int role) const
295 {
296     Q_D(const ImportFilterModel);
297 
298     if (!index.isValid())
299     {
300         return QVariant();
301     }
302 
303     switch (role)
304     {
305         case DCategorizedSortFilterProxyModel::CategoryDisplayRole:
306             return categoryIdentifier(d->importItemModel->camItemInfoRef(mapToSource(index)));
307 
308         case CategorizationModeRole:
309             return d->sorter.categorizationMode;
310 
311         case SortOrderRole:
312             return d->sorter.sortRole;
313 
314         case CategoryFormatRole:
315             return d->importItemModel->camItemInfoRef(mapToSource(index)).mime;
316 
317         case CategoryDateRole:
318             return d->importItemModel->camItemInfoRef(mapToSource(index)).ctime;
319 
320         case ImportFilterModelPointerRole:
321             return QVariant::fromValue(const_cast<ImportFilterModel*>(this));
322     }
323 
324     return DCategorizedSortFilterProxyModel::data(index, role);
325 }
326 
importFilterModel() const327 ImportFilterModel* ImportFilterModel::importFilterModel() const
328 {
329     return const_cast<ImportFilterModel*>(this);
330 }
331 
332 // --- Sorting and Categorization ----------------------------------------------
333 
setCamItemSortSettings(const CamItemSortSettings & sorter)334 void ImportFilterModel::setCamItemSortSettings(const CamItemSortSettings& sorter)
335 {
336     Q_D(ImportFilterModel);
337     d->sorter = sorter;
338     setCategorizedModel(d->sorter.categorizationMode != CamItemSortSettings::NoCategories);
339     invalidate();
340 }
341 
setCategorizationMode(CamItemSortSettings::CategorizationMode mode)342 void ImportFilterModel::setCategorizationMode(CamItemSortSettings::CategorizationMode mode)
343 {
344     Q_D(ImportFilterModel);
345     d->sorter.setCategorizationMode(mode);
346     setCamItemSortSettings(d->sorter);
347 }
348 
setSortRole(CamItemSortSettings::SortRole role)349 void ImportFilterModel::setSortRole(CamItemSortSettings::SortRole role)
350 {
351     Q_D(ImportFilterModel);
352     d->sorter.setSortRole(role);
353     setCamItemSortSettings(d->sorter);
354 }
355 
setSortOrder(CamItemSortSettings::SortOrder order)356 void ImportFilterModel::setSortOrder(CamItemSortSettings::SortOrder order)
357 {
358     Q_D(ImportFilterModel);
359     d->sorter.setSortOrder(order);
360     setCamItemSortSettings(d->sorter);
361 }
362 
setStringTypeNatural(bool natural)363 void ImportFilterModel::setStringTypeNatural(bool natural)
364 {
365     Q_D(ImportFilterModel);
366     d->sorter.setStringTypeNatural(natural);
367     setCamItemSortSettings(d->sorter);
368 }
369 
setFilter(Digikam::Filter * filter)370 void ImportFilterModel::setFilter(Digikam::Filter* filter)
371 {
372     Q_D(ImportFilterModel);
373     d->filter = filter;
374     invalidateFilter();
375 }
376 
setCameraThumbsController(CameraThumbsCtrl * const thumbsCtrl)377 void ImportFilterModel::setCameraThumbsController(CameraThumbsCtrl* const thumbsCtrl)
378 {
379     Q_D(ImportFilterModel);
380     d->importItemModel->setCameraThumbsController(thumbsCtrl);
381 }
382 
setSendCamItemInfoSignals(bool sendSignals)383 void ImportFilterModel::setSendCamItemInfoSignals(bool sendSignals)
384 {
385     if (sendSignals)
386     {
387         connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
388                 this, SLOT(slotRowsInserted(QModelIndex,int,int)));
389 
390         connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
391                 this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
392     }
393     else
394     {
395         disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
396                    this, SLOT(slotRowsInserted(QModelIndex,int,int)));
397 
398         disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
399                    this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
400     }
401 }
402 
slotRowsInserted(const QModelIndex &,int start,int end)403 void ImportFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end)
404 {
405     QList<CamItemInfo> infos;
406 
407     for (int i = start ; i < end ; ++i)
408     {
409         infos << camItemInfo(index(i, 0));
410     }
411 
412     emit camItemInfosAdded(infos);
413 }
414 
slotRowsAboutToBeRemoved(const QModelIndex &,int start,int end)415 void ImportFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end)
416 {
417     QList<CamItemInfo> infos;
418 
419     for (int i = start ; i < end ; ++i)
420     {
421         infos << camItemInfo(index(i, 0));
422     }
423 
424     emit camItemInfosAboutToBeRemoved(infos);
425 }
426 
setDirectSourceImportModel(ImportItemModel * const sourceModel)427 void ImportFilterModel::setDirectSourceImportModel(ImportItemModel* const sourceModel)
428 {
429     Q_D(ImportFilterModel);
430 
431     if (d->importItemModel)
432     {
433 /*
434         disconnect(d->importItemModel, SIGNAL(modelReset()),
435                    this, SLOT(slotModelReset()));
436 
437         // TODO: slotModelReset(); will be added when implementing filtering options
438 */
439         disconnect(d->importItemModel, SIGNAL(processAdded(QList<CamItemInfo>)),
440                    this, SLOT(slotProcessAdded(QList<CamItemInfo>)));
441     }
442 
443     // TODO do we need to delete the old one?
444 
445     d->importItemModel = sourceModel;
446 
447     if (d->importItemModel)
448     {
449 /*
450         connect(d, SIGNAL(reAddCamItemInfos(QList<CamItemInfo>)),
451                 d->importItemModel, SLOT(reAddCamItemInfos(QList<CamItemInfo>)));
452 
453         connect(d, SIGNAL(reAddingFinished()),
454                 d->importItemModel, SLOT(reAddingFinished()));
455 
456         connect(d->importItemModel, SIGNAL(modelReset()),
457                 this, SLOT(slotModelReset()));
458 */
459         connect(d->importItemModel, SIGNAL(processAdded(QList<CamItemInfo>)),
460                 this, SLOT(slotProcessAdded(QList<CamItemInfo>)));
461     }
462 
463     setSourceModel(d->importItemModel);
464 }
465 
slotProcessAdded(const QList<CamItemInfo> &)466 void ImportFilterModel::slotProcessAdded(const QList<CamItemInfo>&)
467 {
468     invalidate();
469 }
470 
compareCategories(const QModelIndex & left,const QModelIndex & right) const471 int ImportFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const
472 {
473     Q_D(const ImportFilterModel);
474 
475     if (!d->sorter.isCategorized())
476     {
477         return 0;
478     }
479 
480     if (!left.isValid() || !right.isValid())
481     {
482         return -1;
483     }
484 
485     return compareInfosCategories(d->importItemModel->camItemInfoRef(left), d->importItemModel->camItemInfoRef(right));
486 }
487 
subSortLessThan(const QModelIndex & left,const QModelIndex & right) const488 bool ImportFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const
489 {
490     Q_D(const ImportFilterModel);
491 
492     if (!left.isValid() || !right.isValid())
493     {
494         return true;
495     }
496 
497     if (left == right)
498     {
499         return false;
500     }
501 
502     const CamItemInfo& leftInfo  = d->importItemModel->camItemInfoRef(left);
503     const CamItemInfo& rightInfo = d->importItemModel->camItemInfoRef(right);
504 
505     if (leftInfo == rightInfo)
506     {
507         return d->sorter.lessThan(left.data(ImportItemModel::ExtraDataRole), right.data(ImportItemModel::ExtraDataRole));
508     }
509 
510     return infosLessThan(leftInfo, rightInfo);
511 }
512 
compareInfosCategories(const CamItemInfo & left,const CamItemInfo & right) const513 int ImportFilterModel::compareInfosCategories(const CamItemInfo& left, const CamItemInfo& right) const
514 {
515     Q_D(const ImportFilterModel);
516 
517     return d->sorter.compareCategories(left, right);
518 }
519 
infosLessThan(const CamItemInfo & left,const CamItemInfo & right) const520 bool ImportFilterModel::infosLessThan(const CamItemInfo& left, const CamItemInfo& right) const
521 {
522     Q_D(const ImportFilterModel);
523 
524     return d->sorter.lessThan(left, right);
525 }
526 
categoryIdentifier(const CamItemInfo & info) const527 QString ImportFilterModel::categoryIdentifier(const CamItemInfo& info) const
528 {
529     Q_D(const ImportFilterModel);
530 
531     switch (d->sorter.categorizationMode)
532     {
533         case CamItemSortSettings::NoCategories:
534             return QString();
535 
536         case CamItemSortSettings::CategoryByFolder:
537             return info.folder;
538 
539         case CamItemSortSettings::CategoryByFormat:
540             return info.mime;
541 
542         case CamItemSortSettings::CategoryByDate:
543             return info.ctime.date().toString(Qt::ISODate);
544 
545         default:
546             return QString();
547     }
548 }
549 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const550 bool ImportFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
551 {
552     Q_D(const ImportFilterModel);
553 
554     if (!d->filter)
555     {
556         return true;
557     }
558 
559     QModelIndex idx         = sourceModel()->index(source_row, 0, source_parent);
560     const CamItemInfo &info = d->importItemModel->camItemInfo(idx);
561 
562     if (d->filter->matchesCurrentFilter(info))
563     {
564         return true;
565     }
566 
567     return false;
568 }
569 
570 // -------------------------------------------------------------------------------------------------------
571 
NoDuplicatesImportFilterModel(QObject * const parent)572 NoDuplicatesImportFilterModel::NoDuplicatesImportFilterModel(QObject* const parent)
573     : ImportSortFilterModel(parent)
574 {
575 }
576 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const577 bool NoDuplicatesImportFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
578 {
579     QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
580 
581     if (index.data(ImportItemModel::ExtraDataDuplicateCount).toInt() <= 1)
582     {
583         return true;
584     }
585 
586     QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent);
587 
588     if (!previousIndex.isValid())
589     {
590         return true;
591     }
592 
593     if (sourceImportModel()->camItemId(mapFromDirectSourceToSourceImportModel(index)) ==
594         sourceImportModel()->camItemId(mapFromDirectSourceToSourceImportModel(previousIndex)))
595     {
596         return false;
597     }
598 
599     return true;
600 }
601 
602 } // namespace Digikam
603 
604 #include "importfiltermodel.moc"
605