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