1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-03-24
7  * Description : Qt Model for Albums - filter model
8  *
9  * Copyright (C) 2008-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2009      by Johannes Wienke <languitar at semipol dot de>
11  * Copyright (C) 2014-2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "albumfiltermodel.h"
27 
28 // Qt includes
29 
30 #include <QSortFilterProxyModel>
31 #include <QHeaderView>
32 
33 // Local includes
34 
35 #include "digikam_debug.h"
36 #include "albummanager.h"
37 #include "albummodel.h"
38 #include "applicationsettings.h"
39 #include "itemsortcollator.h"
40 #include "facetags.h"
41 
42 namespace Digikam
43 {
44 
AlbumFilterModel(QObject * const parent)45 AlbumFilterModel::AlbumFilterModel(QObject* const parent)
46     : QSortFilterProxyModel(parent),
47       m_filterBehavior     (FullFiltering),
48       m_chainedModel       (nullptr),
49       m_parent             (parent)
50 {
51     setSortRole(AbstractAlbumModel::AlbumSortRole);
52     setSortCaseSensitivity(Qt::CaseInsensitive);
53 
54     // sorting may have changed when the string comparison is different
55 
56     connect(ApplicationSettings::instance(), SIGNAL(setupChanged()),
57             this, SLOT(invalidate()));
58 
59     // dynamicSortFilter does not work well for us: a dataChange may, because of our way of filtering,
60     // also affect parents and children of the changed index, which is not handled by QSortFilterProxyModel.
61 
62     setDynamicSortFilter(false);
63 
64     // Instead, we listen directly to AlbumManager's relevant change signals
65 
66     connect(AlbumManager::instance(), SIGNAL(signalAlbumRenamed(Album*)),
67             this, SLOT(slotAlbumRenamed(Album*)));
68 
69     connect(AlbumManager::instance(), SIGNAL(signalAlbumsUpdated(int)),
70             this, SLOT(slotAlbumsHaveBeenUpdated(int)));
71 }
72 
setFilterBehavior(FilterBehavior behavior)73 void AlbumFilterModel::setFilterBehavior(FilterBehavior behavior)
74 {
75     if (behavior == m_filterBehavior)
76     {
77         return;
78     }
79 
80     m_filterBehavior = behavior;
81     invalidateFilter();
82 }
83 
setSearchTextSettings(const SearchTextSettings & settings)84 void AlbumFilterModel::setSearchTextSettings(const SearchTextSettings& settings)
85 {
86     if (!sourceModel())
87     {
88         return;
89     }
90 
91     // don't use isFiltering here because it may be reimplemented
92 
93     bool wasSearching = settingsFilter(m_settings);
94     bool willSearch   = settingsFilter(settings);
95     emit searchTextSettingsAboutToChange(wasSearching, willSearch);
96 
97     m_settings = settings;
98     invalidateFilter();
99     emit signalFilterChanged();
100 
101     emit searchTextSettingsChanged(wasSearching, willSearch);
102 
103     if (sourceAlbumModel()->albumType() == Album::PHYSICAL)
104     {
105         // find out if this setting has some results or not
106 
107         int validRows = 0;
108 
109         // for every collection we got
110 
111         for (int i = 0 ; i < rowCount(rootAlbumIndex()) ; ++i)
112         {
113             QModelIndex collectionIndex = index(i, 0, rootAlbumIndex());
114 
115             // count the number of rows
116 
117             validRows                  += rowCount(collectionIndex);
118         }
119 
120         bool hasResult = validRows > 0;
121         qCDebug(DIGIKAM_GENERAL_LOG) << "new search text settings:" << settings.text
122                                      << ": hasResult =" << hasResult
123                                      << ", validRows =" << validRows;
124         emit hasSearchResult(hasResult);
125     }
126     else
127     {
128         QModelIndex head = rootAlbumIndex(); // either root, or invalid, thus toplevel
129         emit hasSearchResult(rowCount(head));
130     }
131 }
132 
settingsFilter(const SearchTextSettings & settings) const133 bool AlbumFilterModel::settingsFilter(const SearchTextSettings& settings) const
134 {
135     return !settings.text.isEmpty();
136 }
137 
isFiltering() const138 bool AlbumFilterModel::isFiltering() const
139 {
140     return settingsFilter(m_settings);
141 }
142 
updateFilter()143 void AlbumFilterModel::updateFilter()
144 {
145     if (isFiltering())
146     {
147         invalidateFilter();
148     }
149 }
150 
searchTextSettings() const151 SearchTextSettings AlbumFilterModel::searchTextSettings() const
152 {
153     return m_settings;
154 }
155 
setSourceAlbumModel(AbstractAlbumModel * const source)156 void AlbumFilterModel::setSourceAlbumModel(AbstractAlbumModel* const source)
157 {
158     if (m_chainedModel)
159     {
160         m_chainedModel->setSourceAlbumModel(source);
161     }
162     else
163     {
164         if (source != sourceModel())
165         {
166             setSourceModel(source);
167         }
168     }
169 }
170 
setSourceFilterModel(AlbumFilterModel * const source)171 void AlbumFilterModel::setSourceFilterModel(AlbumFilterModel* const source)
172 {
173     if (source)
174     {
175         AbstractAlbumModel* const model = sourceAlbumModel();
176 
177         if (model)
178         {
179             source->setSourceAlbumModel(model);
180         }
181     }
182 
183     if ((m_chainedModel != source) || (sourceModel() != source))
184     {
185         m_chainedModel = source;
186         setSourceModel(source);
187     }
188 }
189 
setSourceModel(QAbstractItemModel * const model)190 void AlbumFilterModel::setSourceModel(QAbstractItemModel* const model)
191 {
192     // made it protected, only setSourceAlbumModel is public
193 
194     QSortFilterProxyModel::setSourceModel(model);
195 }
196 
sourceAlbumModel() const197 AbstractAlbumModel* AlbumFilterModel::sourceAlbumModel() const
198 {
199     if (m_chainedModel)
200     {
201         return m_chainedModel->sourceAlbumModel();
202     }
203 
204     return static_cast<AbstractAlbumModel*>(sourceModel());
205 }
206 
sourceFilterModel() const207 AlbumFilterModel* AlbumFilterModel::sourceFilterModel() const
208 {
209     return m_chainedModel;
210 }
211 
mapToSourceAlbumModel(const QModelIndex & index) const212 QModelIndex AlbumFilterModel::mapToSourceAlbumModel(const QModelIndex& index) const
213 {
214     if (!index.isValid())
215     {
216         return QModelIndex();
217     }
218 
219     if (m_chainedModel)
220     {
221         return m_chainedModel->mapToSourceAlbumModel(mapToSource(index));
222     }
223 
224     return mapToSource(index);
225 }
226 
mapFromSourceAlbumModel(const QModelIndex & albummodel_index) const227 QModelIndex AlbumFilterModel::mapFromSourceAlbumModel(const QModelIndex& albummodel_index) const
228 {
229     if (!albummodel_index.isValid())
230     {
231         return QModelIndex();
232     }
233 
234     if (m_chainedModel)
235     {
236         return mapFromSource(m_chainedModel->mapFromSourceAlbumModel(albummodel_index));
237     }
238 
239     return mapFromSource(albummodel_index);
240 }
241 
albumForIndex(const QModelIndex & index) const242 Album* AlbumFilterModel::albumForIndex(const QModelIndex& index) const
243 {
244     return AbstractAlbumModel::retrieveAlbum(index);
245 }
246 
indexForAlbum(Album * album) const247 QModelIndex AlbumFilterModel::indexForAlbum(Album* album) const
248 {
249     AbstractAlbumModel* const model = sourceAlbumModel();
250 
251     if (!model)
252     {
253         return QModelIndex();
254     }
255 
256     return mapFromSourceAlbumModel(model->indexForAlbum(album));
257 }
258 
rootAlbumIndex() const259 QModelIndex AlbumFilterModel::rootAlbumIndex() const
260 {
261     AbstractAlbumModel* const model = sourceAlbumModel();
262 
263     if (!model)
264     {
265         return QModelIndex();
266     }
267 
268     return mapFromSourceAlbumModel(model->rootAlbumIndex());
269 }
270 
dataForCurrentSortRole(Album * album) const271 QVariant AlbumFilterModel::dataForCurrentSortRole(Album* album) const
272 {
273     if (album)
274     {
275         if      (album->type() == Album::PHYSICAL)
276         {
277             PAlbum* const a = static_cast<PAlbum*>(album);
278 
279             ApplicationSettings::AlbumSortRole sortRole = ApplicationSettings::instance()->getAlbumSortRole();
280 
281             switch (sortRole)
282             {
283                 case ApplicationSettings::ByFolder:
284                     return a->title();
285 
286                 case ApplicationSettings::ByDate:
287                     return a->date();
288 
289                 default:
290                     return a->category();
291             }
292         }
293         else if (album->type() == Album::TAG)
294         {
295             return static_cast<TAlbum*>(album)->title();
296         }
297         else if (album->type() == Album::DATE)
298         {
299             return static_cast<DAlbum*>(album)->date();
300         }
301         else if (album->type() == Album::SEARCH)
302         {
303             return static_cast<SAlbum*>(album)->title();
304         }
305     }
306 
307     return QVariant();
308 }
309 
matches(Album * album) const310 bool AlbumFilterModel::matches(Album* album) const
311 {
312     // We want to work on the visual representation, so we use model data with AlbumTitleRole,
313     // not a direct Album method.
314     // We use direct source's index, not our index,
315     // because if the item is currently filtered out, we won't have an index for this album.
316 
317     QModelIndex source_index = sourceAlbumModel()->indexForAlbum(album);
318 
319     if (m_chainedModel)
320     {
321         source_index = m_chainedModel->mapFromSourceAlbumModel(source_index);
322     }
323 
324     QString displayTitle = source_index.data(AbstractAlbumModel::AlbumTitleRole).toString();
325 
326     return displayTitle.contains(m_settings.text, m_settings.caseSensitive);
327 }
328 
matchResult(const QModelIndex & index) const329 AlbumFilterModel::MatchResult AlbumFilterModel::matchResult(const QModelIndex& index) const
330 {
331     return matchResult(albumForIndex(index));
332 }
333 
matchResult(Album * album) const334 AlbumFilterModel::MatchResult AlbumFilterModel::matchResult(Album* album) const
335 {
336     if (!album)
337     {
338         return NoMatch;
339     }
340 
341     PAlbum* const palbum = dynamic_cast<PAlbum*>(album);
342 
343     if (album->isRoot() || (palbum && palbum->isAlbumRoot()))
344     {
345         return SpecialMatch;
346     }
347 
348     TAlbum* const talbum = dynamic_cast<TAlbum*>(album);
349 
350     if (talbum && talbum->isInternalTag())
351     {
352         return NoMatch;
353     }
354 
355     if (matches(album))
356     {
357         return DirectMatch;
358     }
359 
360     if (m_filterBehavior == SimpleFiltering)
361     {
362         return NoMatch;
363     }
364 
365     if (m_filterBehavior == FullFiltering)
366     {
367         // check if any of the parents match the search
368 
369         Album* parent         = album->parent();
370         PAlbum* const pparent = palbum ? static_cast<PAlbum*>(parent) : nullptr;
371 
372         while (parent && !(parent->isRoot() || (pparent && pparent->isAlbumRoot())))
373         {
374             if (matches(parent))
375             {
376                 return ParentMatch;
377             }
378 
379             parent = parent->parent();
380         }
381     }
382 
383     // check if any of the children match the search
384 
385     AlbumIterator it(album);
386 
387     while (it.current())
388     {
389         if (matches(*it))
390         {
391             return ChildMatch;
392         }
393 
394         ++it;
395     }
396 
397     return NoMatch;
398 }
399 
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const400 bool AlbumFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
401 {
402     QModelIndex index  = sourceModel()->index(source_row, 0, source_parent);
403     Album* const album = AbstractAlbumModel::retrieveAlbum(index);
404     MatchResult result = matchResult(album);
405 
406     return result;
407 }
408 
lessThan(const QModelIndex & left,const QModelIndex & right) const409 bool AlbumFilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
410 {
411     Album* leftAlbum  = albumForIndex(left);
412     Album* rightAlbum = albumForIndex(right);
413 
414     if (!leftAlbum || !rightAlbum)
415     {
416         return QSortFilterProxyModel::lessThan(left, right);
417     }
418 
419     if ((leftAlbum->type() == Album::TAG) && (rightAlbum->type() == Album::TAG))
420     {
421         if ((leftAlbum->id() == FaceTags::unconfirmedPersonTagId()) != (rightAlbum->id() == FaceTags::unconfirmedPersonTagId()))
422         {
423             // unconfirmed tag album go to the top, regardless of sort role
424 
425             return (sortOrder() == Qt::AscendingOrder) ? (leftAlbum->id() == FaceTags::unconfirmedPersonTagId())
426                                                        : (leftAlbum->id() != FaceTags::unconfirmedPersonTagId());
427         }
428 
429         if ((leftAlbum->id() == FaceTags::unknownPersonTagId()) != (rightAlbum->id() == FaceTags::unknownPersonTagId()))
430         {
431             // unknown tag albums go to the top, regardless of sort role
432 
433             return (sortOrder() == Qt::AscendingOrder) ? (leftAlbum->id() == FaceTags::unknownPersonTagId())
434                                                        : (leftAlbum->id() != FaceTags::unknownPersonTagId());
435         }
436 
437         // Verify this, to prevent auto-creation of Ignored Tag.
438 
439         if (FaceTags::existsIgnoredPerson())
440         {
441             if ((leftAlbum->id() == FaceTags::ignoredPersonTagId()) != (rightAlbum->id() == FaceTags::ignoredPersonTagId()))
442             {
443                 // ignored tag albums go to the top, regardless of sort role
444 
445                 return (sortOrder() == Qt::AscendingOrder) ? (leftAlbum->id() == FaceTags::ignoredPersonTagId())
446                                                            : (leftAlbum->id() != FaceTags::ignoredPersonTagId());
447             }
448         }
449 
450         /**
451          * Implementation to sort Tags that contain
452          * Unconfirmed Faces, according to the Unconfirmed
453          * Face Count.
454          */
455 
456         if (sourceAlbumModel() && sourceAlbumModel()->isFaceTagModel())
457         {
458             QMap<int, int> unconfirmedFaceCount = AlbumManager::instance()->getUnconfirmedFaceCount();
459             int leftFaceCount                   = unconfirmedFaceCount.value(leftAlbum->id(), 0);
460             int rightFaceCount                  = unconfirmedFaceCount.value(rightAlbum->id(), 0);
461 
462             if ((leftFaceCount != 0) || (rightFaceCount != 0))
463             {
464                 return (sortOrder() == Qt::AscendingOrder) ? (leftFaceCount > rightFaceCount)
465                                                            : (leftFaceCount < rightFaceCount);
466             }
467         }
468     }
469 
470     if (leftAlbum->isTrashAlbum() != rightAlbum->isTrashAlbum())
471     {
472         // trash albums go to the bottom, regardless of sort role
473 
474         return (sortOrder() == Qt::AscendingOrder) ? !leftAlbum->isTrashAlbum()
475                                                    : leftAlbum->isTrashAlbum();
476     }
477 
478     QVariant valLeft  = dataForCurrentSortRole(leftAlbum);
479     QVariant valRight = dataForCurrentSortRole(rightAlbum);
480 
481     ApplicationSettings::AlbumSortRole role = ApplicationSettings::instance()->getAlbumSortRole();
482 
483     if (((role == ApplicationSettings::ByDate) || (role == ApplicationSettings::ByCategory)) &&
484         (valLeft == valRight))
485     {
486         return QSortFilterProxyModel::lessThan(left, right);
487     }
488 
489     if ((valLeft.type() == QVariant::String) && (valRight.type() == QVariant::String))
490     {
491         ItemSortCollator* const sorter = ItemSortCollator::instance();
492         bool natural                   = ApplicationSettings::instance()->isStringTypeNatural();
493 
494         return (sorter->albumCompare(valLeft.toString(), valRight.toString(), sortCaseSensitivity(), natural) < 0);
495     }
496     else if ((valLeft.type() == QVariant::Date) && (valRight.type() == QVariant::Date))
497     {
498         return (compareByOrder(valLeft.toDate(), valRight.toDate(), Qt::AscendingOrder) < 0);
499     }
500 
501     return QSortFilterProxyModel::lessThan(left, right);
502 }
503 
slotAlbumRenamed(Album * album)504 void AlbumFilterModel::slotAlbumRenamed(Album* album)
505 {
506     if (album)
507     {
508         slotAlbumsHaveBeenUpdated(album->type());
509     }
510 }
511 
slotAlbumsHaveBeenUpdated(int type)512 void AlbumFilterModel::slotAlbumsHaveBeenUpdated(int type)
513 {
514     if (sourceAlbumModel() && sourceAlbumModel()->albumType() == type)
515     {
516         invalidate();
517     }
518 }
519 
520 // -----------------------------------------------------------------------------
521 
CheckableAlbumFilterModel(QObject * const parent)522 CheckableAlbumFilterModel::CheckableAlbumFilterModel(QObject* const parent) :
523     AlbumFilterModel(parent),
524     m_filterChecked(false),
525     m_filterPartiallyChecked(false)
526 {
527 }
528 
setSourceAlbumModel(AbstractCheckableAlbumModel * const source)529 void CheckableAlbumFilterModel::setSourceAlbumModel(AbstractCheckableAlbumModel* const source)
530 {
531     AlbumFilterModel::setSourceAlbumModel(source);
532 }
533 
setSourceFilterModel(CheckableAlbumFilterModel * const source)534 void CheckableAlbumFilterModel::setSourceFilterModel(CheckableAlbumFilterModel* const source)
535 {
536     AlbumFilterModel::setSourceFilterModel(source);
537 }
538 
sourceAlbumModel() const539 AbstractCheckableAlbumModel* CheckableAlbumFilterModel::sourceAlbumModel() const
540 {
541     return static_cast<AbstractCheckableAlbumModel*>(AlbumFilterModel::sourceAlbumModel());
542 }
543 
setFilterChecked(bool filter)544 void CheckableAlbumFilterModel::setFilterChecked(bool filter)
545 {
546     m_filterChecked = filter;
547     invalidateFilter();
548     emit signalFilterChanged();
549 }
550 
setFilterPartiallyChecked(bool filter)551 void CheckableAlbumFilterModel::setFilterPartiallyChecked(bool filter)
552 {
553     m_filterPartiallyChecked = filter;
554     invalidateFilter();
555     emit signalFilterChanged();
556 }
557 
isFiltering() const558 bool CheckableAlbumFilterModel::isFiltering() const
559 {
560     return (AlbumFilterModel::isFiltering() || m_filterChecked || m_filterPartiallyChecked);
561 }
562 
matches(Album * album) const563 bool CheckableAlbumFilterModel::matches(Album* album) const
564 {
565     bool accepted = AlbumFilterModel::matches(album);
566 
567     if (!m_filterChecked && !m_filterPartiallyChecked)
568     {
569         return accepted;
570     }
571 
572     Qt::CheckState state = sourceAlbumModel()->checkState(album);
573 
574     bool stateAccepted = false;
575 
576     if (m_filterPartiallyChecked)
577     {
578         stateAccepted |= state == Qt::PartiallyChecked;
579     }
580 
581     if (m_filterChecked)
582     {
583         stateAccepted |= state == Qt::Checked;
584     }
585 
586     return (accepted && stateAccepted);
587 }
588 
589 // -----------------------------------------------------------------------------
590 
SearchFilterModel(QObject * const parent)591 SearchFilterModel::SearchFilterModel(QObject* const parent)
592     : CheckableAlbumFilterModel(parent),
593       m_searchType             (-1),
594       m_listTemporary          (false)
595 {
596 }
597 
setSourceSearchModel(SearchModel * const source)598 void SearchFilterModel::setSourceSearchModel(SearchModel* const source)
599 {
600     setSourceAlbumModel(source);
601 }
602 
sourceSearchModel() const603 SearchModel* SearchFilterModel::sourceSearchModel() const
604 {
605     return dynamic_cast<SearchModel*> (sourceModel());
606 }
607 
setFilterSearchType(DatabaseSearch::Type type)608 void SearchFilterModel::setFilterSearchType(DatabaseSearch::Type type)
609 {
610     setTypeFilter(type);
611 }
612 
listNormalSearches()613 void SearchFilterModel::listNormalSearches()
614 {
615     setTypeFilter(-1);
616 }
617 
listAllSearches()618 void SearchFilterModel::listAllSearches()
619 {
620     setTypeFilter(-2);
621 }
622 
listTimelineSearches()623 void SearchFilterModel::listTimelineSearches()
624 {
625     setTypeFilter(DatabaseSearch::TimeLineSearch);
626 }
627 
listHaarSearches()628 void SearchFilterModel::listHaarSearches()
629 {
630     setTypeFilter(DatabaseSearch::HaarSearch);
631 }
632 
listMapSearches()633 void SearchFilterModel::listMapSearches()
634 {
635     setTypeFilter(DatabaseSearch::MapSearch);
636 }
637 
listDuplicatesSearches()638 void SearchFilterModel::listDuplicatesSearches()
639 {
640     setTypeFilter(DatabaseSearch::DuplicatesSearch);
641 }
642 
setTypeFilter(int type)643 void SearchFilterModel::setTypeFilter(int type)
644 {
645     m_searchType = type;
646     invalidateFilter();
647     emit signalFilterChanged();
648 }
649 
setListTemporarySearches(bool list)650 void SearchFilterModel::setListTemporarySearches(bool list)
651 {
652     m_listTemporary = list;
653     invalidateFilter();
654     emit signalFilterChanged();
655 }
656 
isFiltering() const657 bool SearchFilterModel::isFiltering() const
658 {
659     return ((m_searchType != -2) || !m_listTemporary);
660 }
661 
matches(Album * album) const662 bool SearchFilterModel::matches(Album* album) const
663 {
664     if (!CheckableAlbumFilterModel::matches(album))
665     {
666         return false;
667     }
668 
669     SAlbum* const salbum = static_cast<SAlbum*>(album);
670 
671     if      (m_searchType == -1)
672     {
673         if (!salbum->isNormalSearch())
674         {
675             return false;
676         }
677     }
678     else if (m_searchType == -2)
679     {
680     }
681     else
682     {
683         if (salbum->searchType() != (DatabaseSearch::Type)m_searchType)
684         {
685             return false;
686         }
687     }
688 
689     if (!m_listTemporary && salbum->isTemporarySearch())
690     {
691         return false;
692     }
693 
694     return true;
695 }
696 
setSourceAlbumModel(AbstractAlbumModel * const source)697 void SearchFilterModel::setSourceAlbumModel(AbstractAlbumModel* const source)
698 {
699     AlbumFilterModel::setSourceAlbumModel(source);
700 }
701 
702 // -----------------------------------------------------------------------------
703 
TagPropertiesFilterModel(QObject * const parent)704 TagPropertiesFilterModel::TagPropertiesFilterModel(QObject* const parent)
705     : CheckableAlbumFilterModel(parent)
706 {
707     connect(AlbumManager::instance(), SIGNAL(signalTagPropertiesChanged(TAlbum*)),
708             this, SLOT(tagPropertiesChanged(TAlbum*)));
709 }
710 
setSourceAlbumModel(TagModel * const source)711 void TagPropertiesFilterModel::setSourceAlbumModel(TagModel* const source)
712 {
713     CheckableAlbumFilterModel::setSourceAlbumModel(source);
714 }
715 
sourceTagModel() const716 TagModel* TagPropertiesFilterModel::sourceTagModel() const
717 {
718     return dynamic_cast<TagModel*>(sourceModel());
719 }
720 
listOnlyTagsWithProperty(const QString & property)721 void TagPropertiesFilterModel::listOnlyTagsWithProperty(const QString& property)
722 {
723     if (m_propertiesWhiteList.contains(property))
724     {
725         return;
726     }
727 
728     m_propertiesWhiteList << property;
729     invalidateFilter();
730     emit signalFilterChanged();
731 }
732 
removeListOnlyProperty(const QString & property)733 void TagPropertiesFilterModel::removeListOnlyProperty(const QString& property)
734 {
735     if (!m_propertiesWhiteList.contains(property))
736     {
737         return;
738     }
739 
740     m_propertiesWhiteList.remove(property);
741     invalidateFilter();
742     emit signalFilterChanged();
743 }
744 
doNotListTagsWithProperty(const QString & property)745 void TagPropertiesFilterModel::doNotListTagsWithProperty(const QString& property)
746 {
747     if (m_propertiesBlackList.contains(property))
748     {
749         return;
750     }
751 
752     m_propertiesBlackList << property;
753     invalidateFilter();
754     emit signalFilterChanged();
755 }
756 
removeDoNotListProperty(const QString & property)757 void TagPropertiesFilterModel::removeDoNotListProperty(const QString& property)
758 {
759     if (!m_propertiesBlackList.contains(property))
760     {
761         return;
762     }
763 
764     m_propertiesBlackList.remove(property);
765     invalidateFilter();
766     emit signalFilterChanged();
767 }
768 
isFiltering() const769 bool TagPropertiesFilterModel::isFiltering() const
770 {
771     return (!m_propertiesWhiteList.isEmpty() || !m_propertiesBlackList.isEmpty());
772 }
773 
tagPropertiesChanged(TAlbum *)774 void TagPropertiesFilterModel::tagPropertiesChanged(TAlbum*)
775 {
776     // I do not expect batch changes. Otherwise we'll need a timer.
777 
778     if (isFiltering())
779     {
780         invalidateFilter();
781 
782         // Sort new when tag properties change.
783 
784         invalidate();
785     }
786 }
787 
matches(Album * album) const788 bool TagPropertiesFilterModel::matches(Album* album) const
789 {
790     if (!CheckableAlbumFilterModel::matches(album))
791     {
792         return false;
793     }
794 
795     TAlbum* const talbum = static_cast<TAlbum*>(album);
796 
797     foreach (const QString& prop, m_propertiesBlackList)
798     {
799         if (talbum->hasProperty(prop))
800         {
801             return false;
802         }
803     }
804 
805     foreach (const QString& prop, m_propertiesWhiteList)
806     {
807         if (!talbum->hasProperty(prop))
808         {
809             return false;
810         }
811     }
812 
813     return true;
814 }
815 
816 // -----------------------------------------------------------------------
817 
TagsManagerFilterModel(QObject * const parent)818 TagsManagerFilterModel::TagsManagerFilterModel(QObject* const parent)
819     : TagPropertiesFilterModel(parent)
820 {
821 }
822 
setQuickListTags(const QList<int> & tags)823 void TagsManagerFilterModel::setQuickListTags(const QList<int>& tags)
824 {
825     m_keywords.clear();
826 
827     foreach (int tag, tags)
828     {
829         m_keywords << tag;
830     }
831 
832     invalidateFilter();
833     emit signalFilterChanged();
834 }
835 
matches(Album * album) const836 bool TagsManagerFilterModel::matches(Album* album) const
837 {
838     if (!TagPropertiesFilterModel::matches(album))
839     {
840         return false;
841     }
842 
843     if (m_keywords.isEmpty())
844     {
845         return true;
846     }
847 
848     bool dirty = false;
849 
850     for (QSet<int>::const_iterator it = m_keywords.begin() ;
851          it != m_keywords.end() ; ++it)
852     {
853         TAlbum* const talbum = AlbumManager::instance()->findTAlbum(*it);
854 
855         if (!talbum)
856         {
857             continue;
858         }
859 
860         if (talbum->title().compare(album->title()) == 0)
861         {
862             dirty = true;
863         }
864     }
865 
866     return dirty;
867 }
868 
869 } // namespace Digikam
870