1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-03-05
7  * Description : Qt item model for database entries
8  *
9  * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2011-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C)      2010 by Andi Clemens <andi dot clemens at gmail dot com>
12  * Copyright (C)      2011 by Michael G. Hansen <mike at mghansen dot de>
13  * Copyright (C)      2014 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
14  *
15  * This program is free software; you can redistribute it
16  * and/or modify it under the terms of the GNU General
17  * Public License as published by the Free Software Foundation;
18  * either version 2, or (at your option)
19  * any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * ============================================================ */
27 
28 #include "itemfiltermodel_p.h"
29 
30 // KDE includes
31 
32 #include <klocalizedstring.h>
33 
34 // Local includes
35 
36 #include "itemfiltermodelthreads.h"
37 #include "digikam_debug.h"
38 #include "coredbaccess.h"
39 #include "coredbchangesets.h"
40 #include "coredbwatch.h"
41 #include "iteminfolist.h"
42 #include "itemmodel.h"
43 #include "facetagsiface.h"
44 #include "facetags.h"
45 
46 namespace Digikam
47 {
48 
ImageSortFilterModel(QObject * const parent)49 ImageSortFilterModel::ImageSortFilterModel(QObject* const parent)
50     : DCategorizedSortFilterProxyModel(parent),
51       m_chainedModel                  (nullptr)
52 {
53 }
54 
setSourceItemModel(ItemModel * const source)55 void ImageSortFilterModel::setSourceItemModel(ItemModel* const source)
56 {
57     if (m_chainedModel)
58     {
59         m_chainedModel->setSourceItemModel(source);
60     }
61     else
62     {
63         setDirectSourceItemModel(source);
64     }
65 }
66 
setSourceFilterModel(ImageSortFilterModel * const source)67 void ImageSortFilterModel::setSourceFilterModel(ImageSortFilterModel* const source)
68 {
69     if (source)
70     {
71         ItemModel* const model = sourceItemModel();
72 
73         if (model)
74         {
75             source->setSourceItemModel(model);
76         }
77     }
78 
79     m_chainedModel = source;
80     setSourceModel(source);
81 }
82 
setDirectSourceItemModel(ItemModel * const model)83 void ImageSortFilterModel::setDirectSourceItemModel(ItemModel* const model)
84 {
85     setSourceModel(model);
86 }
87 
setSourceModel(QAbstractItemModel * const model)88 void ImageSortFilterModel::setSourceModel(QAbstractItemModel* const model)
89 {
90     // made it protected, only setSourceItemModel is public
91 
92     DCategorizedSortFilterProxyModel::setSourceModel(model);
93 }
94 
sourceItemModel() const95 ItemModel* ImageSortFilterModel::sourceItemModel() const
96 {
97     if (m_chainedModel)
98     {
99         return m_chainedModel->sourceItemModel();
100     }
101 
102     return static_cast<ItemModel*>(sourceModel());
103 }
104 
sourceFilterModel() const105 ImageSortFilterModel* ImageSortFilterModel::sourceFilterModel() const
106 {
107     return m_chainedModel;
108 }
109 
imageFilterModel() const110 ItemFilterModel* ImageSortFilterModel::imageFilterModel() const
111 {
112     // reimplemented in ItemFilterModel
113 
114     if (m_chainedModel)
115     {
116         return m_chainedModel->imageFilterModel();
117     }
118 
119     return nullptr;
120 }
121 
mapToSourceItemModel(const QModelIndex & index) const122 QModelIndex ImageSortFilterModel::mapToSourceItemModel(const QModelIndex& index) const
123 {
124     if (!index.isValid())
125     {
126         return QModelIndex();
127     }
128 
129     if (m_chainedModel)
130     {
131         return m_chainedModel->mapToSourceItemModel(mapToSource(index));
132     }
133 
134     return mapToSource(index);
135 }
136 
mapFromSourceItemModel(const QModelIndex & albummodel_index) const137 QModelIndex ImageSortFilterModel::mapFromSourceItemModel(const QModelIndex& albummodel_index) const
138 {
139     if (!albummodel_index.isValid())
140     {
141         return QModelIndex();
142     }
143 
144     if (m_chainedModel)
145     {
146         return mapFromSource(m_chainedModel->mapFromSourceItemModel(albummodel_index));
147     }
148 
149     return mapFromSource(albummodel_index);
150 }
151 
152 
mapFromDirectSourceToSourceItemModel(const QModelIndex & sourceModel_index) const153 QModelIndex ImageSortFilterModel::mapFromDirectSourceToSourceItemModel(const QModelIndex& sourceModel_index) const
154 {
155     if (!sourceModel_index.isValid())
156     {
157         return QModelIndex();
158     }
159 
160     if (m_chainedModel)
161     {
162         return m_chainedModel->mapToSourceItemModel(sourceModel_index);
163     }
164 
165     return sourceModel_index;
166 }
167 
168 // -------------- Convenience mappers -------------------------------------------------------------------
169 
mapListToSource(const QList<QModelIndex> & indexes) const170 QList<QModelIndex> ImageSortFilterModel::mapListToSource(const QList<QModelIndex>& indexes) const
171 {
172     QList<QModelIndex> sourceIndexes;
173 
174     foreach (const QModelIndex& index, indexes)
175     {
176         sourceIndexes << mapToSourceItemModel(index);
177     }
178 
179     return sourceIndexes;
180 }
181 
mapListFromSource(const QList<QModelIndex> & sourceIndexes) const182 QList<QModelIndex> ImageSortFilterModel::mapListFromSource(const QList<QModelIndex>& sourceIndexes) const
183 {
184     QList<QModelIndex> indexes;
185 
186     foreach (const QModelIndex& index, sourceIndexes)
187     {
188         indexes << mapFromSourceItemModel(index);
189     }
190 
191     return indexes;
192 }
193 
imageInfo(const QModelIndex & index) const194 ItemInfo ImageSortFilterModel::imageInfo(const QModelIndex& index) const
195 {
196     return sourceItemModel()->imageInfo(mapToSourceItemModel(index));
197 }
198 
imageId(const QModelIndex & index) const199 qlonglong ImageSortFilterModel::imageId(const QModelIndex& index) const
200 {
201     return sourceItemModel()->imageId(mapToSourceItemModel(index));
202 }
203 
imageInfos(const QList<QModelIndex> & indexes) const204 QList<ItemInfo> ImageSortFilterModel::imageInfos(const QList<QModelIndex>& indexes) const
205 {
206     QList<ItemInfo> infos;
207     ItemModel* const model = sourceItemModel();
208 
209     foreach (const QModelIndex& index, indexes)
210     {
211         infos << model->imageInfo(mapToSourceItemModel(index));
212     }
213 
214     return infos;
215 }
216 
imageIds(const QList<QModelIndex> & indexes) const217 QList<qlonglong> ImageSortFilterModel::imageIds(const QList<QModelIndex>& indexes) const
218 {
219     QList<qlonglong> ids;
220     ItemModel* const model = sourceItemModel();
221 
222     foreach (const QModelIndex& index, indexes)
223     {
224         ids << model->imageId(mapToSourceItemModel(index));
225     }
226 
227     return ids;
228 }
229 
indexForPath(const QString & filePath) const230 QModelIndex ImageSortFilterModel::indexForPath(const QString& filePath) const
231 {
232     return mapFromSourceItemModel(sourceItemModel()->indexForPath(filePath));
233 }
234 
indexForItemInfo(const ItemInfo & info) const235 QModelIndex ImageSortFilterModel::indexForItemInfo(const ItemInfo& info) const
236 {
237     return mapFromSourceItemModel(sourceItemModel()->indexForItemInfo(info));
238 }
239 
indexForImageId(qlonglong id) const240 QModelIndex ImageSortFilterModel::indexForImageId(qlonglong id) const
241 {
242     return mapFromSourceItemModel(sourceItemModel()->indexForImageId(id));
243 }
244 
imageInfosSorted() const245 QList<ItemInfo> ImageSortFilterModel::imageInfosSorted() const
246 {
247     QList<ItemInfo>  infos;
248     const int         size = rowCount();
249     ItemModel* const model = sourceItemModel();
250 
251     for (int i = 0 ; i < size ; ++i)
252     {
253         infos << model->imageInfo(mapToSourceItemModel(index(i, 0)));
254     }
255 
256     return infos;
257 }
258 
259 // --------------------------------------------------------------------------------------------
260 
ItemFilterModel(QObject * const parent)261 ItemFilterModel::ItemFilterModel(QObject* const parent)
262     : ImageSortFilterModel(parent),
263       d_ptr               (new ItemFilterModelPrivate)
264 {
265     d_ptr->init(this);
266 }
267 
ItemFilterModel(ItemFilterModelPrivate & dd,QObject * const parent)268 ItemFilterModel::ItemFilterModel(ItemFilterModelPrivate& dd, QObject* const parent)
269     : ImageSortFilterModel(parent),
270       d_ptr               (&dd)
271 {
272     d_ptr->init(this);
273 }
274 
~ItemFilterModel()275 ItemFilterModel::~ItemFilterModel()
276 {
277     Q_D(ItemFilterModel);
278     delete d;
279 }
280 
setDirectSourceItemModel(ItemModel * const sourceModel)281 void ItemFilterModel::setDirectSourceItemModel(ItemModel* const sourceModel)
282 {
283     Q_D(ItemFilterModel);
284 
285     if (d->imageModel)
286     {
287         d->imageModel->unsetPreprocessor(d);
288 
289         disconnect(d->imageModel, SIGNAL(modelReset()),
290                    this, SLOT(slotModelReset()));
291 
292         slotModelReset();
293     }
294 
295     d->imageModel = sourceModel;
296 
297     if (d->imageModel)
298     {
299         d->imageModel->setPreprocessor(d);
300 
301         connect(d->imageModel, SIGNAL(preprocess(QList<ItemInfo>,QList<QVariant>)),
302                 d, SLOT(preprocessInfos(QList<ItemInfo>,QList<QVariant>)));
303 
304         connect(d->imageModel, SIGNAL(processAdded(QList<ItemInfo>,QList<QVariant>)),
305                 d, SLOT(processAddedInfos(QList<ItemInfo>,QList<QVariant>)));
306 
307         connect(d, SIGNAL(reAddItemInfos(QList<ItemInfo>,QList<QVariant>)),
308                 d->imageModel, SLOT(reAddItemInfos(QList<ItemInfo>,QList<QVariant>)));
309 
310         connect(d, SIGNAL(reAddingFinished()),
311                 d->imageModel, SLOT(reAddingFinished()));
312 
313         connect(d->imageModel, SIGNAL(modelReset()),
314                 this, SLOT(slotModelReset()));
315 
316         connect(d->imageModel, SIGNAL(imageChange(ImageChangeset,QItemSelection)),
317                 this, SLOT(slotImageChange(ImageChangeset)));
318 
319         connect(d->imageModel, SIGNAL(imageTagChange(ImageTagChangeset,QItemSelection)),
320                 this, SLOT(slotImageTagChange(ImageTagChangeset)));
321     }
322 
323     setSourceModel(d->imageModel);
324 }
325 
data(const QModelIndex & index,int role) const326 QVariant ItemFilterModel::data(const QModelIndex& index, int role) const
327 {
328     Q_D(const ItemFilterModel);
329 
330     if (!index.isValid())
331     {
332         return QVariant();
333     }
334 
335     /**
336      * Keeping track of the Face (if any) associated with this Model Index
337      * is important to allow categorization by Face.
338      */
339     QVariant extraData = d->imageModel->data(mapToSource(index), ItemModel::ExtraDataRole);
340 
341     FaceTagsIface face;
342 
343     if (!extraData.isNull())
344     {
345         face = FaceTagsIface::fromVariant(extraData);
346     }
347 
348     switch (role)
349     {
350         // Attention: This breaks should there ever be another filter model between this and the ItemModel
351 
352         case DCategorizedSortFilterProxyModel::CategoryDisplayRole:
353         {
354             return categoryIdentifier(d->imageModel->imageInfoRef(mapToSource(index)), face);
355         }
356 
357         case CategorizationModeRole:
358         {
359             return d->sorter.categorizationMode;
360         }
361 
362         case SortOrderRole:
363         {
364             return d->sorter.sortRole;
365         }
366 /*
367         case CategoryCountRole:
368         {
369             return categoryCount(d->imageModel->imageInfoRef(mapToSource(index)));
370         }
371 */
372         case CategoryAlbumIdRole:
373         {
374             return d->imageModel->imageInfoRef(mapToSource(index)).albumId();
375         }
376 
377         case CategoryFormatRole:
378         {
379             return d->imageModel->imageInfoRef(mapToSource(index)).format();
380         }
381 
382         case CategoryDateRole:
383         {
384             return d->imageModel->imageInfoRef(mapToSource(index)).dateTime();
385         }
386 
387         case CategoryFaceRole:
388         {
389             if (extraData.isNull())
390             {
391                 return i18nc("@item: filter model", "No face");
392             }
393 
394             if      ((face.type() == FaceTagsIface::UnknownName) ||
395                      (face.type() == FaceTagsIface::IgnoredName))
396             {
397                 return FaceTags::faceNameForTag(face.tagId());
398             }
399             else if (face.type() == FaceTagsIface::ConfirmedName)
400             {
401                 QString name = FaceTags::faceNameForTag(face.tagId());
402                 name        += QString::fromUtf8(" [%1]").arg(i18nc("@item: filter model", "Confirmed"));
403 
404                 return name;
405             }
406 
407             return d->imageModel->imageInfoRef(mapToSource(index)).getSuggestedNames().value(face.region().toXml());
408         }
409 
410         case GroupIsOpenRole:
411         {
412             return (d->groupFilter.isAllOpen() ||
413                     d->groupFilter.isOpen(d->imageModel->imageInfoRef(mapToSource(index)).id()));
414         }
415 
416         case ItemFilterModelPointerRole:
417         {
418             return QVariant::fromValue(const_cast<ItemFilterModel*>(this));
419         }
420     }
421 
422     return DCategorizedSortFilterProxyModel::data(index, role);
423 }
424 
imageFilterModel() const425 ItemFilterModel* ItemFilterModel::imageFilterModel() const
426 {
427     return const_cast<ItemFilterModel*>(this);
428 }
429 
suggestedWatchFlags() const430 DatabaseFields::Set ItemFilterModel::suggestedWatchFlags() const
431 {
432     DatabaseFields::Set watchFlags;
433     watchFlags |= DatabaseFields::Album  | DatabaseFields::Name         | DatabaseFields::FileSize    |
434                   DatabaseFields::ModificationDate;
435     watchFlags |= DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::Orientation |
436                   DatabaseFields::Width  | DatabaseFields::Height;
437     watchFlags |= DatabaseFields::Comment;
438     watchFlags |= DatabaseFields::ImageRelations;
439 
440     return watchFlags;
441 }
442 
443 // -------------- Filter settings --------------
444 
setDayFilter(const QList<QDateTime> & days)445 void ItemFilterModel::setDayFilter(const QList<QDateTime>& days)
446 {
447     Q_D(ItemFilterModel);
448     d->filter.setDayFilter(days);
449     setItemFilterSettings(d->filter);
450 }
451 
setTagFilter(const QList<int> & includedTags,const QList<int> & excludedTags,ItemFilterSettings::MatchingCondition matchingCond,bool showUnTagged,const QList<int> & clTagIds,const QList<int> & plTagIds)452 void ItemFilterModel::setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
453                                    ItemFilterSettings::MatchingCondition matchingCond,
454                                    bool showUnTagged, const QList<int>& clTagIds, const QList<int>& plTagIds)
455 {
456     Q_D(ItemFilterModel);
457     d->filter.setTagFilter(includedTags, excludedTags, matchingCond, showUnTagged, clTagIds, plTagIds);
458     setItemFilterSettings(d->filter);
459 }
460 
setRatingFilter(int rating,ItemFilterSettings::RatingCondition ratingCond,bool isUnratedExcluded)461 void ItemFilterModel::setRatingFilter(int rating, ItemFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded)
462 {
463     Q_D(ItemFilterModel);
464     d->filter.setRatingFilter(rating, ratingCond, isUnratedExcluded);
465     setItemFilterSettings(d->filter);
466 }
467 
setUrlWhitelist(const QList<QUrl> & urlList,const QString & id)468 void ItemFilterModel::setUrlWhitelist(const QList<QUrl>& urlList, const QString& id)
469 {
470     Q_D(ItemFilterModel);
471     d->filter.setUrlWhitelist(urlList, id);
472     setItemFilterSettings(d->filter);
473 }
474 
setIdWhitelist(const QList<qlonglong> & idList,const QString & id)475 void ItemFilterModel::setIdWhitelist(const QList<qlonglong>& idList, const QString& id)
476 {
477     Q_D(ItemFilterModel);
478     d->filter.setIdWhitelist(idList, id);
479     setItemFilterSettings(d->filter);
480 }
481 
setMimeTypeFilter(int mimeTypeFilter)482 void ItemFilterModel::setMimeTypeFilter(int mimeTypeFilter)
483 {
484     Q_D(ItemFilterModel);
485     d->filter.setMimeTypeFilter(mimeTypeFilter);
486     setItemFilterSettings(d->filter);
487 }
488 
setGeolocationFilter(const ItemFilterSettings::GeolocationCondition & condition)489 void ItemFilterModel::setGeolocationFilter(const ItemFilterSettings::GeolocationCondition& condition)
490 {
491     Q_D(ItemFilterModel);
492     d->filter.setGeolocationFilter(condition);
493     setItemFilterSettings(d->filter);
494 }
495 
setTextFilter(const SearchTextFilterSettings & settings)496 void ItemFilterModel::setTextFilter(const SearchTextFilterSettings& settings)
497 {
498     Q_D(ItemFilterModel);
499     d->filter.setTextFilter(settings);
500     setItemFilterSettings(d->filter);
501 }
502 
setItemFilterSettings(const ItemFilterSettings & settings)503 void ItemFilterModel::setItemFilterSettings(const ItemFilterSettings& settings)
504 {
505     Q_D(ItemFilterModel);
506 
507     {
508         QMutexLocker lock(&d->mutex);
509         d->version++;
510         d->filter              = settings;
511         d->filterCopy          = settings;
512         d->versionFilterCopy   = d->versionFilter;
513         d->groupFilterCopy     = d->groupFilter;
514 
515         d->needPrepareComments = settings.isFilteringByText();
516         d->needPrepareTags     = settings.isFilteringByTags();
517         d->needPrepareGroups   = true;
518         d->needPrepare         = d->needPrepareComments || d->needPrepareTags || d->needPrepareGroups;
519 
520         d->hasOneMatch         = false;
521         d->hasOneMatchForText  = false;
522     }
523 
524     d->filterResults.clear();
525 /*
526     d->categoryCountHashInt.clear();
527     d->categoryCountHashString.clear();
528 */
529     if (d->imageModel)
530     {
531         d->infosToProcess(d->imageModel->imageInfos());
532     }
533 
534     emit filterSettingsChanged(settings);
535 }
536 
setVersionManagerSettings(const VersionManagerSettings & settings)537 void ItemFilterModel::setVersionManagerSettings(const VersionManagerSettings& settings)
538 {
539     Q_D(ItemFilterModel);
540     d->versionFilter.setVersionManagerSettings(settings);
541     setVersionItemFilterSettings(d->versionFilter);
542 }
543 
setExceptionList(const QList<qlonglong> & idList,const QString & id)544 void ItemFilterModel::setExceptionList(const QList<qlonglong>& idList, const QString& id)
545 {
546     Q_D(ItemFilterModel);
547     d->versionFilter.setExceptionList(idList, id);
548     setVersionItemFilterSettings(d->versionFilter);
549 }
550 
setVersionItemFilterSettings(const VersionItemFilterSettings & settings)551 void ItemFilterModel::setVersionItemFilterSettings(const VersionItemFilterSettings& settings)
552 {
553     Q_D(ItemFilterModel);
554     d->versionFilter = settings;
555     slotUpdateFilter();
556 }
557 
isGroupOpen(qlonglong group) const558 bool ItemFilterModel::isGroupOpen(qlonglong group) const
559 {
560     Q_D(const ItemFilterModel);
561 
562     return d->groupFilter.isOpen(group);
563 }
564 
isAllGroupsOpen() const565 bool ItemFilterModel::isAllGroupsOpen() const
566 {
567     Q_D(const ItemFilterModel);
568 
569     return d->groupFilter.isAllOpen();
570 }
571 
setGroupOpen(qlonglong group,bool open)572 void ItemFilterModel::setGroupOpen(qlonglong group, bool open)
573 {
574     Q_D(ItemFilterModel);
575     d->groupFilter.setOpen(group, open);
576     setGroupItemFilterSettings(d->groupFilter);
577 }
578 
toggleGroupOpen(qlonglong group)579 void ItemFilterModel::toggleGroupOpen(qlonglong group)
580 {
581     setGroupOpen(group, !isGroupOpen(group));
582 }
583 
setAllGroupsOpen(bool open)584 void ItemFilterModel::setAllGroupsOpen(bool open)
585 {
586     Q_D(ItemFilterModel);
587     d->groupFilter.setAllOpen(open);
588     setGroupItemFilterSettings(d->groupFilter);
589 }
590 
setGroupItemFilterSettings(const GroupItemFilterSettings & settings)591 void ItemFilterModel::setGroupItemFilterSettings(const GroupItemFilterSettings& settings)
592 {
593     Q_D(ItemFilterModel);
594     d->groupFilter = settings;
595     slotUpdateFilter();
596 }
597 
slotUpdateFilter()598 void ItemFilterModel::slotUpdateFilter()
599 {
600     Q_D(ItemFilterModel);
601     setItemFilterSettings(d->filter);
602 }
603 
imageFilterSettings() const604 ItemFilterSettings ItemFilterModel::imageFilterSettings() const
605 {
606     Q_D(const ItemFilterModel);
607 
608     return d->filter;
609 }
610 
imageSortSettings() const611 ItemSortSettings ItemFilterModel::imageSortSettings() const
612 {
613     Q_D(const ItemFilterModel);
614 
615     return d->sorter;
616 }
617 
versionItemFilterSettings() const618 VersionItemFilterSettings ItemFilterModel::versionItemFilterSettings() const
619 {
620     Q_D(const ItemFilterModel);
621 
622     return d->versionFilter;
623 }
624 
groupItemFilterSettings() const625 GroupItemFilterSettings ItemFilterModel::groupItemFilterSettings() const
626 {
627     Q_D(const ItemFilterModel);
628 
629     return d->groupFilter;
630 }
631 
slotModelReset()632 void ItemFilterModel::slotModelReset()
633 {
634     Q_D(ItemFilterModel);
635 
636     {
637         QMutexLocker lock(&d->mutex);
638 
639         // discard all packages on the way that are marked as send out for re-add
640 
641         d->lastDiscardVersion = d->version;
642         d->sentOutForReAdd    = 0;
643 
644         // discard all packages on the way
645 
646         d->version++;
647         d->sentOut            = 0;
648 
649         d->hasOneMatch        = false;
650         d->hasOneMatchForText = false;
651     }
652 
653     d->filterResults.clear();
654 }
655 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const656 bool ItemFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
657 {
658     Q_D(const ItemFilterModel);
659 
660     if (source_parent.isValid())
661     {
662         return false;
663     }
664 
665     qlonglong id                              = d->imageModel->imageId(source_row);
666     QHash<qlonglong, bool>::const_iterator it = d->filterResults.constFind(id);
667 
668     if (it != d->filterResults.constEnd())
669     {
670         return it.value();
671     }
672 
673     // usually done in thread and cache, unless source model changed
674 
675     ItemInfo info = d->imageModel->imageInfo(source_row);
676     bool match    = d->filter.matches(info);
677     match         = match ? d->versionFilter.matches(info) : false;
678 
679     return (match ? d->groupFilter.matches(info) : false);
680 }
681 
setSendItemInfoSignals(bool sendSignals)682 void ItemFilterModel::setSendItemInfoSignals(bool sendSignals)
683 {
684     if (sendSignals)
685     {
686         connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
687                 this, SLOT(slotRowsInserted(QModelIndex,int,int)));
688 
689         connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
690                 this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
691     }
692     else
693     {
694         disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
695                    this, SLOT(slotRowsInserted(QModelIndex,int,int)));
696 
697         disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
698                    this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
699     }
700 }
701 
slotRowsInserted(const QModelIndex &,int start,int end)702 void ItemFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end)
703 {
704     QList<ItemInfo> infos;
705 
706     for (int i = start ; i <= end ; ++i)
707     {
708         infos << imageInfo(index(i, 0));
709     }
710 
711     emit imageInfosAdded(infos);
712 }
713 
slotRowsAboutToBeRemoved(const QModelIndex &,int start,int end)714 void ItemFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end)
715 {
716     QList<ItemInfo> infos;
717 
718     for (int i = start ; i <= end; ++i)
719     {
720         infos << imageInfo(index(i, 0));
721     }
722 
723     emit imageInfosAboutToBeRemoved(infos);
724 }
725 
726 // -------------- Threaded preparation & filtering --------------
727 
addPrepareHook(ItemFilterModelPrepareHook * const hook)728 void ItemFilterModel::addPrepareHook(ItemFilterModelPrepareHook* const hook)
729 {
730     Q_D(ItemFilterModel);
731     QMutexLocker lock(&d->mutex);
732     d->prepareHooks << hook;
733 }
734 
removePrepareHook(ItemFilterModelPrepareHook * const hook)735 void ItemFilterModel::removePrepareHook(ItemFilterModelPrepareHook* const hook)
736 {
737     Q_D(ItemFilterModel);
738     QMutexLocker lock(&d->mutex);
739     d->prepareHooks.removeAll(hook);
740 }
741 
process(ItemFilterModelTodoPackage package)742 void ItemFilterModelPreparer::process(ItemFilterModelTodoPackage package)
743 {
744     if (!checkVersion(package))
745     {
746         emit discarded(package);
747         return;
748     }
749 
750     // get thread-local copy
751 
752     bool needPrepareTags, needPrepareComments, needPrepareGroups;
753     QList<ItemFilterModelPrepareHook*> prepareHooks;
754 
755     {
756         QMutexLocker lock(&d->mutex);
757         needPrepareTags     = d->needPrepareTags;
758         needPrepareComments = d->needPrepareComments;
759         needPrepareGroups   = d->needPrepareGroups;
760         prepareHooks        = d->prepareHooks;
761     }
762 
763     // TODO: Make efficient!!
764 
765     if (needPrepareComments)
766     {
767         foreach (const ItemInfo& info, package.infos)
768         {
769             info.comment();
770         }
771     }
772 
773     if (!checkVersion(package))
774     {
775         emit discarded(package);
776         return;
777     }
778 
779     // The downside of QVector: At some point, we may need a QList for an API.
780     // Nonetheless, QList and ItemInfo is fast. We could as well
781     // reimplement ItemInfoList to ItemInfoVector (internally with templates?)
782 
783     ItemInfoList infoList;
784 
785     if (needPrepareTags || needPrepareGroups)
786     {
787         infoList = ItemInfoList(package.infos.toList());
788     }
789 
790     if (needPrepareTags)
791     {
792         infoList.loadTagIds();
793     }
794 
795     if (needPrepareGroups)
796     {
797         infoList.loadGroupImageIds();
798     }
799 
800     foreach (ItemFilterModelPrepareHook* const hook, prepareHooks)
801     {
802         hook->prepare(package.infos);
803     }
804 
805     emit processed(package);
806 }
807 
process(ItemFilterModelTodoPackage package)808 void ItemFilterModelFilterer::process(ItemFilterModelTodoPackage package)
809 {
810     if (!checkVersion(package))
811     {
812         emit discarded(package);
813         return;
814     }
815 
816     // get thread-local copy
817 
818     ItemFilterSettings        localFilter;
819     VersionItemFilterSettings localVersionFilter;
820     GroupItemFilterSettings   localGroupFilter;
821     bool                      hasOneMatch;
822     bool                      hasOneMatchForText;
823 
824     {
825         QMutexLocker lock(&d->mutex);
826         localFilter        = d->filterCopy;
827         localVersionFilter = d->versionFilterCopy;
828         localGroupFilter   = d->groupFilterCopy;
829         hasOneMatch        = d->hasOneMatch;
830         hasOneMatchForText = d->hasOneMatchForText;
831     }
832 
833     // Actual filtering. The variants to spare checking hasOneMatch over and over again.
834 
835     if      (hasOneMatch && hasOneMatchForText)
836     {
837         foreach (const ItemInfo& info, package.infos)
838         {
839             package.filterResults[info.id()] = (localFilter.matches(info)        &&
840                                                 localVersionFilter.matches(info) &&
841                                                 localGroupFilter.matches(info));
842         }
843     }
844     else if (hasOneMatch)
845     {
846         bool matchForText;
847 
848         foreach (const ItemInfo& info, package.infos)
849         {
850             package.filterResults[info.id()] = (localFilter.matches(info, &matchForText) &&
851                                                 localVersionFilter.matches(info)         &&
852                                                 localGroupFilter.matches(info));
853 
854             if (matchForText)
855             {
856                 hasOneMatchForText = true;
857             }
858         }
859     }
860     else
861     {
862         bool result, matchForText;
863 
864         foreach (const ItemInfo& info, package.infos)
865         {
866             result                           = (localFilter.matches(info, &matchForText) &&
867                                                 localVersionFilter.matches(info)         &&
868                                                 localGroupFilter.matches(info));
869 
870             package.filterResults[info.id()] = result;
871 
872             if (result)
873             {
874                 hasOneMatch = true;
875             }
876 
877             if (matchForText)
878             {
879                 hasOneMatchForText = true;
880             }
881         }
882     }
883 
884     if (checkVersion(package))
885     {
886         QMutexLocker lock(&d->mutex);
887         d->hasOneMatch        = hasOneMatch;
888         d->hasOneMatchForText = hasOneMatchForText;
889     }
890 
891     emit processed(package);
892 }
893 
894 // -------------- Sorting and Categorization -------------------------------------------------------
895 
setItemSortSettings(const ItemSortSettings & sorter)896 void ItemFilterModel::setItemSortSettings(const ItemSortSettings& sorter)
897 {
898     Q_D(ItemFilterModel);
899     d->sorter = sorter;
900     setCategorizedModel(d->sorter.categorizationMode != ItemSortSettings::NoCategories);
901     invalidate();
902 }
903 
setCategorizationMode(ItemSortSettings::CategorizationMode mode)904 void ItemFilterModel::setCategorizationMode(ItemSortSettings::CategorizationMode mode)
905 {
906     Q_D(ItemFilterModel);
907     d->sorter.setCategorizationMode(mode);
908     setItemSortSettings(d->sorter);
909 }
910 
setCategorizationSortOrder(ItemSortSettings::SortOrder order)911 void ItemFilterModel::setCategorizationSortOrder(ItemSortSettings::SortOrder order)
912 {
913     Q_D(ItemFilterModel);
914     d->sorter.setCategorizationSortOrder(order);
915     setItemSortSettings(d->sorter);
916 }
917 
setSortRole(ItemSortSettings::SortRole role)918 void ItemFilterModel::setSortRole(ItemSortSettings::SortRole role)
919 {
920     Q_D(ItemFilterModel);
921     d->sorter.setSortRole(role);
922     setItemSortSettings(d->sorter);
923 }
924 
setSortOrder(ItemSortSettings::SortOrder order)925 void ItemFilterModel::setSortOrder(ItemSortSettings::SortOrder order)
926 {
927     Q_D(ItemFilterModel);
928     d->sorter.setSortOrder(order);
929     setItemSortSettings(d->sorter);
930 }
931 
setStringTypeNatural(bool natural)932 void ItemFilterModel::setStringTypeNatural(bool natural)
933 {
934     Q_D(ItemFilterModel);
935     d->sorter.setStringTypeNatural(natural);
936     setItemSortSettings(d->sorter);
937 }
938 
compareCategories(const QModelIndex & left,const QModelIndex & right) const939 int ItemFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const
940 {
941     // source indexes
942 
943     Q_D(const ItemFilterModel);
944 
945     if (!d->sorter.isCategorized())
946     {
947         return 0;
948     }
949 
950     if (!left.isValid() || !right.isValid())
951     {
952         return -1;
953     }
954 
955     const ItemInfo& leftInfo    = d->imageModel->imageInfoRef(left);
956     const ItemInfo& rightInfo   = d->imageModel->imageInfoRef(right);
957 
958     // Check grouping
959 
960     qlonglong leftGroupImageId  = leftInfo.groupImageId();
961     qlonglong rightGroupImageId = rightInfo.groupImageId();
962 
963     QVariant leftExtraData      = left.data(ItemModel::ExtraDataRole);
964     QVariant rightExtraData     = right.data(ItemModel::ExtraDataRole);
965 
966     FaceTagsIface leftFace;
967     FaceTagsIface rightFace;
968 
969     if (!leftExtraData.isNull())
970     {
971         leftFace = FaceTagsIface::fromVariant(leftExtraData);
972     }
973 
974     if (!rightExtraData.isNull())
975     {
976         rightFace = FaceTagsIface::fromVariant(rightExtraData);
977     }
978 
979     return compareInfosCategories(
980                                   (leftGroupImageId  == -1) ? leftInfo  : ItemInfo(leftGroupImageId),
981                                   (rightGroupImageId == -1) ? rightInfo : ItemInfo(rightGroupImageId),
982                                   leftFace, rightFace
983                                  );
984 }
985 
subSortLessThan(const QModelIndex & left,const QModelIndex & right) const986 bool ItemFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const
987 {
988     // source indexes
989 
990     Q_D(const ItemFilterModel);
991 
992     if (!left.isValid() || !right.isValid())
993     {
994         return true;
995     }
996 
997     if (left == right)
998     {
999         return false;
1000     }
1001 
1002     const ItemInfo& leftInfo    = d->imageModel->imageInfoRef(left);
1003     const ItemInfo& rightInfo   = d->imageModel->imageInfoRef(right);
1004 
1005     if (leftInfo == rightInfo)
1006     {
1007         return d->sorter.lessThan(left.data(ItemModel::ExtraDataRole), right.data(ItemModel::ExtraDataRole));
1008     }
1009 
1010     // Check grouping
1011 
1012     qlonglong leftGroupImageId  = leftInfo.groupImageId();
1013     qlonglong rightGroupImageId = rightInfo.groupImageId();
1014 
1015     // Either no grouping (-1), or same group image, or same image
1016 
1017     if (leftGroupImageId == rightGroupImageId)
1018     {
1019         return infosLessThan(leftInfo, rightInfo);
1020     }
1021 
1022     // We have grouping to handle
1023 
1024     // Is one grouped on the other? Sort behind leader.
1025 
1026     if (leftGroupImageId == rightInfo.id())
1027     {
1028         return false;
1029     }
1030 
1031     if (rightGroupImageId == leftInfo.id())
1032     {
1033         return true;
1034     }
1035 
1036     // Use the group leader for sorting
1037 
1038     return infosLessThan(
1039                          (leftGroupImageId  == -1) ? leftInfo  : ItemInfo(leftGroupImageId),
1040                          (rightGroupImageId == -1) ? rightInfo : ItemInfo(rightGroupImageId)
1041                         );
1042 }
1043 
compareInfosCategories(const ItemInfo & left,const ItemInfo & right) const1044 int ItemFilterModel::compareInfosCategories(const ItemInfo& left, const ItemInfo& right) const
1045 {
1046     // Note: reimplemented in ItemAlbumFilterModel
1047 
1048     Q_D(const ItemFilterModel);
1049 
1050     FaceTagsIface leftFace, rightFace;
1051 
1052     return d->sorter.compareCategories(left, right, leftFace, rightFace);
1053 }
1054 
compareInfosCategories(const ItemInfo & left,const ItemInfo & right,const FaceTagsIface & leftFace,const FaceTagsIface & rightFace) const1055 int ItemFilterModel::compareInfosCategories(const ItemInfo& left, const ItemInfo& right,
1056                                             const FaceTagsIface& leftFace, const FaceTagsIface& rightFace) const
1057 {
1058     // Note: reimplemented in ItemAlbumFilterModel
1059 
1060     Q_D(const ItemFilterModel);
1061 
1062     return d->sorter.compareCategories(left, right, leftFace, rightFace);
1063 }
1064 
1065 // Feel free to optimize. QString::number is 3x slower.
1066 
fastNumberToString(int id)1067 static inline QString fastNumberToString(int id)
1068 {
1069     const int size = sizeof(int) * 2;
1070     int number     = id;
1071     char c[size + 1];
1072     c[size]        = '\0';
1073 
1074     for (int i = 0 ; i < size ; ++i)
1075     {
1076         c[i]     = 'a' + (number & 0xF);
1077         number >>= 4;
1078     }
1079 
1080     return QLatin1String(c);
1081 }
1082 
categoryIdentifier(const ItemInfo & i,const FaceTagsIface & face) const1083 QString ItemFilterModel::categoryIdentifier(const ItemInfo& i, const FaceTagsIface& face) const
1084 {
1085     Q_D(const ItemFilterModel);
1086 
1087     if (!d->sorter.isCategorized())
1088     {
1089         return QString();
1090     }
1091 
1092     qlonglong groupedImageId = i.groupImageId();
1093     ItemInfo info = (groupedImageId == -1) ? i : ItemInfo(groupedImageId);
1094 
1095     switch (d->sorter.categorizationMode)
1096     {
1097         case ItemSortSettings::NoCategories:
1098         {
1099             return QString();
1100         }
1101 
1102         case ItemSortSettings::OneCategory:
1103         {
1104             return QString();
1105         }
1106 
1107         case ItemSortSettings::CategoryByAlbum:
1108         {
1109             return fastNumberToString(info.albumId());
1110         }
1111 
1112         case ItemSortSettings::CategoryByFormat:
1113         {
1114             return info.format();
1115         }
1116 
1117         case ItemSortSettings::CategoryByMonth:
1118         {
1119             return info.dateTime().date().toString(QLatin1String("MMyyyy"));
1120         }
1121 
1122         case ItemSortSettings::CategoryByFaces:
1123         {
1124             // No face in image.
1125 
1126             if (face.isNull())
1127             {
1128                 return QLatin1String("NO_FACE");
1129             }
1130 
1131             if      (face.type() == FaceTagsIface::UnknownName)
1132             {
1133                 return QLatin1String("UNKNOWN_FACE");
1134             }
1135             else if (face.type() == FaceTagsIface::IgnoredName)
1136             {
1137                 return QLatin1String("IGNORED_FACE");
1138             }
1139             else if (face.type() == FaceTagsIface::ConfirmedName)
1140             {
1141                 // Region is Confirmed. Appending TagId,
1142                 // to prevent multiple Confirmed categories.
1143 
1144                 return QLatin1String("CONFIRMED_FACE") +
1145                        fastNumberToString(face.tagId());
1146             }
1147 
1148             // Suggested Name exists for Region.
1149 
1150             const QMap<QString, QString> map = info.getSuggestedNames();
1151 
1152             if (!map.value(face.region().toXml()).isEmpty())
1153             {
1154                 return map.value(face.region().toXml()) +
1155                        fastNumberToString(face.tagId());
1156             }
1157 
1158             return QLatin1String("NO_FACE");
1159         }
1160 
1161         default:
1162         {
1163             return QString();
1164         }
1165     }
1166 }
1167 
infosLessThan(const ItemInfo & left,const ItemInfo & right) const1168 bool ItemFilterModel::infosLessThan(const ItemInfo& left, const ItemInfo& right) const
1169 {
1170     Q_D(const ItemFilterModel);
1171 
1172     return d->sorter.lessThan(left, right);
1173 }
1174 
1175 // -------------- Watching changes -----------------------------------------------------------------
1176 
slotImageTagChange(const ImageTagChangeset & changeset)1177 void ItemFilterModel::slotImageTagChange(const ImageTagChangeset& changeset)
1178 {
1179     Q_D(ItemFilterModel);
1180 
1181     if (!d->imageModel || d->imageModel->isEmpty())
1182     {
1183         return;
1184     }
1185 
1186     // already scheduled to re-filter?
1187 
1188     if (d->updateFilterTimer->isActive())
1189     {
1190         return;
1191     }
1192 
1193     // do we filter at all?
1194 
1195     if (!d->versionFilter.isFilteringByTags() &&
1196         !d->filter.isFilteringByTags()        &&
1197         !d->filter.isFilteringByText())
1198     {
1199         return;
1200     }
1201 
1202     // is one of our images affected?
1203 
1204     foreach (const qlonglong& id, changeset.ids())
1205     {
1206         // if one matching image id is found, trigger a refresh
1207 
1208         if (d->imageModel->hasImage(id))
1209         {
1210             d->updateFilterTimer->start();
1211             return;
1212         }
1213     }
1214 }
1215 
slotImageChange(const ImageChangeset & changeset)1216 void ItemFilterModel::slotImageChange(const ImageChangeset& changeset)
1217 {
1218     Q_D(ItemFilterModel);
1219 
1220     if (!d->imageModel || d->imageModel->isEmpty())
1221     {
1222         return;
1223     }
1224 
1225     // already scheduled to re-filter?
1226 
1227     if (d->updateFilterTimer->isActive())
1228     {
1229         return;
1230     }
1231 
1232     // is one of the values affected that we filter or sort by?
1233 
1234     DatabaseFields::Set set = changeset.changes();
1235     bool sortAffected       = (set & d->sorter.watchFlags());
1236     bool filterAffected     = (set & d->filter.watchFlags()) || (set & d->groupFilter.watchFlags());
1237     bool categoryAffected   = sortAffected && (d->sorter.categorizationMode == ItemSortSettings::CategoryByAlbum);
1238 
1239     if (!sortAffected && !filterAffected)
1240     {
1241         return;
1242     }
1243 
1244     // is one of our images affected?
1245 
1246     bool imageAffected = false;
1247 
1248     foreach (const qlonglong& id, changeset.ids())
1249     {
1250         // if one matching image id is found, trigger a refresh
1251 
1252         if (d->imageModel->hasImage(id))
1253         {
1254             imageAffected = true;
1255             break;
1256         }
1257     }
1258 
1259     if (!imageAffected)
1260     {
1261         return;
1262     }
1263 
1264     if (categoryAffected || filterAffected)
1265     {
1266         d->updateFilterTimer->start();
1267     }
1268     else
1269     {
1270         invalidate();    // just resort, reuse filter results
1271     }
1272 }
1273 
1274 // -------------------------------------------------------------------------------------------------------
1275 
NoDuplicatesItemFilterModel(QObject * const parent)1276 NoDuplicatesItemFilterModel::NoDuplicatesItemFilterModel(QObject* const parent)
1277     : ImageSortFilterModel(parent)
1278 {
1279 }
1280 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const1281 bool NoDuplicatesItemFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
1282 {
1283     QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
1284 
1285     if (index.data(ItemModel::ExtraDataDuplicateCount).toInt() <= 1)
1286     {
1287         return true;
1288     }
1289 
1290     QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent);
1291 
1292     if (!previousIndex.isValid())
1293     {
1294         return true;
1295     }
1296 
1297     if (sourceItemModel()->imageId(mapFromDirectSourceToSourceItemModel(index)) ==
1298         sourceItemModel()->imageId(mapFromDirectSourceToSourceItemModel(previousIndex)))
1299     {
1300         return false;
1301     }
1302 
1303     return true;
1304 }
1305 
1306 /*
1307 void NoDuplicatesItemFilterModel::setSourceModel(QAbstractItemModel* model)
1308 {
1309     if (sourceModel())
1310     {
1311     }
1312 
1313     ImageSortFilterModel::setSourceModel(model);
1314 
1315     if (sourceModel())
1316     {
1317         connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
1318                 this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
1319     }
1320 }
1321 
1322 void NoDuplicatesItemFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end)
1323 {
1324     bool needInvalidate = false;
1325 
1326     for (int i = begin; i<=end; ++i)
1327     {
1328         QModelIndex index = sourceModel()->index(i, 0, parent);
1329 
1330         // filtered out by us?
1331 
1332         if (!mapFromSource(index).isValid())
1333         {
1334             continue;
1335         }
1336 
1337         QModelIndex sourceIndex = mapFromDirectSourceToSourceItemModel(index);
1338         qlonglong id            = sourceItemModel()->imageId(sourceIndex);
1339 
1340         if (sourceItemModel()->numberOfIndexesForImageId(id) > 1)
1341         {
1342             needInvalidate = true;
1343         }
1344     }
1345 }
1346 */
1347 
1348 } // namespace Digikam
1349