diff --git a/digikam.changes b/digikam.changes index 22c7ca1..bc10ef0 100644 --- a/digikam.changes +++ b/digikam.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Sat Aug 26 09:16:31 UTC 2017 - fabian@ritter-vogt.de + +- Add patch to fix build with cmake 3.9: + * fix-build-cmake39.patch + ------------------------------------------------------------------- Sun Jun 25 10:12:45 UTC 2017 - fabian@ritter-vogt.de diff --git a/digikam.spec b/digikam.spec index 47a8cc4..bb467ec 100644 --- a/digikam.spec +++ b/digikam.spec @@ -26,6 +26,8 @@ Url: http://www.digikam.org/ Source0: http://download.kde.org/stable/%{name}/%{name}-%{version}.tar.xz # PATCH-FIX-OPENSUSE find_libastro-qt5.patch -- fix build of geolocation support in Leap 42.1 Patch0: find_libastro-qt5.patch +# PATCH-FIX-UPSTREAM +Patch1: fix-build-cmake39.patch #This pulls in QWebEngine, which is not available on ppc64 %ifarch %ix86 x86_64 %arm aarch64 mips mips64 BuildRequires: akonadi-contact-devel @@ -76,8 +78,8 @@ BuildRequires: threadweaver-devel >= 5.1.0 BuildRequires: update-desktop-files BuildRequires: xorg-x11-devel BuildRequires: cmake(KF5CalendarCore) -BuildRequires: pkgconfig(Qt5Core) >= 5.6.0 BuildRequires: pkgconfig(Qt5Concurrent) +BuildRequires: pkgconfig(Qt5Core) >= 5.6.0 BuildRequires: pkgconfig(Qt5OpenGL) BuildRequires: pkgconfig(Qt5Sql) BuildRequires: pkgconfig(Qt5Svg) @@ -169,6 +171,9 @@ The main digikam libraries that are being shared between showfoto and digikam # we renamed libastro to libastro-qt5 in Leap 42.1, make FindMARBLE.cmake find it %patch0 -p1 %endif +pushd core +%patch1 -p1 +popd # Remove build time references so build-compare can do its work FAKE_BUILDDATE=$(LC_ALL=C date -u -r %{_sourcedir}/%{name}.changes '+%%b %%e %%Y') diff --git a/fix-build-cmake39.patch b/fix-build-cmake39.patch new file mode 100644 index 0000000..1a7d67c --- /dev/null +++ b/fix-build-cmake39.patch @@ -0,0 +1,13337 @@ +From 7e00441c257e7e9e5dc5ab983fc06046fb72b0c5 Mon Sep 17 00:00:00 2001 +From: Gilles Caulier +Date: Sat, 22 Jul 2017 15:46:08 +0200 +Subject: fix broken linking stage under MacOS with macports. move database + models into libdigikamdatabase. Let's others model in place to be included + into libdigikamcore + +--- + libs/database/CMakeLists.txt | 16 +- + libs/database/models/imagefiltermodel.cpp | 1116 ++++++++++++++++++ + libs/database/models/imagefiltermodel.h | 299 +++++ + libs/database/models/imagefiltermodelpriv.cpp | 258 ++++ + libs/database/models/imagefiltermodelpriv.h | 159 +++ + libs/database/models/imagefiltermodelthreads.cpp | 40 + + libs/database/models/imagefiltermodelthreads.h | 100 ++ + libs/database/models/imagefiltersettings.cpp | 952 +++++++++++++++ + libs/database/models/imagefiltersettings.h | 349 ++++++ + libs/database/models/imagelistmodel.cpp | 70 ++ + libs/database/models/imagelistmodel.h | 63 + + libs/database/models/imagemodel.cpp | 1368 ++++++++++++++++++++++ + libs/database/models/imagemodel.h | 364 ++++++ + libs/database/models/imagesortsettings.cpp | 400 +++++++ + libs/database/models/imagesortsettings.h | 225 ++++ + libs/database/models/imagethumbnailmodel.cpp | 323 +++++ + libs/database/models/imagethumbnailmodel.h | 140 +++ + libs/database/models/imageversionsmodel.cpp | 183 +++ + libs/database/models/imageversionsmodel.h | 75 ++ + libs/models/CMakeLists.txt | 15 +- + libs/models/imagefiltermodel.cpp | 1116 ------------------ + libs/models/imagefiltermodel.h | 299 ----- + libs/models/imagefiltermodelpriv.cpp | 258 ---- + libs/models/imagefiltermodelpriv.h | 159 --- + libs/models/imagefiltermodelthreads.cpp | 40 - + libs/models/imagefiltermodelthreads.h | 100 -- + libs/models/imagefiltersettings.cpp | 952 --------------- + libs/models/imagefiltersettings.h | 349 ------ + libs/models/imagelistmodel.cpp | 70 -- + libs/models/imagelistmodel.h | 63 - + libs/models/imagemodel.cpp | 1368 ---------------------- + libs/models/imagemodel.h | 364 ------ + libs/models/imagesortsettings.cpp | 400 ------- + libs/models/imagesortsettings.h | 225 ---- + libs/models/imagethumbnailmodel.cpp | 323 ----- + libs/models/imagethumbnailmodel.h | 140 --- + libs/models/imageversionsmodel.cpp | 183 --- + libs/models/imageversionsmodel.h | 75 -- + 38 files changed, 6499 insertions(+), 6500 deletions(-) + create mode 100644 libs/database/models/imagefiltermodel.cpp + create mode 100644 libs/database/models/imagefiltermodel.h + create mode 100644 libs/database/models/imagefiltermodelpriv.cpp + create mode 100644 libs/database/models/imagefiltermodelpriv.h + create mode 100644 libs/database/models/imagefiltermodelthreads.cpp + create mode 100644 libs/database/models/imagefiltermodelthreads.h + create mode 100644 libs/database/models/imagefiltersettings.cpp + create mode 100644 libs/database/models/imagefiltersettings.h + create mode 100644 libs/database/models/imagelistmodel.cpp + create mode 100644 libs/database/models/imagelistmodel.h + create mode 100644 libs/database/models/imagemodel.cpp + create mode 100644 libs/database/models/imagemodel.h + create mode 100644 libs/database/models/imagesortsettings.cpp + create mode 100644 libs/database/models/imagesortsettings.h + create mode 100644 libs/database/models/imagethumbnailmodel.cpp + create mode 100644 libs/database/models/imagethumbnailmodel.h + create mode 100644 libs/database/models/imageversionsmodel.cpp + create mode 100644 libs/database/models/imageversionsmodel.h + delete mode 100644 libs/models/imagefiltermodel.cpp + delete mode 100644 libs/models/imagefiltermodel.h + delete mode 100644 libs/models/imagefiltermodelpriv.cpp + delete mode 100644 libs/models/imagefiltermodelpriv.h + delete mode 100644 libs/models/imagefiltermodelthreads.cpp + delete mode 100644 libs/models/imagefiltermodelthreads.h + delete mode 100644 libs/models/imagefiltersettings.cpp + delete mode 100644 libs/models/imagefiltersettings.h + delete mode 100644 libs/models/imagelistmodel.cpp + delete mode 100644 libs/models/imagelistmodel.h + delete mode 100644 libs/models/imagemodel.cpp + delete mode 100644 libs/models/imagemodel.h + delete mode 100644 libs/models/imagesortsettings.cpp + delete mode 100644 libs/models/imagesortsettings.h + delete mode 100644 libs/models/imagethumbnailmodel.cpp + delete mode 100644 libs/models/imagethumbnailmodel.h + delete mode 100644 libs/models/imageversionsmodel.cpp + delete mode 100644 libs/models/imageversionsmodel.h + +diff --git a/libs/database/CMakeLists.txt b/libs/database/CMakeLists.txt +index 7d05536..a431a36 100644 +--- a/libs/database/CMakeLists.txt ++++ b/libs/database/CMakeLists.txt +@@ -13,6 +13,18 @@ endif (POLICY CMP0063) + # Boost uses operator names (and, not, ...) + string(REPLACE "-fno-operator-names" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + ++set(libdatabasemodels_SRCS ++ models/imagemodel.cpp ++ models/imagefiltermodel.cpp ++ models/imagefiltermodelpriv.cpp ++ models/imagefiltermodelthreads.cpp ++ models/imagefiltersettings.cpp ++ models/imagelistmodel.cpp ++ models/imagesortsettings.cpp ++ models/imagethumbnailmodel.cpp ++ models/imageversionsmodel.cpp ++) ++ + set(libdatabasecore_SRCS + server/databaseserverstarter.cpp + server/databaseservererror.cpp +@@ -152,10 +164,10 @@ if(ENABLE_DBUS) + include_directories($) + endif() + +-add_library(digikamdatabase_src OBJECT ${digikamdatabase_LIB_SRCS}) ++add_library(digikamdatabase_src OBJECT ${digikamdatabase_LIB_SRCS} ${libdatabasemodels_SRCS}) + add_library(digikamdatabasemain_src OBJECT ${libdatabaseutils_SRCS} ${libimgqsort_SRCS}) + add_library(digikamdatabasecore_src OBJECT ${libdatabasecore_SRCS}) +-add_library(digikamdatabase SHARED $ $) ++add_library(digikamdatabase $) + + generate_export_header(digikamdatabase + BASE_NAME digikam_database +diff --git a/libs/database/models/imagefiltermodel.cpp b/libs/database/models/imagefiltermodel.cpp +new file mode 100644 +index 0000000..3d57e05 +--- /dev/null ++++ b/libs/database/models/imagefiltermodel.cpp +@@ -0,0 +1,1116 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011-2017 by Gilles Caulier ++ * Copyright (C) 2010 by Andi Clemens ++ * Copyright (C) 2011 by Michael G. Hansen ++ * Copyright (C) 2014 by Mohamed Anwer ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagefiltermodel.h" ++#include "imagefiltermodelpriv.h" ++#include "imagefiltermodelthreads.h" ++ ++// Local includes ++ ++#include "digikam_debug.h" ++#include "coredbaccess.h" ++#include "coredbchangesets.h" ++#include "coredbwatch.h" ++#include "imageinfolist.h" ++#include "imagemodel.h" ++ ++namespace Digikam ++{ ++ ++ImageSortFilterModel::ImageSortFilterModel(QObject* parent) ++ : DCategorizedSortFilterProxyModel(parent), m_chainedModel(0) ++{ ++} ++ ++void ImageSortFilterModel::setSourceImageModel(ImageModel* source) ++{ ++ if (m_chainedModel) ++ { ++ m_chainedModel->setSourceImageModel(source); ++ } ++ else ++ { ++ setDirectSourceImageModel(source); ++ } ++} ++ ++void ImageSortFilterModel::setSourceFilterModel(ImageSortFilterModel* source) ++{ ++ if (source) ++ { ++ ImageModel* const model = sourceImageModel(); ++ ++ if (model) ++ { ++ source->setSourceImageModel(model); ++ } ++ } ++ ++ m_chainedModel = source; ++ setSourceModel(source); ++} ++ ++void ImageSortFilterModel::setDirectSourceImageModel(ImageModel* model) ++{ ++ setSourceModel(model); ++} ++ ++void ImageSortFilterModel::setSourceModel(QAbstractItemModel* model) ++{ ++ // made it protected, only setSourceImageModel is public ++ DCategorizedSortFilterProxyModel::setSourceModel(model); ++} ++ ++ImageModel* ImageSortFilterModel::sourceImageModel() const ++{ ++ if (m_chainedModel) ++ { ++ return m_chainedModel->sourceImageModel(); ++ } ++ ++ return static_cast(sourceModel()); ++} ++ ++ImageSortFilterModel* ImageSortFilterModel::sourceFilterModel() const ++{ ++ return m_chainedModel; ++} ++ ++ImageFilterModel* ImageSortFilterModel::imageFilterModel() const ++{ ++ // reimplemented in ImageFilterModel ++ if (m_chainedModel) ++ { ++ return m_chainedModel->imageFilterModel(); ++ } ++ ++ return 0; ++} ++ ++QModelIndex ImageSortFilterModel::mapToSourceImageModel(const QModelIndex& index) const ++{ ++ if (m_chainedModel) ++ { ++ return m_chainedModel->mapToSourceImageModel(mapToSource(index)); ++ } ++ ++ return mapToSource(index); ++} ++ ++QModelIndex ImageSortFilterModel::mapFromSourceImageModel(const QModelIndex& albummodel_index) const ++{ ++ if (m_chainedModel) ++ { ++ return mapFromSource(m_chainedModel->mapFromSourceImageModel(albummodel_index)); ++ } ++ ++ return mapFromSource(albummodel_index); ++} ++ ++ ++QModelIndex ImageSortFilterModel::mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const ++{ ++ if (m_chainedModel) ++ { ++ return m_chainedModel->mapToSourceImageModel(sourceModel_index); ++ } ++ return sourceModel_index; ++} ++ ++// -------------- Convenience mappers ------------------------------------------------------------------- ++ ++QList ImageSortFilterModel::mapListToSource(const QList& indexes) const ++{ ++ QList sourceIndexes; ++ foreach(const QModelIndex& index, indexes) ++ { ++ sourceIndexes << mapToSourceImageModel(index); ++ } ++ return sourceIndexes; ++} ++ ++QList ImageSortFilterModel::mapListFromSource(const QList& sourceIndexes) const ++{ ++ QList indexes; ++ foreach(const QModelIndex& index, sourceIndexes) ++ { ++ indexes << mapFromSourceImageModel(index); ++ } ++ return indexes; ++} ++ ++ImageInfo ImageSortFilterModel::imageInfo(const QModelIndex& index) const ++{ ++ return sourceImageModel()->imageInfo(mapToSourceImageModel(index)); ++} ++ ++qlonglong ImageSortFilterModel::imageId(const QModelIndex& index) const ++{ ++ return sourceImageModel()->imageId(mapToSourceImageModel(index)); ++} ++ ++QList ImageSortFilterModel::imageInfos(const QList& indexes) const ++{ ++ QList infos; ++ ImageModel* const model = sourceImageModel(); ++ ++ foreach(const QModelIndex& index, indexes) ++ { ++ infos << model->imageInfo(mapToSourceImageModel(index)); ++ } ++ ++ return infos; ++} ++ ++QList ImageSortFilterModel::imageIds(const QList& indexes) const ++{ ++ QList ids; ++ ImageModel* const model = sourceImageModel(); ++ ++ foreach(const QModelIndex& index, indexes) ++ { ++ ids << model->imageId(mapToSourceImageModel(index)); ++ } ++ ++ return ids; ++} ++ ++QModelIndex ImageSortFilterModel::indexForPath(const QString& filePath) const ++{ ++ return mapFromSourceImageModel(sourceImageModel()->indexForPath(filePath)); ++} ++ ++QModelIndex ImageSortFilterModel::indexForImageInfo(const ImageInfo& info) const ++{ ++ return mapFromSourceImageModel(sourceImageModel()->indexForImageInfo(info)); ++} ++ ++QModelIndex ImageSortFilterModel::indexForImageId(qlonglong id) const ++{ ++ return mapFromSourceImageModel(sourceImageModel()->indexForImageId(id)); ++} ++ ++QList ImageSortFilterModel::imageInfosSorted() const ++{ ++ QList infos; ++ const int size = rowCount(); ++ ImageModel* const model = sourceImageModel(); ++ ++ for (int i=0; iimageInfo(mapToSourceImageModel(index(i, 0))); ++ } ++ ++ return infos; ++} ++ ++// -------------------------------------------------------------------------------------------- ++ ++ImageFilterModel::ImageFilterModel(QObject* parent) ++ : ImageSortFilterModel(parent), ++ d_ptr(new ImageFilterModelPrivate) ++{ ++ d_ptr->init(this); ++} ++ ++ImageFilterModel::ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent) ++ : ImageSortFilterModel(parent), ++ d_ptr(&dd) ++{ ++ d_ptr->init(this); ++} ++ ++ImageFilterModel::~ImageFilterModel() ++{ ++ Q_D(ImageFilterModel); ++ delete d; ++} ++ ++void ImageFilterModel::setDirectSourceImageModel(ImageModel* sourceModel) ++{ ++ Q_D(ImageFilterModel); ++ ++ if (d->imageModel) ++ { ++ d->imageModel->unsetPreprocessor(d); ++ disconnect(d->imageModel, SIGNAL(modelReset()), ++ this, SLOT(slotModelReset())); ++ slotModelReset(); ++ } ++ ++ d->imageModel = sourceModel; ++ ++ if (d->imageModel) ++ { ++ d->imageModel->setPreprocessor(d); ++ ++ connect(d->imageModel, SIGNAL(preprocess(QList,QList)), ++ d, SLOT(preprocessInfos(QList,QList))); ++ ++ connect(d->imageModel, SIGNAL(processAdded(QList,QList)), ++ d, SLOT(processAddedInfos(QList,QList))); ++ ++ connect(d, SIGNAL(reAddImageInfos(QList,QList)), ++ d->imageModel, SLOT(reAddImageInfos(QList,QList))); ++ ++ connect(d, SIGNAL(reAddingFinished()), ++ d->imageModel, SLOT(reAddingFinished())); ++ ++ connect(d->imageModel, SIGNAL(modelReset()), ++ this, SLOT(slotModelReset())); ++ ++ connect(d->imageModel, SIGNAL(imageChange(ImageChangeset,QItemSelection)), ++ this, SLOT(slotImageChange(ImageChangeset))); ++ ++ connect(d->imageModel, SIGNAL(imageTagChange(ImageTagChangeset,QItemSelection)), ++ this, SLOT(slotImageTagChange(ImageTagChangeset))); ++ } ++ ++ setSourceModel(d->imageModel); ++} ++ ++QVariant ImageFilterModel::data(const QModelIndex& index, int role) const ++{ ++ Q_D(const ImageFilterModel); ++ ++ if (!index.isValid()) ++ { ++ return QVariant(); ++ } ++ ++ switch (role) ++ { ++ // Attention: This breaks should there ever be another filter model between this and the ImageModel ++ ++ case DCategorizedSortFilterProxyModel::CategoryDisplayRole: ++ return categoryIdentifier(d->imageModel->imageInfoRef(mapToSource(index))); ++ case CategorizationModeRole: ++ return d->sorter.categorizationMode; ++ case SortOrderRole: ++ return d->sorter.sortRole; ++ //case CategoryCountRole: ++ // return categoryCount(d->imageModel->imageInfoRef(mapToSource(index))); ++ case CategoryAlbumIdRole: ++ return d->imageModel->imageInfoRef(mapToSource(index)).albumId(); ++ case CategoryFormatRole: ++ return d->imageModel->imageInfoRef(mapToSource(index)).format(); ++ case GroupIsOpenRole: ++ return d->groupFilter.isAllOpen() || ++ d->groupFilter.isOpen(d->imageModel->imageInfoRef(mapToSource(index)).id()); ++ case ImageFilterModelPointerRole: ++ return QVariant::fromValue(const_cast(this)); ++ } ++ ++ return DCategorizedSortFilterProxyModel::data(index, role); ++} ++ ++ImageFilterModel* ImageFilterModel::imageFilterModel() const ++{ ++ return const_cast(this); ++} ++ ++DatabaseFields::Set ImageFilterModel::suggestedWatchFlags() const ++{ ++ DatabaseFields::Set watchFlags; ++ watchFlags |= DatabaseFields::Name | DatabaseFields::FileSize | DatabaseFields::ModificationDate; ++ watchFlags |= DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::Orientation | ++ DatabaseFields::Width | DatabaseFields::Height; ++ watchFlags |= DatabaseFields::Comment; ++ watchFlags |= DatabaseFields::ImageRelations; ++ return watchFlags; ++} ++ ++// -------------- Filter settings -------------- ++ ++void ImageFilterModel::setDayFilter(const QList& days) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setDayFilter(days); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setTagFilter(const QList& includedTags, const QList& excludedTags, ++ ImageFilterSettings::MatchingCondition matchingCond, ++ bool showUnTagged, const QList& clTagIds, const QList& plTagIds) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setTagFilter(includedTags, excludedTags, matchingCond, showUnTagged, clTagIds, plTagIds); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setRatingFilter(rating, ratingCond, isUnratedExcluded); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setUrlWhitelist(const QList urlList, const QString& id) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setUrlWhitelist(urlList, id); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setIdWhitelist(const QList& idList, const QString& id) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setIdWhitelist(idList, id); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setMimeTypeFilter(int mimeTypeFilter) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setMimeTypeFilter(mimeTypeFilter); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setGeolocationFilter(condition); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setTextFilter(const SearchTextFilterSettings& settings) ++{ ++ Q_D(ImageFilterModel); ++ d->filter.setTextFilter(settings); ++ setImageFilterSettings(d->filter); ++} ++ ++void ImageFilterModel::setImageFilterSettings(const ImageFilterSettings& settings) ++{ ++ Q_D(ImageFilterModel); ++ ++ { ++ QMutexLocker lock(&d->mutex); ++ d->version++; ++ d->filter = settings; ++ d->filterCopy = settings; ++ d->versionFilterCopy = d->versionFilter; ++ d->groupFilterCopy = d->groupFilter; ++ ++ d->needPrepareComments = settings.isFilteringByText(); ++ d->needPrepareTags = settings.isFilteringByTags(); ++ d->needPrepareGroups = true; ++ d->needPrepare = d->needPrepareComments || d->needPrepareTags || d->needPrepareGroups; ++ ++ d->hasOneMatch = false; ++ d->hasOneMatchForText = false; ++ } ++ ++ d->filterResults.clear(); ++ ++ //d->categoryCountHashInt.clear(); ++ //d->categoryCountHashString.clear(); ++ if (d->imageModel) ++ { ++ d->infosToProcess(d->imageModel->imageInfos()); ++ } ++ ++ emit filterSettingsChanged(settings); ++} ++ ++void ImageFilterModel::setVersionManagerSettings(const VersionManagerSettings& settings) ++{ ++ Q_D(ImageFilterModel); ++ d->versionFilter.setVersionManagerSettings(settings); ++ setVersionImageFilterSettings(d->versionFilter); ++} ++ ++void ImageFilterModel::setExceptionList(const QList& idList, const QString& id) ++{ ++ Q_D(ImageFilterModel); ++ d->versionFilter.setExceptionList(idList, id); ++ setVersionImageFilterSettings(d->versionFilter); ++} ++ ++void ImageFilterModel::setVersionImageFilterSettings(const VersionImageFilterSettings& settings) ++{ ++ Q_D(ImageFilterModel); ++ d->versionFilter = settings; ++ slotUpdateFilter(); ++} ++ ++bool ImageFilterModel::isGroupOpen(qlonglong group) const ++{ ++ Q_D(const ImageFilterModel); ++ return d->groupFilter.isOpen(group); ++} ++ ++bool ImageFilterModel::isAllGroupsOpen() const ++{ ++ Q_D(const ImageFilterModel); ++ return d->groupFilter.isAllOpen(); ++} ++ ++void ImageFilterModel::setGroupOpen(qlonglong group, bool open) ++{ ++ Q_D(ImageFilterModel); ++ d->groupFilter.setOpen(group, open); ++ setGroupImageFilterSettings(d->groupFilter); ++} ++ ++void ImageFilterModel::toggleGroupOpen(qlonglong group) ++{ ++ setGroupOpen(group, !isGroupOpen(group)); ++} ++ ++void ImageFilterModel::setAllGroupsOpen(bool open) ++{ ++ Q_D(ImageFilterModel); ++ d->groupFilter.setAllOpen(open); ++ setGroupImageFilterSettings(d->groupFilter); ++} ++ ++void ImageFilterModel::setGroupImageFilterSettings(const GroupImageFilterSettings& settings) ++{ ++ Q_D(ImageFilterModel); ++ d->groupFilter = settings; ++ slotUpdateFilter(); ++} ++ ++void ImageFilterModel::slotUpdateFilter() ++{ ++ Q_D(ImageFilterModel); ++ setImageFilterSettings(d->filter); ++} ++ ++ImageFilterSettings ImageFilterModel::imageFilterSettings() const ++{ ++ Q_D(const ImageFilterModel); ++ return d->filter; ++} ++ ++ImageSortSettings ImageFilterModel::imageSortSettings() const ++{ ++ Q_D(const ImageFilterModel); ++ return d->sorter; ++} ++ ++VersionImageFilterSettings ImageFilterModel::versionImageFilterSettings() const ++{ ++ Q_D(const ImageFilterModel); ++ return d->versionFilter; ++} ++ ++GroupImageFilterSettings ImageFilterModel::groupImageFilterSettings() const ++{ ++ Q_D(const ImageFilterModel); ++ return d->groupFilter; ++} ++ ++void ImageFilterModel::slotModelReset() ++{ ++ Q_D(ImageFilterModel); ++ { ++ QMutexLocker lock(&d->mutex); ++ // discard all packages on the way that are marked as send out for re-add ++ d->lastDiscardVersion = d->version; ++ d->sentOutForReAdd = 0; ++ // discard all packages on the way ++ d->version++; ++ d->sentOut = 0; ++ ++ d->hasOneMatch = false; ++ d->hasOneMatchForText = false; ++ } ++ d->filterResults.clear(); ++} ++ ++bool ImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const ++{ ++ Q_D(const ImageFilterModel); ++ ++ if (source_parent.isValid()) ++ { ++ return false; ++ } ++ ++ qlonglong id = d->imageModel->imageId(source_row); ++ QHash::const_iterator it = d->filterResults.constFind(id); ++ ++ if (it != d->filterResults.constEnd()) ++ { ++ return it.value(); ++ } ++ ++ // usually done in thread and cache, unless source model changed ++ ImageInfo info = d->imageModel->imageInfo(source_row); ++ bool match = d->filter.matches(info); ++ match = match ? d->versionFilter.matches(info) : false; ++ ++ return match ? d->groupFilter.matches(info) : false; ++} ++ ++void ImageFilterModel::setSendImageInfoSignals(bool sendSignals) ++{ ++ if (sendSignals) ++ { ++ connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), ++ this, SLOT(slotRowsInserted(QModelIndex,int,int))); ++ ++ connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), ++ this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); ++ } ++ else ++ { ++ disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), ++ this, SLOT(slotRowsInserted(QModelIndex,int,int))); ++ ++ disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), ++ this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); ++ } ++} ++ ++void ImageFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end) ++{ ++ QList infos; ++ ++ for (int i=start; i<=end; ++i) ++ { ++ infos << imageInfo(index(i, 0)); ++ } ++ ++ emit imageInfosAdded(infos); ++} ++ ++void ImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) ++{ ++ QList infos; ++ ++ for (int i=start; i<=end; ++i) ++ { ++ infos << imageInfo(index(i, 0)); ++ } ++ ++ emit imageInfosAboutToBeRemoved(infos); ++} ++ ++// -------------- Threaded preparation & filtering -------------- ++ ++void ImageFilterModel::addPrepareHook(ImageFilterModelPrepareHook* hook) ++{ ++ Q_D(ImageFilterModel); ++ QMutexLocker lock(&d->mutex); ++ d->prepareHooks << hook; ++} ++ ++void ImageFilterModel::removePrepareHook(ImageFilterModelPrepareHook* hook) ++{ ++ Q_D(ImageFilterModel); ++ QMutexLocker lock(&d->mutex); ++ d->prepareHooks.removeAll(hook); ++} ++ ++void ImageFilterModelPreparer::process(ImageFilterModelTodoPackage package) ++{ ++ if (!checkVersion(package)) ++ { ++ emit discarded(package); ++ return; ++ } ++ ++ // get thread-local copy ++ bool needPrepareTags, needPrepareComments, needPrepareGroups; ++ QList prepareHooks; ++ { ++ QMutexLocker lock(&d->mutex); ++ needPrepareTags = d->needPrepareTags; ++ needPrepareComments = d->needPrepareComments; ++ needPrepareGroups = d->needPrepareGroups; ++ prepareHooks = d->prepareHooks; ++ } ++ ++ //TODO: Make efficient!! ++ if (needPrepareComments) ++ { ++ foreach(const ImageInfo& info, package.infos) ++ { ++ info.comment(); ++ } ++ } ++ ++ if (!checkVersion(package)) ++ { ++ emit discarded(package); ++ return; ++ } ++ ++ // The downside of QVector: At some point, we may need a QList for an API. ++ // Nonetheless, QList and ImageInfo is fast. We could as well ++ // reimplement ImageInfoList to ImageInfoVector (internally with templates?) ++ ImageInfoList infoList; ++ ++ if (needPrepareTags || needPrepareGroups) ++ { ++ infoList = package.infos.toList(); ++ } ++ ++ if (needPrepareTags) ++ { ++ infoList.loadTagIds(); ++ } ++ ++ if (needPrepareGroups) ++ { ++ infoList.loadGroupImageIds(); ++ } ++ ++ foreach(ImageFilterModelPrepareHook* hook, prepareHooks) ++ { ++ hook->prepare(package.infos); ++ } ++ ++ emit processed(package); ++} ++ ++void ImageFilterModelFilterer::process(ImageFilterModelTodoPackage package) ++{ ++ if (!checkVersion(package)) ++ { ++ emit discarded(package); ++ return; ++ } ++ ++ // get thread-local copy ++ ImageFilterSettings localFilter; ++ VersionImageFilterSettings localVersionFilter; ++ GroupImageFilterSettings localGroupFilter; ++ bool hasOneMatch; ++ bool hasOneMatchForText; ++ { ++ QMutexLocker lock(&d->mutex); ++ localFilter = d->filterCopy; ++ localVersionFilter = d->versionFilterCopy; ++ localGroupFilter = d->groupFilterCopy; ++ hasOneMatch = d->hasOneMatch; ++ hasOneMatchForText = d->hasOneMatchForText; ++ } ++ ++ // Actual filtering. The variants to spare checking hasOneMatch over and over again. ++ if (hasOneMatch && hasOneMatchForText) ++ { ++ foreach(const ImageInfo& info, package.infos) ++ { ++ package.filterResults[info.id()] = localFilter.matches(info) && ++ localVersionFilter.matches(info) && ++ localGroupFilter.matches(info); ++ } ++ } ++ else if (hasOneMatch) ++ { ++ bool matchForText; ++ ++ foreach(const ImageInfo& info, package.infos) ++ { ++ package.filterResults[info.id()] = localFilter.matches(info, &matchForText) && ++ localVersionFilter.matches(info) && ++ localGroupFilter.matches(info); ++ ++ if (matchForText) ++ { ++ hasOneMatchForText = true; ++ } ++ } ++ } ++ else ++ { ++ bool result, matchForText; ++ ++ foreach(const ImageInfo& info, package.infos) ++ { ++ result = localFilter.matches(info, &matchForText) && ++ localVersionFilter.matches(info) && ++ localGroupFilter.matches(info); ++ package.filterResults[info.id()] = result; ++ ++ if (result) ++ { ++ hasOneMatch = true; ++ } ++ ++ if (matchForText) ++ { ++ hasOneMatchForText = true; ++ } ++ } ++ } ++ ++ if (checkVersion(package)) ++ { ++ QMutexLocker lock(&d->mutex); ++ d->hasOneMatch = hasOneMatch; ++ d->hasOneMatchForText = hasOneMatchForText; ++ } ++ ++ emit processed(package); ++} ++ ++// -------------- Sorting and Categorization ------------------------------------------------------- ++ ++void ImageFilterModel::setImageSortSettings(const ImageSortSettings& sorter) ++{ ++ Q_D(ImageFilterModel); ++ d->sorter = sorter; ++ setCategorizedModel(d->sorter.categorizationMode != ImageSortSettings::NoCategories); ++ invalidate(); ++} ++ ++void ImageFilterModel::setCategorizationMode(ImageSortSettings::CategorizationMode mode) ++{ ++ Q_D(ImageFilterModel); ++ d->sorter.setCategorizationMode(mode); ++ setImageSortSettings(d->sorter); ++} ++ ++void ImageFilterModel::setCategorizationSortOrder(ImageSortSettings::SortOrder order) ++{ ++ Q_D(ImageFilterModel); ++ d->sorter.setCategorizationSortOrder(order); ++ setImageSortSettings(d->sorter); ++} ++ ++void ImageFilterModel::setSortRole(ImageSortSettings::SortRole role) ++{ ++ Q_D(ImageFilterModel); ++ d->sorter.setSortRole(role); ++ setImageSortSettings(d->sorter); ++} ++ ++void ImageFilterModel::setSortOrder(ImageSortSettings::SortOrder order) ++{ ++ Q_D(ImageFilterModel); ++ d->sorter.setSortOrder(order); ++ setImageSortSettings(d->sorter); ++} ++ ++void ImageFilterModel::setStringTypeNatural(bool natural) ++{ ++ Q_D(ImageFilterModel); ++ d->sorter.setStringTypeNatural(natural); ++ setImageSortSettings(d->sorter); ++} ++ ++int ImageFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const ++{ ++ // source indexes ++ Q_D(const ImageFilterModel); ++ ++ if (!d->sorter.isCategorized()) ++ { ++ return 0; ++ } ++ ++ if (!left.isValid() || !right.isValid()) ++ { ++ return -1; ++ } ++ ++ const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); ++ const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); ++ ++ // Check grouping ++ qlonglong leftGroupImageId = leftInfo.groupImageId(); ++ qlonglong rightGroupImageId = rightInfo.groupImageId(); ++ ++ return compareInfosCategories(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), ++ rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); ++} ++ ++bool ImageFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const ++{ ++ // source indexes ++ Q_D(const ImageFilterModel); ++ ++ if (!left.isValid() || !right.isValid()) ++ { ++ return true; ++ } ++ ++ if (left == right) ++ { ++ return false; ++ } ++ ++ const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); ++ const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); ++ ++ if (leftInfo == rightInfo) ++ { ++ return d->sorter.lessThan(left.data(ImageModel::ExtraDataRole), right.data(ImageModel::ExtraDataRole)); ++ } ++ ++ // Check grouping ++ qlonglong leftGroupImageId = leftInfo.groupImageId(); ++ qlonglong rightGroupImageId = rightInfo.groupImageId(); ++ ++ // Either no grouping (-1), or same group image, or same image ++ if (leftGroupImageId == rightGroupImageId) ++ { ++ return infosLessThan(leftInfo, rightInfo); ++ } ++ ++ // We have grouping to handle ++ ++ // Is one grouped on the other? Sort behind leader. ++ if (leftGroupImageId == rightInfo.id()) ++ { ++ return false; ++ } ++ if (rightGroupImageId == leftInfo.id()) ++ { ++ return true; ++ } ++ ++ // Use the group leader for sorting ++ return infosLessThan(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), ++ rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); ++} ++ ++int ImageFilterModel::compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const ++{ ++ // Note: reimplemented in ImageAlbumFilterModel ++ Q_D(const ImageFilterModel); ++ return d->sorter.compareCategories(left, right); ++} ++ ++// Feel free to optimize. QString::number is 3x slower. ++static inline QString fastNumberToString(int id) ++{ ++ const int size = sizeof(int) * 2; ++ char c[size+1]; ++ c[size] = '\0'; ++ char* p = c; ++ int number = id; ++ ++ for (int i=0; i>= 4; ++ ++p; ++ } ++ ++ return QString::fromLatin1(c); ++} ++ ++QString ImageFilterModel::categoryIdentifier(const ImageInfo& i) const ++{ ++ Q_D(const ImageFilterModel); ++ ++ if (!d->sorter.isCategorized()) ++ { ++ return QString(); ++ } ++ ++ qlonglong groupedImageId = i.groupImageId(); ++ ImageInfo info = groupedImageId == -1 ? i : ImageInfo(groupedImageId); ++ ++ switch (d->sorter.categorizationMode) ++ { ++ case ImageSortSettings::NoCategories: ++ return QString(); ++ case ImageSortSettings::OneCategory: ++ return QString(); ++ case ImageSortSettings::CategoryByAlbum: ++ return fastNumberToString(info.albumId()); ++ case ImageSortSettings::CategoryByFormat: ++ return info.format(); ++ default: ++ return QString(); ++ } ++} ++ ++bool ImageFilterModel::infosLessThan(const ImageInfo& left, const ImageInfo& right) const ++{ ++ Q_D(const ImageFilterModel); ++ return d->sorter.lessThan(left, right); ++} ++ ++// -------------- Watching changes ----------------------------------------------------------------- ++ ++void ImageFilterModel::slotImageTagChange(const ImageTagChangeset& changeset) ++{ ++ Q_D(ImageFilterModel); ++ ++ if (!d->imageModel || d->imageModel->isEmpty()) ++ { ++ return; ++ } ++ ++ // already scheduled to re-filter? ++ if (d->updateFilterTimer->isActive()) ++ { ++ return; ++ } ++ ++ // do we filter at all? ++ if (!d->versionFilter.isFilteringByTags() && ++ !d->filter.isFilteringByTags() && ++ !d->filter.isFilteringByText()) ++ { ++ return; ++ } ++ ++ // is one of our images affected? ++ foreach(const qlonglong& id, changeset.ids()) ++ { ++ // if one matching image id is found, trigger a refresh ++ if (d->imageModel->hasImage(id)) ++ { ++ d->updateFilterTimer->start(); ++ return; ++ } ++ } ++} ++ ++void ImageFilterModel::slotImageChange(const ImageChangeset& changeset) ++{ ++ Q_D(ImageFilterModel); ++ ++ if (!d->imageModel || d->imageModel->isEmpty()) ++ { ++ return; ++ } ++ ++ // already scheduled to re-filter? ++ if (d->updateFilterTimer->isActive()) ++ { ++ return; ++ } ++ ++ // is one of the values affected that we filter or sort by? ++ DatabaseFields::Set set = changeset.changes(); ++ bool sortAffected = (set & d->sorter.watchFlags()); ++ bool filterAffected = (set & d->filter.watchFlags()) || (set & d->groupFilter.watchFlags()); ++ ++ if (!sortAffected && !filterAffected) ++ { ++ return; ++ } ++ ++ // is one of our images affected? ++ bool imageAffected = false; ++ ++ foreach(const qlonglong& id, changeset.ids()) ++ { ++ // if one matching image id is found, trigger a refresh ++ if (d->imageModel->hasImage(id)) ++ { ++ imageAffected = true; ++ break; ++ } ++ } ++ ++ if (!imageAffected) ++ { ++ return; ++ } ++ ++ if (filterAffected) ++ { ++ d->updateFilterTimer->start(); ++ } ++ else ++ { ++ invalidate(); // just resort, reuse filter results ++ } ++} ++ ++// ------------------------------------------------------------------------------------------------------- ++ ++NoDuplicatesImageFilterModel::NoDuplicatesImageFilterModel(QObject* parent) ++ : ImageSortFilterModel(parent) ++{ ++} ++ ++bool NoDuplicatesImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const ++{ ++ QModelIndex index = sourceModel()->index(source_row, 0, source_parent); ++ ++ if (index.data(ImageModel::ExtraDataDuplicateCount).toInt() <= 1) ++ { ++ return true; ++ } ++ ++ QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent); ++ ++ if (!previousIndex.isValid()) ++ { ++ return true; ++ } ++ ++ if (sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(index)) == sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(previousIndex))) ++ { ++ return false; ++ } ++ return true; ++} ++ ++/* ++void NoDuplicatesImageFilterModel::setSourceModel(QAbstractItemModel* model) ++{ ++ if (sourceModel()) ++ { ++ } ++ ++ ImageSortFilterModel::setSourceModel(model); ++ ++ if (sourceModel()) ++ { ++ connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), ++ this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); ++ } ++} ++ ++void NoDuplicatesImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end) ++{ ++ bool needInvalidate = false; ++ ++ for (int i = begin; i<=end; ++i) ++ { ++ QModelIndex index = sourceModel()->index(i, 0, parent); ++ ++ // filtered out by us? ++ if (!mapFromSource(index).isValid()) ++ { ++ continue; ++ } ++ ++ QModelIndex sourceIndex = mapFromDirectSourceToSourceImageModel(index); ++ qlonglong id = sourceImageModel()->imageId(sourceIndex); ++ ++ if (sourceImageModel()->numberOfIndexesForImageId(id) > 1) ++ { ++ needInvalidate = true; ++ } ++ } ++}*/ ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagefiltermodel.h b/libs/database/models/imagefiltermodel.h +new file mode 100644 +index 0000000..d131b3e +--- /dev/null ++++ b/libs/database/models/imagefiltermodel.h +@@ -0,0 +1,299 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011 by Gilles Caulier ++ * Copyright (C) 2010 by Andi Clemens ++ * Copyright (C) 2011 by Michael G. Hansen ++ * Copyright (C) 2014 by Mohamed Anwer ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGEFILTERMODEL_H ++#define IMAGEFILTERMODEL_H ++ ++// Local includes ++ ++#include "dcategorizedsortfilterproxymodel.h" ++#include "textfilter.h" ++#include "imagefiltersettings.h" ++#include "imagemodel.h" ++#include "imagesortsettings.h" ++#include "digikam_export.h" ++ ++namespace Digikam ++{ ++ ++class ImageChangeset; ++class ImageFilterModel; ++class ImageTagChangeset; ++ ++class DIGIKAM_DATABASE_EXPORT ImageFilterModelPrepareHook ++{ ++public: ++ ++ virtual ~ImageFilterModelPrepareHook() {}; ++ virtual void prepare(const QVector& infos) = 0; ++}; ++ ++// ----------------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT ImageSortFilterModel : public DCategorizedSortFilterProxyModel ++{ ++ Q_OBJECT ++ ++public: ++ ++ explicit ImageSortFilterModel(QObject* parent = 0); ++ ++ void setSourceImageModel(ImageModel* model); ++ ImageModel* sourceImageModel() const; ++ ++ void setSourceFilterModel(ImageSortFilterModel* model); ++ ImageSortFilterModel* sourceFilterModel() const; ++ ++ QModelIndex mapToSourceImageModel(const QModelIndex& index) const; ++ QModelIndex mapFromSourceImageModel(const QModelIndex& imagemodel_index) const; ++ QModelIndex mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const; ++ ++ /// Convenience methods mapped to ImageModel. ++ /// Mentioned indexes returned come from the source image model. ++ QList mapListToSource(const QList& indexes) const; ++ QList mapListFromSource(const QList& sourceIndexes) const; ++ ++ ImageInfo imageInfo(const QModelIndex& index) const; ++ qlonglong imageId(const QModelIndex& index) const; ++ QList imageInfos(const QList& indexes) const; ++ QList imageIds(const QList& indexes) const; ++ ++ QModelIndex indexForPath(const QString& filePath) const; ++ QModelIndex indexForImageInfo(const ImageInfo& info) const; ++ QModelIndex indexForImageId(qlonglong id) const; ++ ++ /** Returns a list of all image infos, sorted according to this model. ++ * If you do not need a sorted list, use ImageModel's imageInfos() method. ++ */ ++ QList imageInfosSorted() const; ++ ++ /// Returns this, any chained ImageFilterModel, or 0. ++ virtual ImageFilterModel* imageFilterModel() const; ++ ++protected: ++ ++ /// Reimplement if needed. Called only when model shall be set as (direct) sourceModel. ++ virtual void setDirectSourceImageModel(ImageModel* model); ++ ++ // made protected ++ virtual void setSourceModel(QAbstractItemModel* model); ++ ++protected: ++ ++ ImageSortFilterModel* m_chainedModel; ++}; ++ ++// ----------------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT ImageFilterModel : public ImageSortFilterModel ++{ ++ Q_OBJECT ++ ++public: ++ ++ enum ImageFilterModelRoles ++ { ++ /// Returns the current categorization mode ++ CategorizationModeRole = ImageModel::FilterModelRoles + 1, ++ /// Returns the current sort order ++ SortOrderRole = ImageModel::FilterModelRoles + 2, ++ // / Returns the number of items in the index' category ++ //CategoryCountRole = ImageModel::FilterModelRoles + 3, ++ /// Returns the id of the PAlbum of the index which is used for category ++ CategoryAlbumIdRole = ImageModel::FilterModelRoles + 3, ++ /// Returns the format of the index which is used for category ++ CategoryFormatRole = ImageModel::FilterModelRoles + 4, ++ /// Returns true if the given image is a group leader, and the group is opened ++ GroupIsOpenRole = ImageModel::FilterModelRoles + 5, ++ ImageFilterModelPointerRole = ImageModel::FilterModelRoles + 50 ++ }; ++ ++public: ++ ++ explicit ImageFilterModel(QObject* parent = 0); ++ ~ImageFilterModel(); ++ ++ /** Add a hook to get added images for preparation tasks before they are added in the model */ ++ void addPrepareHook(ImageFilterModelPrepareHook* hook); ++ void removePrepareHook(ImageFilterModelPrepareHook* hook); ++ ++ /** Returns a set of DatabaseFields suggested to set as watch flags on the source ImageModel. ++ * The contained flags will be those that this model can sort or filter by. */ ++ DatabaseFields::Set suggestedWatchFlags() const; ++ ++ ImageFilterSettings imageFilterSettings() const; ++ VersionImageFilterSettings versionImageFilterSettings() const; ++ GroupImageFilterSettings groupImageFilterSettings() const; ++ ImageSortSettings imageSortSettings() const; ++ ++ // group is identified by the id of its group leader ++ bool isGroupOpen(qlonglong group) const; ++ bool isAllGroupsOpen() const; ++ ++ /// Enables sending imageInfosAdded and imageInfosAboutToBeRemoved ++ void setSendImageInfoSignals(bool sendSignals); ++ ++ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; ++ virtual ImageFilterModel* imageFilterModel() const; ++ ++public Q_SLOTS: ++ ++ /** Changes the current version image filter settings and refilters. */ ++ void setVersionImageFilterSettings(const VersionImageFilterSettings& settings); ++ ++ /** Changes the current version image filter settings and refilters. */ ++ void setGroupImageFilterSettings(const GroupImageFilterSettings& settings); ++ ++ /** Adjust the current ImageFilterSettings. ++ * Equivalent to retrieving the current filter settings, adjusting the parameter ++ * and calling setImageFilterSettings. ++ * Provided for convenience. ++ * It is encouraged to use setImageFilterSettings if you change more than one ++ * parameter at a time. ++ */ ++ void setDayFilter(const QList& days); ++ void setTagFilter(const QList& includedTags, const QList& excludedTags, ++ ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged, ++ const QList& clTagIds, const QList& plTagIds); ++ void setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded); ++ void setMimeTypeFilter(int mimeTypeFilter); ++ void setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition); ++ void setTextFilter(const SearchTextFilterSettings& settings); ++ ++ void setCategorizationMode(ImageSortSettings::CategorizationMode mode); ++ void setCategorizationSortOrder(ImageSortSettings::SortOrder order); ++ void setSortRole(ImageSortSettings::SortRole role); ++ void setSortOrder(ImageSortSettings::SortOrder order); ++ void setStringTypeNatural(bool natural); ++ void setUrlWhitelist(const QList urlList, const QString& id); ++ void setIdWhitelist(const QList& idList, const QString& id); ++ ++ void setVersionManagerSettings(const VersionManagerSettings& settings); ++ void setExceptionList(const QList& idlist, const QString& id); ++ ++ void setGroupOpen(qlonglong group, bool open); ++ void toggleGroupOpen(qlonglong group); ++ void setAllGroupsOpen(bool open); ++ ++ /** Changes the current image filter settings and refilters. */ ++ virtual void setImageFilterSettings(const ImageFilterSettings& settings); ++ ++ /** Changes the current image sort settings and resorts. */ ++ virtual void setImageSortSettings(const ImageSortSettings& settings); ++ ++Q_SIGNALS: ++ ++ /// Signals that the set filter matches at least one index ++ void filterMatches(bool matches); ++ ++ /** Signals that the set text filter matches at least one entry. ++ If no text filter is set, this signal is emitted ++ with 'false' when filterMatches() is emitted. ++ */ ++ void filterMatchesForText(bool matchesByText); ++ ++ /** Emitted when the filter settings have been changed ++ (the model may not yet have been updated) ++ */ ++ void filterSettingsChanged(const ImageFilterSettings& settings); ++ ++ /** These signals need to be explicitly enabled with setSendImageInfoSignals() ++ */ ++ void imageInfosAdded(const QList& infos); ++ void imageInfosAboutToBeRemoved(const QList& infos); ++ ++public: ++ ++ // Declared as public because of use in sub-classes. ++ class ImageFilterModelPrivate; ++ ++protected: ++ ++ ImageFilterModelPrivate* const d_ptr; ++ ++protected: ++ ++ ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent); ++ ++ virtual void setDirectSourceImageModel(ImageModel* model); ++ ++ virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; ++ ++ virtual int compareCategories(const QModelIndex& left, const QModelIndex& right) const; ++ virtual bool subSortLessThan(const QModelIndex& left, const QModelIndex& right) const; ++ //virtual int categoryCount(const ImageInfo& info) const; ++ ++ /** Reimplement to customize category sorting, ++ * Return negative if category of left < category right, ++ * Return 0 if left and right are in the same category, else return positive. ++ */ ++ virtual int compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const; ++ ++ /** Reimplement to customize sorting. Do not take categories into account here. ++ */ ++ virtual bool infosLessThan(const ImageInfo& left, const ImageInfo& right) const; ++ ++ /** Returns a unique identifier for the category if info. The string need not be for user display. ++ */ ++ virtual QString categoryIdentifier(const ImageInfo& info) const; ++ ++protected Q_SLOTS: ++ ++ void slotModelReset(); ++ void slotUpdateFilter(); ++ ++ void slotImageTagChange(const ImageTagChangeset& changeset); ++ void slotImageChange(const ImageChangeset& changeset); ++ ++ void slotRowsInserted(const QModelIndex& parent, int start, int end); ++ void slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); ++ ++private: ++ ++ Q_DECLARE_PRIVATE(ImageFilterModel) ++}; ++ ++// ----------------------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT NoDuplicatesImageFilterModel : public ImageSortFilterModel ++{ ++ Q_OBJECT ++ ++public: ++ ++ explicit NoDuplicatesImageFilterModel(QObject* parent = 0); ++ ++protected: ++ ++ virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; ++}; ++ ++} // namespace Digikam ++ ++Q_DECLARE_METATYPE(Digikam::ImageFilterModel*) ++ ++#endif // IMAGEMODEL_H +diff --git a/libs/database/models/imagefiltermodelpriv.cpp b/libs/database/models/imagefiltermodelpriv.cpp +new file mode 100644 +index 0000000..07d9e79 +--- /dev/null ++++ b/libs/database/models/imagefiltermodelpriv.cpp +@@ -0,0 +1,258 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011-2017 by Gilles Caulier ++ * Copyright (C) 2010 by Andi Clemens ++ * Copyright (C) 2011 by Michael G. Hansen ++ * Copyright (C) 2014 by Mohamed Anwer ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagefiltermodelpriv.h" ++ ++// Local includes ++ ++#include "digikam_debug.h" ++#include "imagefiltermodelthreads.h" ++ ++namespace Digikam ++{ ++ ++ImageFilterModel::ImageFilterModelPrivate::ImageFilterModelPrivate() ++{ ++ imageModel = 0; ++ version = 0; ++ lastDiscardVersion = 0; ++ sentOut = 0; ++ sentOutForReAdd = 0; ++ updateFilterTimer = 0; ++ needPrepare = false; ++ needPrepareComments = false; ++ needPrepareTags = false; ++ needPrepareGroups = false; ++ preparer = 0; ++ filterer = 0; ++ hasOneMatch = false; ++ hasOneMatchForText = false; ++ ++ setupWorkers(); ++} ++ ++ImageFilterModel::ImageFilterModelPrivate::~ImageFilterModelPrivate() ++{ ++ // facilitate thread stopping ++ ++version; ++ preparer->deactivate(); ++ filterer->deactivate(); ++ delete preparer; ++ delete filterer; ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::init(ImageFilterModel* _q) ++{ ++ q = _q; ++ ++ updateFilterTimer = new QTimer(this); ++ updateFilterTimer->setSingleShot(true); ++ updateFilterTimer->setInterval(250); ++ ++ connect(updateFilterTimer, SIGNAL(timeout()), ++ q, SLOT(slotUpdateFilter())); ++ ++ // inter-thread redirection ++ qRegisterMetaType("ImageFilterModelTodoPackage"); ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList& infos, const QList& extraValues) ++{ ++ infosToProcess(infos, extraValues, true); ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList& infos, const QList& extraValues) ++{ ++ // These have already been added, we just process them afterwards ++ infosToProcess(infos, extraValues, false); ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::setupWorkers() ++{ ++ preparer = new ImageFilterModelPreparer(this); ++ filterer = new ImageFilterModelFilterer(this); ++ ++ // A package in constructed in infosToProcess. ++ // Normal flow is infosToProcess -> preparer::process -> filterer::process -> packageFinished. ++ // If no preparation is needed, the first step is skipped. ++ // If filter version changes, both will discard old package and send them to packageDiscarded. ++ ++ connect(this, SIGNAL(packageToPrepare(ImageFilterModelTodoPackage)), ++ preparer, SLOT(process(ImageFilterModelTodoPackage))); ++ ++ connect(this, SIGNAL(packageToFilter(ImageFilterModelTodoPackage)), ++ filterer, SLOT(process(ImageFilterModelTodoPackage))); ++ ++ connect(preparer, SIGNAL(processed(ImageFilterModelTodoPackage)), ++ filterer, SLOT(process(ImageFilterModelTodoPackage))); ++ ++ connect(filterer, SIGNAL(processed(ImageFilterModelTodoPackage)), ++ this, SLOT(packageFinished(ImageFilterModelTodoPackage))); ++ ++ connect(preparer, SIGNAL(discarded(ImageFilterModelTodoPackage)), ++ this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); ++ ++ connect(filterer, SIGNAL(discarded(ImageFilterModelTodoPackage)), ++ this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos) ++{ ++ infosToProcess(infos, QList(), false); ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd) ++{ ++ if (infos.isEmpty()) ++ { ++ return; ++ } ++ ++ filterer->schedule(); ++ ++ if (needPrepare) ++ { ++ preparer->schedule(); ++ } ++ ++ Q_ASSERT(extraValues.isEmpty() || infos.size() == extraValues.size()); ++ ++ // prepare and filter in chunks ++ const int size = infos.size(); ++ const int maxChunkSize = needPrepare ? PrepareChunkSize : FilterChunkSize; ++ const bool hasExtraValues = !extraValues.isEmpty(); ++ QList::const_iterator it = infos.constBegin(), end; ++ QList::const_iterator xit = extraValues.constBegin(), xend; ++ int index = 0; ++ QVector infoVector; ++ QVector extraValueVector; ++ ++ while (it != infos.constEnd()) ++ { ++ const int chunkSize = qMin(maxChunkSize, size - index); ++ infoVector.resize(chunkSize); ++ end = it + chunkSize; ++ qCopy(it, end, infoVector.begin()); ++ ++ if (hasExtraValues) ++ { ++ extraValueVector.resize(chunkSize); ++ xend = xit + chunkSize; ++ qCopy(xit, xend, extraValueVector.begin()); ++ xit = xend; ++ } ++ ++ it = end; ++ index += chunkSize; ++ ++ ++sentOut; ++ ++ if (forReAdd) ++ { ++ ++sentOutForReAdd; ++ } ++ ++ if (needPrepare) ++ { ++ emit packageToPrepare(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); ++ } ++ else ++ { ++ emit packageToFilter(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); ++ } ++ } ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::packageFinished(const ImageFilterModelTodoPackage& package) ++{ ++ // check if it got discarded on the journey ++ if (package.version != version) ++ { ++ packageDiscarded(package); ++ return; ++ } ++ ++ // incorporate result ++ QHash::const_iterator it = package.filterResults.constBegin(); ++ ++ for (; it != package.filterResults.constEnd(); ++it) ++ { ++ filterResults.insert(it.key(), it.value()); ++ } ++ ++ // re-add if necessary ++ if (package.isForReAdd) ++ { ++ emit reAddImageInfos(package.infos.toList(), package.extraValues.toList()); ++ ++ if (sentOutForReAdd == 1) // last package ++ { ++ emit reAddingFinished(); ++ } ++ } ++ ++ // decrement counters ++ --sentOut; ++ ++ if (package.isForReAdd) ++ { ++ --sentOutForReAdd; ++ } ++ ++ // If all packages have returned, filtered and readded, and no more are expected, ++ // and there is need to tell the filter result to the view, do that ++ if (sentOut == 0 && sentOutForReAdd == 0 && !imageModel->isRefreshing()) ++ { ++ q->invalidate(); // use invalidate, not invalidateFilter only. Sorting may have changed as well. ++ emit (q->filterMatches(hasOneMatch)); ++ emit (q->filterMatchesForText(hasOneMatchForText)); ++ filterer->deactivate(); ++ preparer->deactivate(); ++ } ++} ++ ++void ImageFilterModel::ImageFilterModelPrivate::packageDiscarded(const ImageFilterModelTodoPackage& package) ++{ ++ // Either, the model was reset, or the filter changed ++ // In the former case throw all away, in the latter case, recycle ++ if (package.version > lastDiscardVersion) ++ { ++ // Recycle packages: Send again with current version ++ // Do not increment sentOut or sentOutForReAdd here: it was not decremented! ++ ++ if (needPrepare) ++ { ++ emit packageToPrepare(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); ++ } ++ else ++ { ++ emit packageToFilter(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); ++ } ++ } ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagefiltermodelpriv.h b/libs/database/models/imagefiltermodelpriv.h +new file mode 100644 +index 0000000..a9e3f22 +--- /dev/null ++++ b/libs/database/models/imagefiltermodelpriv.h +@@ -0,0 +1,159 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-11 ++ * Description : Qt item model for database entries - private shared header ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGEFILTERMODELPRIV_H ++#define IMAGEFILTERMODELPRIV_H ++ ++// Qt includes ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// Local includes ++ ++#include "imageinfo.h" ++#include "imagefiltermodel.h" ++ ++#include "digikam_export.h" ++// Yes, we need the EXPORT macro in a private header because ++// this private header is shared across binary objects. ++// This does NOT make this classes here any more public! ++ ++namespace Digikam ++{ ++ ++const int PrepareChunkSize = 101; ++const int FilterChunkSize = 2001; ++ ++class ImageFilterModelTodoPackage ++{ ++public: ++ ++ ImageFilterModelTodoPackage() ++ : version(0), isForReAdd(false) ++ { ++ } ++ ++ ImageFilterModelTodoPackage(const QVector& infos, const QVector& extraValues, int version, bool isForReAdd) ++ : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd) ++ { ++ } ++ ++ QVector infos; ++ QVector extraValues; ++ unsigned int version; ++ bool isForReAdd; ++ QHash filterResults; ++}; ++ ++// ------------------------------------------------------------------------------------------------ ++ ++class ImageFilterModelPreparer; ++class ImageFilterModelFilterer; ++ ++class DIGIKAM_DATABASE_EXPORT ImageFilterModel::ImageFilterModelPrivate : public QObject ++{ ++ Q_OBJECT ++ ++public: ++ ++ ImageFilterModelPrivate(); ++ ~ImageFilterModelPrivate(); ++ ++ void init(ImageFilterModel* q); ++ void setupWorkers(); ++ void infosToProcess(const QList& infos); ++ void infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd = true); ++ ++public: ++ ++ ImageFilterModel* q; ++ ++ ImageModel* imageModel; ++ ++ ImageFilterSettings filter; ++ ImageSortSettings sorter; ++ VersionImageFilterSettings versionFilter; ++ GroupImageFilterSettings groupFilter; ++ ++ volatile unsigned int version; ++ unsigned int lastDiscardVersion; ++ unsigned int lastFilteredVersion; ++ int sentOut; ++ int sentOutForReAdd; ++ ++ QTimer* updateFilterTimer; ++ ++ bool needPrepare; ++ bool needPrepareComments; ++ bool needPrepareTags; ++ bool needPrepareGroups; ++ ++ QMutex mutex; ++ ImageFilterSettings filterCopy; ++ VersionImageFilterSettings versionFilterCopy; ++ GroupImageFilterSettings groupFilterCopy; ++ ImageFilterModelPreparer* preparer; ++ ImageFilterModelFilterer* filterer; ++ ++ QHash filterResults; ++ bool hasOneMatch; ++ bool hasOneMatchForText; ++ ++ QList prepareHooks; ++ ++/* ++ QHash > categoryCountHashInt; ++ QHash > categoryCountHashString; ++ ++public: ++ ++ void cacheCategoryCount(int id, qlonglong imageid) const ++ { const_cast(this)->categoryCountHashInt[id].insert(imageid); } ++ void cacheCategoryCount(const QString& id, qlonglong imageid) const ++ { const_cast(this)->categoryCountHashString[id].insert(imageid); } ++*/ ++ ++public Q_SLOTS: ++ ++ void preprocessInfos(const QList& infos, const QList& extraValues); ++ void processAddedInfos(const QList& infos, const QList& extraValues); ++ void packageFinished(const ImageFilterModelTodoPackage& package); ++ void packageDiscarded(const ImageFilterModelTodoPackage& package); ++ ++Q_SIGNALS: ++ ++ void packageToPrepare(const ImageFilterModelTodoPackage& package); ++ void packageToFilter(const ImageFilterModelTodoPackage& package); ++ void reAddImageInfos(const QList& infos, const QList& extraValues); ++ void reAddingFinished(); ++}; ++ ++} // namespace Digikam ++ ++#endif // IMAGEFILTERMODELPRIV_H +diff --git a/libs/database/models/imagefiltermodelthreads.cpp b/libs/database/models/imagefiltermodelthreads.cpp +new file mode 100644 +index 0000000..aa5c462 +--- /dev/null ++++ b/libs/database/models/imagefiltermodelthreads.cpp +@@ -0,0 +1,40 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011-2017 by Gilles Caulier ++ * Copyright (C) 2010 by Andi Clemens ++ * Copyright (C) 2011 by Michael G. Hansen ++ * Copyright (C) 2014 by Mohamed Anwer ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagefiltermodel.h" ++#include "imagefiltermodelpriv.h" ++#include "imagefiltermodelthreads.h" ++ ++namespace Digikam ++{ ++ ++ImageFilterModelWorker::ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d) ++ : d(d) ++{ ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagefiltermodelthreads.h b/libs/database/models/imagefiltermodelthreads.h +new file mode 100644 +index 0000000..83fa987 +--- /dev/null ++++ b/libs/database/models/imagefiltermodelthreads.h +@@ -0,0 +1,100 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-11 ++ * Description : Qt item model for database entries - private header ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGEFILTERMODELTHREADS_H ++#define IMAGEFILTERMODELTHREADS_H ++ ++// Qt includes ++ ++#include ++ ++// Local includes ++ ++#include "digikam_export.h" ++#include "workerobject.h" ++ ++namespace Digikam ++{ ++ ++class DIGIKAM_DATABASE_EXPORT ImageFilterModelWorker : public WorkerObject ++{ ++ Q_OBJECT ++ ++public: ++ ++ explicit ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d); ++ ++ bool checkVersion(const ImageFilterModelTodoPackage& package) ++ { ++ return d->version == package.version; ++ } ++ ++public Q_SLOTS: ++ ++ virtual void process(ImageFilterModelTodoPackage package) = 0; ++ ++Q_SIGNALS: ++ ++ void processed(const ImageFilterModelTodoPackage& package); ++ void discarded(const ImageFilterModelTodoPackage& package); ++ ++protected: ++ ++ ImageFilterModel::ImageFilterModelPrivate* d; ++}; ++ ++// ----------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT ImageFilterModelPreparer : public ImageFilterModelWorker ++{ ++ Q_OBJECT ++ ++public: ++ ++ explicit ImageFilterModelPreparer(ImageFilterModel::ImageFilterModelPrivate* const d) ++ : ImageFilterModelWorker(d) ++ { ++ } ++ ++ void process(ImageFilterModelTodoPackage package); ++}; ++ ++// ---------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT ImageFilterModelFilterer : public ImageFilterModelWorker ++{ ++ Q_OBJECT ++ ++public: ++ ++ explicit ImageFilterModelFilterer(ImageFilterModel::ImageFilterModelPrivate* const d) ++ : ImageFilterModelWorker(d) ++ { ++ } ++ ++ void process(ImageFilterModelTodoPackage package); ++}; ++ ++} // namespace Digikam ++ ++#endif // IMAGEFILTERMODELTHREADS_H +diff --git a/libs/database/models/imagefiltersettings.cpp b/libs/database/models/imagefiltersettings.cpp +new file mode 100644 +index 0000000..b61e7f9 +--- /dev/null ++++ b/libs/database/models/imagefiltersettings.cpp +@@ -0,0 +1,952 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Filter values for use with ImageFilterModel ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011-2017 by Gilles Caulier ++ * Copyright (C) 2010 by Andi Clemens ++ * Copyright (C) 2011 by Michael G. Hansen ++ * Copyright (C) 2014 by Mohamed Anwer ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagefiltersettings.h" ++ ++// C++ includes ++ ++#include ++ ++// Qt includes ++ ++#include ++ ++// Local includes ++ ++#include "digikam_debug.h" ++#include "coredbfields.h" ++#include "digikam_globals.h" ++#include "imageinfo.h" ++#include "tagscache.h" ++#include "versionmanagersettings.h" ++ ++namespace Digikam ++{ ++ ++ImageFilterSettings::ImageFilterSettings() ++{ ++ m_untaggedFilter = false; ++ m_isUnratedExcluded = false; ++ m_ratingFilter = 0; ++ m_mimeTypeFilter = MimeFilter::AllFiles; ++ m_ratingCond = GreaterEqualCondition; ++ m_matchingCond = OrCondition; ++ m_geolocationCondition = GeolocationNoFilter; ++} ++ ++DatabaseFields::Set ImageFilterSettings::watchFlags() const ++{ ++ DatabaseFields::Set set; ++ ++ if (isFilteringByDay()) ++ { ++ set |= DatabaseFields::CreationDate; ++ } ++ ++ if (isFilteringByText()) ++ { ++ set |= DatabaseFields::Name; ++ set |= DatabaseFields::Comment; ++ } ++ ++ if (isFilteringByRating()) ++ { ++ set |= DatabaseFields::Rating; ++ } ++ ++ if (isFilteringByTypeMime()) ++ { ++ set |= DatabaseFields::Category; ++ set |= DatabaseFields::Format; ++ } ++ ++ if (isFilteringByGeolocation()) ++ { ++ set |= DatabaseFields::ImagePositionsAll; ++ } ++ ++ if (isFilteringByColorLabels()) ++ { ++ set |= DatabaseFields::ColorLabel; ++ } ++ ++ if (isFilteringByPickLabels()) ++ { ++ set |= DatabaseFields::PickLabel; ++ } ++ ++ return set; ++} ++ ++bool ImageFilterSettings::isFilteringByDay() const ++{ ++ if (!m_dayFilter.isEmpty()) ++ { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ImageFilterSettings::isFilteringByTags() const ++{ ++ if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter) ++ { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ImageFilterSettings::isFilteringByColorLabels() const ++{ ++ if (!m_colorLabelTagFilter.isEmpty()) ++ { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ImageFilterSettings::isFilteringByPickLabels() const ++{ ++ if (!m_pickLabelTagFilter.isEmpty()) ++ { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ImageFilterSettings::isFilteringByText() const ++{ ++ if (!m_textFilterSettings.text.isEmpty()) ++ { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ImageFilterSettings::isFilteringByTypeMime() const ++{ ++ if (m_mimeTypeFilter != MimeFilter::AllFiles) ++ { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ImageFilterSettings::isFilteringByGeolocation() const ++{ ++ return (m_geolocationCondition != GeolocationNoFilter); ++} ++ ++bool ImageFilterSettings::isFilteringByRating() const ++{ ++ if (m_ratingFilter != 0 || m_ratingCond != GreaterEqualCondition || m_isUnratedExcluded) ++ { ++ return true; ++ } ++ ++ return false; ++} ++ ++bool ImageFilterSettings::isFilteringInternally() const ++{ ++ return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty()); ++} ++ ++bool ImageFilterSettings::isFiltering() const ++{ ++ return isFilteringByDay() || ++ isFilteringByTags() || ++ isFilteringByText() || ++ isFilteringByRating() || ++ isFilteringByTypeMime() || ++ isFilteringByColorLabels() || ++ isFilteringByPickLabels() || ++ isFilteringByGeolocation(); ++} ++ ++void ImageFilterSettings::setDayFilter(const QList& days) ++{ ++ m_dayFilter.clear(); ++ ++ for (QList::const_iterator it = days.constBegin(); it != days.constEnd(); ++it) ++ { ++ m_dayFilter.insert(*it, true); ++ } ++} ++ ++void ImageFilterSettings::setTagFilter(const QList& includedTags, ++ const QList& excludedTags, ++ MatchingCondition matchingCondition, ++ bool showUnTagged, ++ const QList& clTagIds, ++ const QList& plTagIds) ++{ ++ m_includeTagFilter = includedTags; ++ m_excludeTagFilter = excludedTags; ++ m_matchingCond = matchingCondition; ++ m_untaggedFilter = showUnTagged; ++ m_colorLabelTagFilter = clTagIds; ++ m_pickLabelTagFilter = plTagIds; ++} ++ ++void ImageFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded) ++{ ++ m_ratingFilter = rating; ++ m_ratingCond = ratingCondition; ++ m_isUnratedExcluded = isUnratedExcluded; ++} ++ ++void ImageFilterSettings::setMimeTypeFilter(int mime) ++{ ++ m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime; ++} ++ ++void ImageFilterSettings::setGeolocationFilter(const GeolocationCondition& condition) ++{ ++ m_geolocationCondition = condition; ++} ++ ++void ImageFilterSettings::setTextFilter(const SearchTextFilterSettings& settings) ++{ ++ m_textFilterSettings = settings; ++} ++ ++void ImageFilterSettings::setTagNames(const QHash& hash) ++{ ++ m_tagNameHash = hash; ++} ++ ++void ImageFilterSettings::setAlbumNames(const QHash& hash) ++{ ++ m_albumNameHash = hash; ++} ++ ++void ImageFilterSettings::setUrlWhitelist(const QList& urlList, const QString& id) ++{ ++ if (urlList.isEmpty()) ++ { ++ m_urlWhitelists.remove(id); ++ } ++ else ++ { ++ m_urlWhitelists.insert(id, urlList); ++ } ++} ++ ++void ImageFilterSettings::setIdWhitelist(const QList& idList, const QString& id) ++{ ++ if (idList.isEmpty()) ++ { ++ m_idWhitelists.remove(id); ++ } ++ else ++ { ++ m_idWhitelists.insert(id, idList); ++ } ++} ++ ++template ++bool containsAnyOf(const ContainerA& listA, const ContainerB& listB) ++{ ++ foreach (const typename ContainerA::value_type& a, listA) ++ { ++ if (listB.contains(a)) ++ { ++ return true; ++ } ++ } ++ return false; ++} ++ ++template ++bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception) ++{ ++ foreach (const typename ContainerB::value_type& n, noneOfList) ++ { ++ if (n != exception && list.contains(n)) ++ { ++ return false; ++ } ++ } ++ return true; ++} ++ ++bool ImageFilterSettings::matches(const ImageInfo& info, bool* const foundText) const ++{ ++ if (foundText) ++ { ++ *foundText = false; ++ } ++ ++ if (!isFilteringInternally()) ++ { ++ return true; ++ } ++ ++ bool match = false; ++ ++ if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty()) ++ { ++ QList tagIds = info.tagIds(); ++ QList::const_iterator it; ++ ++ match = m_includeTagFilter.isEmpty(); ++ ++ if (m_matchingCond == OrCondition) ++ { ++ for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) ++ { ++ if (tagIds.contains(*it)) ++ { ++ match = true; ++ break; ++ } ++ } ++ ++ match |= (m_untaggedFilter && tagIds.isEmpty()); ++ } ++ else // AND matching condition... ++ { ++ // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match ++ if (!m_untaggedFilter) ++ { ++ for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) ++ { ++ if (!tagIds.contains(*it)) ++ { ++ break; ++ } ++ } ++ ++ if (it == m_includeTagFilter.end()) ++ { ++ match = true; ++ } ++ } ++ } ++ ++ for (it = m_excludeTagFilter.begin(); it != m_excludeTagFilter.end(); ++it) ++ { ++ if (tagIds.contains(*it)) ++ { ++ match = false; ++ break; ++ } ++ } ++ } ++ else if (m_untaggedFilter) ++ { ++ match = !TagsCache::instance()->containsPublicTags(info.tagIds()); ++ } ++ else ++ { ++ match = true; ++ } ++ ++ //-- Filter by pick labels ------------------------------------------------ ++ ++ if (!m_pickLabelTagFilter.isEmpty()) ++ { ++ QList tagIds = info.tagIds(); ++ bool matchPL = false; ++ ++ if (containsAnyOf(m_pickLabelTagFilter, tagIds)) ++ { ++ matchPL = true; ++ } ++ else if (!matchPL) ++ { ++ int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel); ++ ++ if (m_pickLabelTagFilter.contains(noPickLabelTagId)) ++ { ++ // Searching for "has no ColorLabel" requires special handling: ++ // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag ++ matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId); ++ } ++ } ++ ++ match &= matchPL; ++ } ++ ++ //-- Filter by color labels ------------------------------------------------ ++ ++ if (!m_colorLabelTagFilter.isEmpty()) ++ { ++ QList tagIds = info.tagIds(); ++ bool matchCL = false; ++ ++ if (containsAnyOf(m_colorLabelTagFilter, tagIds)) ++ { ++ matchCL = true; ++ } ++ else if (!matchCL) ++ { ++ int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel); ++ ++ if (m_colorLabelTagFilter.contains(noColorLabelTagId)) ++ { ++ // Searching for "has no ColorLabel" requires special handling: ++ // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag ++ matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId); ++ } ++ } ++ ++ match &= matchCL; ++ } ++ ++ //-- Filter by date ----------------------------------------------------------- ++ ++ if (!m_dayFilter.isEmpty()) ++ { ++ match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime())); ++ } ++ ++ //-- Filter by rating --------------------------------------------------------- ++ ++ if (m_ratingFilter >= 0) ++ { ++ // for now we treat -1 (no rating) just like a rating of 0. ++ int rating = info.rating(); ++ ++ if (rating == -1) ++ { ++ rating = 0; ++ } ++ ++ if(m_isUnratedExcluded && rating == 0) ++ { ++ match = false; ++ } ++ else ++ { ++ if (m_ratingCond == GreaterEqualCondition) ++ { ++ // If the rating is not >=, i.e it is <, then it does not match. ++ if (rating < m_ratingFilter) ++ { ++ match = false; ++ } ++ } ++ else if (m_ratingCond == EqualCondition) ++ { ++ // If the rating is not =, i.e it is !=, then it does not match. ++ if (rating != m_ratingFilter) ++ { ++ match = false; ++ } ++ } ++ else ++ { ++ // If the rating is not <=, i.e it is >, then it does not match. ++ if (rating > m_ratingFilter) ++ { ++ match = false; ++ } ++ } ++ } ++ } ++ ++ // -- Filter by mime type ----------------------------------------------------- ++ ++ switch (m_mimeTypeFilter) ++ { ++ // info.format is a standardized string: Only one possibility per mime type ++ case MimeFilter::ImageFiles: ++ { ++ if (info.category() != DatabaseItem::Image) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::JPGFiles: ++ { ++ if (info.format() != QLatin1String("JPG")) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::PNGFiles: ++ { ++ if (info.format() != QLatin1String("PNG")) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::TIFFiles: ++ { ++ if (info.format() != QLatin1String("TIFF")) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::DNGFiles: ++ { ++ if (info.format() != QLatin1String("RAW-DNG")) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::NoRAWFiles: ++ { ++ if (info.format().startsWith(QLatin1String("RAW"))) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::RAWFiles: ++ { ++ if (!info.format().startsWith(QLatin1String("RAW"))) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::MoviesFiles: ++ { ++ if (info.category() != DatabaseItem::Video) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::AudioFiles: ++ { ++ if (info.category() != DatabaseItem::Audio) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ case MimeFilter::RasterFiles: ++ { ++ if (info.format() != QLatin1String("PSD") && // Adobe Photoshop Document ++ info.format() != QLatin1String("PSB") && // Adobe Photoshop Big ++ info.format() != QLatin1String("XCF") && // Gimp ++ info.format() != QLatin1String("KRA") && // Krita ++ info.format() != QLatin1String("ORA") // Open Raster ++ ) ++ { ++ match = false; ++ } ++ ++ break; ++ } ++ default: ++ { ++ // All Files: do nothing... ++ break; ++ } ++ } ++ ++ //-- Filter by geolocation ---------------------------------------------------- ++ ++ if (m_geolocationCondition!=GeolocationNoFilter) ++ { ++ if (m_geolocationCondition==GeolocationNoCoordinates) ++ { ++ if (info.hasCoordinates()) ++ { ++ match = false; ++ } ++ } ++ else if (m_geolocationCondition==GeolocationHasCoordinates) ++ { ++ if (!info.hasCoordinates()) ++ { ++ match = false; ++ } ++ } ++ } ++ ++ //-- Filter by text ----------------------------------------------------------- ++ ++ if (!m_textFilterSettings.text.isEmpty()) ++ { ++ bool textMatch = false; ++ ++ // Image name ++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName && ++ info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) ++ { ++ textMatch = true; ++ } ++ ++ // Image title ++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle && ++ info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) ++ { ++ textMatch = true; ++ } ++ ++ // Image comment ++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment && ++ info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) ++ { ++ textMatch = true; ++ } ++ ++ // Tag names ++ foreach(int id, info.tagIds()) ++ { ++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::TagName && ++ m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) ++ { ++ textMatch = true; ++ } ++ } ++ ++ // Album names ++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName && ++ m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) ++ { ++ textMatch = true; ++ } ++ ++ // Image Aspect Ratio ++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio) ++ { ++ QRegExp expRatio (QLatin1String("^\\d+:\\d+$")); ++ QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$")); ++ ++ if (expRatio.indexIn(m_textFilterSettings.text) > -1 && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+")))) ++ { ++ QString trimmedTextFilterSettingsText = m_textFilterSettings.text; ++ QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1String(":"), QString::SkipEmptyParts); ++ ++ if (numberStringList.length() == 2) ++ { ++ QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1); ++ bool canConverseNum = false; ++ bool canConverseDenom = false; ++ int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10); ++ ++ if (canConverseNum && canConverseDenom) ++ { ++ if (fabs(info.aspectRatio() - (double)num / denom) < 0.1) ++ textMatch = true; ++ } ++ } ++ } ++ else if (expFloat.indexIn(m_textFilterSettings.text) > -1) ++ { ++ QString trimmedTextFilterSettingsText = m_textFilterSettings.text; ++ bool canConverse = false; ++ double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse); ++ ++ if (canConverse) ++ { ++ if (fabs(info.aspectRatio() - ratio) < 0.1) ++ textMatch = true; ++ } ++ } ++ } ++ ++ // Image Pixel Size ++ // See bug #341053 for details. ++ ++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize) ++ { ++ QSize size = info.dimensions(); ++ int pixelSize = size.height()*size.width(); ++ QString text = m_textFilterSettings.text; ++ ++ if(text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && pixelSize > (text.remove(0,1)).toInt()) ++ { ++ textMatch = true; ++ } ++ else if(text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && pixelSize < (text.remove(0,1)).toInt()) ++ { ++ textMatch = true; ++ } ++ else if(text.contains(QRegExp(QLatin1String("^\\d+$"))) && pixelSize == text.toInt()) ++ { ++ textMatch = true; ++ } ++ } ++ ++ match &= textMatch; ++ ++ if (foundText) ++ { ++ *foundText = textMatch; ++ } ++ } ++ ++ // -- filter by URL-whitelists ------------------------------------------------ ++ // NOTE: whitelists are always AND for now. ++ ++ if (match) ++ { ++ const QUrl url = info.fileUrl(); ++ ++ for (QHash>::const_iterator it = m_urlWhitelists.constBegin(); ++ it!=m_urlWhitelists.constEnd(); ++it) ++ { ++ match = it->contains(url); ++ ++ if (!match) ++ { ++ break; ++ } ++ } ++ } ++ ++ if (match) ++ { ++ const qlonglong id = info.id(); ++ ++ for (QHash >::const_iterator it = m_idWhitelists.constBegin(); ++ it!=m_idWhitelists.constEnd(); ++it) ++ { ++ match = it->contains(id); ++ ++ if (!match) ++ { ++ break; ++ } ++ } ++ } ++ ++ return match; ++} ++ ++// ------------------------------------------------------------------------------------------------- ++ ++VersionImageFilterSettings::VersionImageFilterSettings() ++{ ++ m_includeTagFilter = 0; ++ m_exceptionTagFilter = 0; ++} ++ ++VersionImageFilterSettings::VersionImageFilterSettings(const VersionManagerSettings& settings) ++{ ++ setVersionManagerSettings(settings); ++} ++ ++bool VersionImageFilterSettings::operator==(const VersionImageFilterSettings& other) const ++{ ++ return m_excludeTagFilter == other.m_excludeTagFilter && ++ m_exceptionLists == other.m_exceptionLists; ++} ++ ++bool VersionImageFilterSettings::matches(const ImageInfo& info) const ++{ ++ if (!isFiltering()) ++ { ++ return true; ++ } ++ ++ const qlonglong id = info.id(); ++ ++ for (QHash >::const_iterator it = m_exceptionLists.constBegin(); ++ it != m_exceptionLists.constEnd(); ++it) ++ { ++ if (it->contains(id)) ++ { ++ return true; ++ } ++ } ++ ++ bool match = true; ++ QList tagIds = info.tagIds(); ++ ++ if (!tagIds.contains(m_includeTagFilter)) ++ { ++ for (QList::const_iterator it = m_excludeTagFilter.begin(); ++ it != m_excludeTagFilter.end(); ++it) ++ { ++ if (tagIds.contains(*it)) ++ { ++ match = false; ++ break; ++ } ++ } ++ } ++ ++ if (!match) ++ { ++ if (tagIds.contains(m_exceptionTagFilter)) ++ { ++ match = true; ++ } ++ } ++ ++ return match; ++} ++ ++bool VersionImageFilterSettings::isHiddenBySettings(const ImageInfo& info) const ++{ ++ QList tagIds = info.tagIds(); ++ ++ foreach(int tagId, m_excludeTagFilter) ++ { ++ if (tagIds.contains(tagId)) ++ { ++ return true; ++ } ++ } ++ ++ return false; ++} ++ ++bool VersionImageFilterSettings::isExemptedBySettings(const ImageInfo& info) const ++{ ++ return info.tagIds().contains(m_exceptionTagFilter); ++} ++ ++void VersionImageFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings) ++{ ++ m_excludeTagFilter.clear(); ++ ++ if (!settings.enabled) ++ { ++ return; ++ } ++ ++ if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal)) ++ { ++ m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); ++ } ++ ++ if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates)) ++ { ++ m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion()); ++ } ++ ++ m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion()); ++ m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible()); ++} ++ ++void VersionImageFilterSettings::setExceptionList(const QList& idList, const QString& id) ++{ ++ if (idList.isEmpty()) ++ { ++ m_exceptionLists.remove(id); ++ } ++ else ++ { ++ m_exceptionLists.insert(id, idList); ++ } ++} ++ ++bool VersionImageFilterSettings::isFiltering() const ++{ ++ return !m_excludeTagFilter.isEmpty(); ++} ++ ++bool VersionImageFilterSettings::isFilteringByTags() const ++{ ++ return isFiltering(); ++} ++ ++// ------------------------------------------------------------------------------------------------- ++ ++GroupImageFilterSettings::GroupImageFilterSettings() ++ : m_allOpen(false) ++{ ++} ++ ++bool GroupImageFilterSettings::operator==(const GroupImageFilterSettings& other) const ++{ ++ return (m_allOpen == other.m_allOpen && ++ m_openGroups == other.m_openGroups); ++} ++ ++bool GroupImageFilterSettings::matches(const ImageInfo& info) const ++{ ++ if (m_allOpen) ++ { ++ return true; ++ } ++ ++ if (info.isGrouped()) ++ { ++ return m_openGroups.contains(info.groupImage().id()); ++ } ++ return true; ++} ++ ++void GroupImageFilterSettings::setOpen(qlonglong group, bool open) ++{ ++ if (open) ++ { ++ m_openGroups << group; ++ } ++ else ++ { ++ m_openGroups.remove(group); ++ } ++} ++ ++bool GroupImageFilterSettings::isOpen(qlonglong group) const ++{ ++ return m_openGroups.contains(group); ++} ++ ++void GroupImageFilterSettings::setAllOpen(bool open) ++{ ++ m_allOpen = open; ++} ++ ++bool GroupImageFilterSettings::isAllOpen() const ++{ ++ return m_allOpen; ++} ++ ++bool GroupImageFilterSettings::isFiltering() const ++{ ++ return !m_allOpen; ++} ++ ++DatabaseFields::Set GroupImageFilterSettings::watchFlags() const ++{ ++ return DatabaseFields::ImageRelations; ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagefiltersettings.h b/libs/database/models/imagefiltersettings.h +new file mode 100644 +index 0000000..0e7beae +--- /dev/null ++++ b/libs/database/models/imagefiltersettings.h +@@ -0,0 +1,349 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Filter values for use with ImageFilterModel ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011-2017 by Gilles Caulier ++ * Copyright (C) 2010 by Andi Clemens ++ * Copyright (C) 2011 by Michael G. Hansen ++ * Copyright (C) 2014 by Mohamed Anwer ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGEFILTERSETTINGS_H ++#define IMAGEFILTERSETTINGS_H ++ ++// Qt includes ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// Local includes ++ ++#include "searchtextbar.h" ++#include "mimefilter.h" ++#include "digikam_export.h" ++ ++namespace Digikam ++{ ++ ++class ImageInfo; ++class VersionManagerSettings; ++ ++namespace DatabaseFields ++{ ++ class Set; ++} ++ ++// --------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT SearchTextFilterSettings : public SearchTextSettings ++{ ++ ++public: ++ ++ enum TextFilterFields ++ { ++ None = 0x00, ++ ImageName = 0x01, ++ ImageTitle = 0x02, ++ ImageComment = 0x04, ++ TagName = 0x08, ++ AlbumName = 0x10, ++ ImageAspectRatio = 0x20, ++ ImagePixelSize = 0x40, ++ All = ImageName | ImageTitle | ImageComment | TagName | AlbumName | ImageAspectRatio | ImagePixelSize ++ }; ++ ++public: ++ ++ SearchTextFilterSettings() ++ { ++ textFields = None; ++ } ++ ++ explicit SearchTextFilterSettings(const SearchTextSettings& settings) ++ { ++ caseSensitive = settings.caseSensitive; ++ text = settings.text; ++ textFields = None; ++ } ++ ++ TextFilterFields textFields; ++}; ++ ++// --------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT ImageFilterSettings ++{ ++public: ++ ++ ImageFilterSettings(); ++ ++ /** ++ * Returns true if the given ImageInfo matches the filter criteria. ++ * Optionally, foundText is set to true if it matched by text search. ++ */ ++ bool matches(const ImageInfo& info, bool* const foundText = 0) const; ++ ++public: ++ ++ /// --- Tags filter --- ++ ++ /// Possible logical matching condition used to sort tags id. ++ enum MatchingCondition ++ { ++ OrCondition, ++ AndCondition ++ }; ++ ++ void setTagFilter(const QList& includedTags, ++ const QList& excludedTags, ++ MatchingCondition matchingCond, ++ bool showUnTagged, ++ const QList& clTagIds, ++ const QList& plTagIds); ++ ++public: ++ ++ /// --- Rating filter --- ++ ++ /// Possible conditions used to filter rating: >=, =, <= ++ enum RatingCondition ++ { ++ GreaterEqualCondition, ++ EqualCondition, ++ LessEqualCondition ++ }; ++ ++ void setRatingFilter(int rating, RatingCondition ratingCond, bool isUnratedExcluded); ++ ++public: ++ ++ /// --- Date filter --- ++ void setDayFilter(const QList& days); ++ ++public: ++ ++ /// --- Text filter --- ++ void setTextFilter(const SearchTextFilterSettings& settings); ++ void setTagNames(const QHash& tagNameHash); ++ void setAlbumNames(const QHash& albumNameHash); ++ ++public: ++ ++ /// --- Mime filter --- ++ void setMimeTypeFilter(int mimeTypeFilter); ++ ++public: ++ ++ /// --- Geolocation filter ++ enum GeolocationCondition ++ { ++ GeolocationNoFilter = 0, ++ GeolocationNoCoordinates = 1 << 1, ++ GeolocationHasCoordinates = 1 << 2 ++ }; ++ ++ void setGeolocationFilter(const GeolocationCondition& condition); ++ ++public: ++ ++ /// Returns if the day is a filter criteria ++ bool isFilteringByDay() const; ++ ++ /// Returns if the type mime is a filter criteria ++ bool isFilteringByTypeMime() const; ++ ++ /// Returns whether geolocation is a filter criteria ++ bool isFilteringByGeolocation() const; ++ ++ /// Returns if the rating is a filter criteria ++ bool isFilteringByRating() const; ++ ++ /// Returns if the pick labels is a filter criteria ++ bool isFilteringByPickLabels() const; ++ ++ /// Returns if the color labels is a filter criteria ++ bool isFilteringByColorLabels() const; ++ ++ /// Returns if the tag is a filter criteria ++ bool isFilteringByTags() const; ++ ++ /// Returns if the text (including comment) is a filter criteria ++ bool isFilteringByText() const; ++ ++ /// Returns if images will be filtered by these criteria at all ++ bool isFiltering() const; ++ ++public: ++ ++ /// --- URL whitelist filter ++ void setUrlWhitelist(const QList& urlList, const QString& id); ++ ++public: ++ ++ /// --- ID whitelist filter ++ void setIdWhitelist(const QList& idList, const QString& id); ++ ++public: ++ ++ /// --- Change notification --- ++ ++ /** Returns database fields a change in which would affect the current filtering. ++ * To find out if an image tag change affects filtering, test isFilteringByTags(). ++ * The text filter will also be affected by changes in tags and album names. ++ */ ++ DatabaseFields::Set watchFlags() const; ++ ++private: ++ ++ /** ++ * @brief Returns whether some internal filtering (whitelist by id or URL) or normal filtering is going on ++ */ ++ bool isFilteringInternally() const; ++ ++private: ++ ++ /// --- Tags filter --- ++ bool m_untaggedFilter; ++ QList m_includeTagFilter; ++ QList m_excludeTagFilter; ++ MatchingCondition m_matchingCond; ++ QList m_colorLabelTagFilter; ++ QList m_pickLabelTagFilter; ++ ++ /// --- Rating filter --- ++ int m_ratingFilter; ++ RatingCondition m_ratingCond; ++ bool m_isUnratedExcluded; ++ ++ /// --- Date filter --- ++ QMap m_dayFilter; ++ ++ /// --- Text filter --- ++ SearchTextFilterSettings m_textFilterSettings; ++ ++ /// Helpers for text search: Set these if you want to search album or tag names with text search ++ QHash m_tagNameHash; ++ QHash m_albumNameHash; ++ ++ /// --- Mime filter --- ++ MimeFilter::TypeMimeFilter m_mimeTypeFilter; ++ ++ /// --- Geolocation filter ++ GeolocationCondition m_geolocationCondition; ++ ++ /// --- URL whitelist filter ++ QHash> m_urlWhitelists; ++ ++ /// --- ID whitelist filter ++ QHash > m_idWhitelists; ++}; ++ ++// --------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT VersionImageFilterSettings ++{ ++public: ++ ++ VersionImageFilterSettings(); ++ explicit VersionImageFilterSettings(const VersionManagerSettings& settings); ++ ++ bool operator==(const VersionImageFilterSettings& other) const; ++ ++ /** ++ * Returns true if the given ImageInfo matches the filter criteria. ++ */ ++ bool matches(const ImageInfo& info) const; ++ ++ bool isHiddenBySettings(const ImageInfo& info) const; ++ bool isExemptedBySettings(const ImageInfo& info) const; ++ ++ /// --- Tags filter --- ++ ++ void setVersionManagerSettings(const VersionManagerSettings& settings); ++ ++ /** ++ * Add list with exceptions: These images will be exempted from filtering by this filter ++ */ ++ void setExceptionList(const QList& idlist, const QString& id); ++ ++ /// Returns if images will be filtered by these criteria at all ++ bool isFiltering() const; ++ ++ /// Returns if the tag is a filter criteria ++ bool isFilteringByTags() const; ++ ++ /// DatabaseFields::Set watchFlags() const: Would return 0 ++ ++protected: ++ ++ QList m_excludeTagFilter; ++ int m_includeTagFilter; ++ int m_exceptionTagFilter; ++ QHash > m_exceptionLists; ++}; ++ ++// --------------------------------------------------------------------------------------- ++ ++class DIGIKAM_DATABASE_EXPORT GroupImageFilterSettings ++{ ++public: ++ ++ GroupImageFilterSettings(); ++ ++ bool operator==(const GroupImageFilterSettings& other) const; ++ ++ /** ++ * Returns true if the given ImageInfo matches the filter criteria. ++ */ ++ bool matches(const ImageInfo& info) const; ++ ++ /** ++ * Open or close a group. ++ */ ++ void setOpen(qlonglong group, bool open); ++ bool isOpen(qlonglong group) const; ++ ++ /** ++ * Open all groups ++ */ ++ void setAllOpen(bool open); ++ bool isAllOpen() const; ++ ++ /// Returns if images will be filtered by these criteria at all ++ bool isFiltering() const; ++ ++ DatabaseFields::Set watchFlags() const; ++ ++protected: ++ ++ bool m_allOpen; ++ QSet m_openGroups; ++}; ++ ++} // namespace Digikam ++ ++Q_DECLARE_METATYPE(Digikam::ImageFilterSettings::GeolocationCondition) ++ ++#endif // IMAGEFILTERSETTINGS_H +diff --git a/libs/database/models/imagelistmodel.cpp b/libs/database/models/imagelistmodel.cpp +new file mode 100644 +index 0000000..fafce34 +--- /dev/null ++++ b/libs/database/models/imagelistmodel.cpp +@@ -0,0 +1,70 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2010-12-06 ++ * Description : An image model based on a static list ++ * ++ * Copyright (C) 2010-2011 by Marcel Wiesweg ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagelistmodel.h" ++ ++// Local includes ++ ++#include "digikam_debug.h" ++#include "coredbaccess.h" ++#include "coredbchangesets.h" ++#include "coredbwatch.h" ++#include "imageinfo.h" ++#include "imageinfolist.h" ++ ++namespace Digikam ++{ ++ ++ImageListModel::ImageListModel(QObject* parent) ++ : ImageThumbnailModel(parent) ++{ ++ connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)), ++ this, SLOT(slotCollectionImageChange(CollectionImageChangeset))); ++} ++ ++ImageListModel::~ImageListModel() ++{ ++} ++ ++void ImageListModel::slotCollectionImageChange(const CollectionImageChangeset& changeset) ++{ ++ if (isEmpty()) ++ { ++ return; ++ } ++ ++ switch (changeset.operation()) ++ { ++ case CollectionImageChangeset::Added: ++ break; ++ case CollectionImageChangeset::Removed: ++ case CollectionImageChangeset::RemovedAll: ++ removeImageInfos(ImageInfoList(changeset.ids())); ++ break; ++ ++ default: ++ break; ++ } ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagelistmodel.h b/libs/database/models/imagelistmodel.h +new file mode 100644 +index 0000000..a225b1b +--- /dev/null ++++ b/libs/database/models/imagelistmodel.h +@@ -0,0 +1,63 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2010-12-06 ++ * Description : An image model based on a static list ++ * ++ * Copyright (C) 2010-2011 by Marcel Wiesweg ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGELISTMODEL_H ++#define IMAGELISTMODEL_H ++ ++// Local includes ++ ++#include "imagethumbnailmodel.h" ++#include "digikam_export.h" ++ ++namespace Digikam ++{ ++ ++class ImageChangeset; ++class CollectionImageChangeset; ++ ++class DIGIKAM_DATABASE_EXPORT ImageListModel : public ImageThumbnailModel ++{ ++ Q_OBJECT ++ ++public: ++ ++ explicit ImageListModel(QObject* parent = 0); ++ ~ImageListModel(); ++ ++ // NOTE: necessary methods to add and remove ImageInfos to the model are inherited from ImageModel ++ ++Q_SIGNALS: ++ ++ /** ++ * Emitted when images are removed from the model because they are removed in the database ++ */ ++ void imageInfosRemoved(const QList& infos); ++ ++protected Q_SLOTS: ++ ++ void slotCollectionImageChange(const CollectionImageChangeset& changeset); ++}; ++ ++} // namespace Digikam ++ ++#endif // IMAGELISTMODEL_H +diff --git a/libs/database/models/imagemodel.cpp b/libs/database/models/imagemodel.cpp +new file mode 100644 +index 0000000..41b43cf +--- /dev/null ++++ b/libs/database/models/imagemodel.cpp +@@ -0,0 +1,1368 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagemodel.h" ++ ++// Qt includes ++ ++#include ++#include ++ ++// Local includes ++ ++#include "digikam_debug.h" ++#include "coredbchangesets.h" ++#include "coredbfields.h" ++#include "coredbwatch.h" ++#include "imageinfo.h" ++#include "imageinfolist.h" ++#include "abstractitemdragdrophandler.h" ++ ++namespace Digikam ++{ ++ ++class ImageModel::Private ++{ ++public: ++ ++ Private() ++ { ++ preprocessor = 0; ++ keepFilePathCache = false; ++ sendRemovalSignals = false; ++ incrementalUpdater = 0; ++ refreshing = false; ++ reAdding = false; ++ incrementalRefreshRequested = false; ++ } ++ ++ ImageInfoList infos; ++ QList extraValues; ++ QHash idHash; ++ ++ bool keepFilePathCache; ++ QHash filePathHash; ++ ++ bool sendRemovalSignals; ++ ++ QObject* preprocessor; ++ bool refreshing; ++ bool reAdding; ++ bool incrementalRefreshRequested; ++ ++ DatabaseFields::Set watchFlags; ++ ++ class ImageModelIncrementalUpdater* incrementalUpdater; ++ ++ ImageInfoList pendingInfos; ++ QList pendingExtraValues; ++ ++ inline bool isValid(const QModelIndex& index) ++ { ++ if (!index.isValid()) ++ { ++ return false; ++ } ++ ++ if (index.row() < 0 || index.row() >= infos.size()) ++ { ++ qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index; ++ return false; ++ } ++ ++ return true; ++ } ++ inline bool extraValueValid(const QModelIndex& index) ++ { ++ // we assume isValid() being called before, no duplicate checks ++ if (index.row() >= extraValues.size()) ++ { ++ qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index; ++ return false; ++ } ++ ++ return true; ++ } ++}; ++ ++typedef QPair IntPair; // to make foreach macro happy ++typedef QList IntPairList; ++ ++class ImageModelIncrementalUpdater ++{ ++public: ++ ++ explicit ImageModelIncrementalUpdater(ImageModel::Private* d); ++ ++ void appendInfos(const QList& infos, const QList& extraValues); ++ void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved); ++ QList oldIndexes(); ++ ++ static QList toContiguousPairs(const QList& ids); ++ ++public: ++ ++ QHash oldIds; ++ QList oldExtraValues; ++ QList newInfos; ++ QList newExtraValues; ++ QList modelRemovals; ++}; ++ ++ImageModel::ImageModel(QObject* parent) ++ : QAbstractListModel(parent), ++ d(new Private) ++{ ++ connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), ++ this, SLOT(slotImageChange(ImageChangeset))); ++ ++ connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), ++ this, SLOT(slotImageTagChange(ImageTagChangeset))); ++} ++ ++ImageModel::~ImageModel() ++{ ++ delete d->incrementalUpdater; ++ delete d; ++} ++ ++// ------------ Access methods ------------- ++ ++void ImageModel::setKeepsFilePathCache(bool keepCache) ++{ ++ d->keepFilePathCache = keepCache; ++} ++ ++bool ImageModel::keepsFilePathCache() const ++{ ++ return d->keepFilePathCache; ++} ++ ++bool ImageModel::isEmpty() const ++{ ++ return d->infos.isEmpty(); ++} ++ ++void ImageModel::setWatchFlags(const DatabaseFields::Set& set) ++{ ++ d->watchFlags = set; ++} ++ ++ImageInfo ImageModel::imageInfo(const QModelIndex& index) const ++{ ++ if (!d->isValid(index)) ++ { ++ return ImageInfo(); ++ } ++ ++ return d->infos.at(index.row()); ++} ++ ++ImageInfo& ImageModel::imageInfoRef(const QModelIndex& index) const ++{ ++ return d->infos[index.row()]; ++} ++ ++qlonglong ImageModel::imageId(const QModelIndex& index) const ++{ ++ if (!d->isValid(index)) ++ { ++ return 0; ++ } ++ ++ return d->infos.at(index.row()).id(); ++} ++ ++QList ImageModel::imageInfos(const QList& indexes) const ++{ ++ QList infos; ++ ++ foreach(const QModelIndex& index, indexes) ++ { ++ infos << imageInfo(index); ++ } ++ ++ return infos; ++} ++ ++QList ImageModel::imageIds(const QList& indexes) const ++{ ++ QList ids; ++ ++ foreach(const QModelIndex& index, indexes) ++ { ++ ids << imageId(index); ++ } ++ ++ return ids; ++} ++ ++ImageInfo ImageModel::imageInfo(int row) const ++{ ++ if (row >= d->infos.size()) ++ { ++ return ImageInfo(); ++ } ++ ++ return d->infos.at(row); ++} ++ ++ImageInfo& ImageModel::imageInfoRef(int row) const ++{ ++ return d->infos[row]; ++} ++ ++qlonglong ImageModel::imageId(int row) const ++{ ++ if (row < 0 || row >= d->infos.size()) ++ { ++ return -1; ++ } ++ ++ return d->infos.at(row).id(); ++} ++ ++QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info) const ++{ ++ return indexForImageId(info.id()); ++} ++ ++QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const ++{ ++ return indexForImageId(info.id(), extraValue); ++} ++ ++QList ImageModel::indexesForImageInfo(const ImageInfo& info) const ++{ ++ return indexesForImageId(info.id()); ++} ++ ++QModelIndex ImageModel::indexForImageId(qlonglong id) const ++{ ++ int index = d->idHash.value(id, -1); ++ ++ if (index != -1) ++ { ++ return createIndex(index, 0); ++ } ++ ++ return QModelIndex(); ++} ++ ++QModelIndex ImageModel::indexForImageId(qlonglong id, const QVariant& extraValue) const ++{ ++ if (d->extraValues.isEmpty()) ++ return indexForImageId(id); ++ ++ QHash::const_iterator it; ++ ++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) ++ { ++ if (d->extraValues.at(it.value()) == extraValue) ++ return createIndex(it.value(), 0); ++ } ++ ++ return QModelIndex(); ++} ++ ++QList ImageModel::indexesForImageId(qlonglong id) const ++{ ++ QList indexes; ++ QHash::const_iterator it; ++ ++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) ++ { ++ indexes << createIndex(it.value(), 0); ++ } ++ ++ return indexes; ++} ++ ++int ImageModel::numberOfIndexesForImageInfo(const ImageInfo& info) const ++{ ++ return numberOfIndexesForImageId(info.id()); ++} ++ ++int ImageModel::numberOfIndexesForImageId(qlonglong id) const ++{ ++ if (d->extraValues.isEmpty()) ++ { ++ return 0; ++ } ++ ++ int count = 0; ++ QHash::const_iterator it; ++ ++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) ++ { ++ ++count; ++ } ++ ++ return count; ++} ++ ++// static method ++ImageInfo ImageModel::retrieveImageInfo(const QModelIndex& index) ++{ ++ if (!index.isValid()) ++ { ++ return ImageInfo(); ++ } ++ ++ ImageModel* const model = index.data(ImageModelPointerRole).value(); ++ int row = index.data(ImageModelInternalId).toInt(); ++ ++ if (!model) ++ { ++ return ImageInfo(); ++ } ++ ++ return model->imageInfo(row); ++} ++ ++// static method ++qlonglong ImageModel::retrieveImageId(const QModelIndex& index) ++{ ++ if (!index.isValid()) ++ { ++ return 0; ++ } ++ ++ ImageModel* const model = index.data(ImageModelPointerRole).value(); ++ int row = index.data(ImageModelInternalId).toInt(); ++ ++ if (!model) ++ { ++ return 0; ++ } ++ ++ return model->imageId(row); ++} ++ ++QModelIndex ImageModel::indexForPath(const QString& filePath) const ++{ ++ if (d->keepFilePathCache) ++ { ++ return indexForImageId(d->filePathHash.value(filePath)); ++ } ++ else ++ { ++ const int size = d->infos.size(); ++ ++ for (int i=0; iinfos.at(i).filePath() == filePath) ++ { ++ return createIndex(i, 0); ++ } ++ } ++ } ++ ++ return QModelIndex(); ++} ++ ++QList ImageModel::indexesForPath(const QString& filePath) const ++{ ++ if (d->keepFilePathCache) ++ { ++ return indexesForImageId(d->filePathHash.value(filePath)); ++ } ++ else ++ { ++ QList indexes; ++ const int size = d->infos.size(); ++ ++ for (int i=0; iinfos.at(i).filePath() == filePath) ++ { ++ indexes << createIndex(i, 0); ++ } ++ } ++ ++ return indexes; ++ } ++} ++ ++ImageInfo ImageModel::imageInfo(const QString& filePath) const ++{ ++ if (d->keepFilePathCache) ++ { ++ qlonglong id = d->filePathHash.value(filePath); ++ ++ if (id) ++ { ++ int index = d->idHash.value(id, -1); ++ ++ if (index != -1) ++ { ++ return d->infos.at(index); ++ } ++ } ++ } ++ else ++ { ++ foreach(const ImageInfo& info, d->infos) ++ { ++ if (info.filePath() == filePath) ++ { ++ return info; ++ } ++ } ++ } ++ ++ return ImageInfo(); ++} ++ ++QList ImageModel::imageInfos(const QString& filePath) const ++{ ++ QList infos; ++ ++ if (d->keepFilePathCache) ++ { ++ qlonglong id = d->filePathHash.value(filePath); ++ ++ if (id) ++ { ++ foreach(int index, d->idHash.values(id)) ++ { ++ infos << d->infos.at(index); ++ } ++ } ++ } ++ else ++ { ++ foreach(const ImageInfo& info, d->infos) ++ { ++ if (info.filePath() == filePath) ++ { ++ infos << info; ++ } ++ } ++ } ++ ++ return infos; ++} ++ ++void ImageModel::addImageInfo(const ImageInfo& info) ++{ ++ addImageInfos(QList() << info, QList()); ++} ++ ++void ImageModel::addImageInfos(const QList& infos) ++{ ++ addImageInfos(infos, QList()); ++} ++ ++void ImageModel::addImageInfos(const QList& infos, const QList& extraValues) ++{ ++ if (infos.isEmpty()) ++ { ++ return; ++ } ++ ++ if (d->incrementalUpdater) ++ { ++ d->incrementalUpdater->appendInfos(infos, extraValues); ++ } ++ else ++ { ++ appendInfos(infos, extraValues); ++ } ++} ++ ++void ImageModel::addImageInfoSynchronously(const ImageInfo& info) ++{ ++ addImageInfosSynchronously(QList() << info, QList()); ++} ++ ++void ImageModel::addImageInfosSynchronously(const QList& infos) ++{ ++ addImageInfos(infos, QList()); ++} ++ ++void ImageModel::addImageInfosSynchronously(const QList& infos, const QList& extraValues) ++{ ++ if (infos.isEmpty()) ++ { ++ return; ++ } ++ ++ publiciseInfos(infos, extraValues); ++ emit processAdded(infos, extraValues); ++} ++ ++void ImageModel::ensureHasImageInfo(const ImageInfo& info) ++{ ++ ensureHasImageInfos(QList() << info, QList()); ++} ++ ++void ImageModel::ensureHasImageInfos(const QList& infos) ++{ ++ ensureHasImageInfos(infos, QList()); ++} ++ ++void ImageModel::ensureHasImageInfos(const QList& infos, const QList& extraValues) ++{ ++ if (extraValues.isEmpty()) ++ { ++ if (!d->pendingExtraValues.isEmpty()) ++ { ++ qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; ++ return; ++ } ++ } ++ else ++ { ++ if (d->pendingInfos.size() != d->pendingExtraValues.size()) ++ { ++ qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; ++ return; ++ } ++ } ++ ++ d->pendingInfos << infos; ++ d->pendingExtraValues << extraValues; ++ cleanSituationChecks(); ++} ++ ++void ImageModel::clearImageInfos() ++{ ++ d->infos.clear(); ++ d->extraValues.clear(); ++ d->idHash.clear(); ++ d->filePathHash.clear(); ++ delete d->incrementalUpdater; ++ d->incrementalUpdater = 0; ++ d->pendingInfos.clear(); ++ d->pendingExtraValues.clear(); ++ d->refreshing = false; ++ d->reAdding = false; ++ d->incrementalRefreshRequested = false; ++ ++ beginResetModel(); ++ endResetModel(); ++ ++ imageInfosCleared(); ++} ++ ++void ImageModel::setImageInfos(const QList& infos) ++{ ++ clearImageInfos(); ++ addImageInfos(infos); ++} ++ ++QList ImageModel::imageInfos() const ++{ ++ return d->infos; ++} ++ ++QList ImageModel::imageIds() const ++{ ++ return d->idHash.keys(); ++} ++ ++bool ImageModel::hasImage(qlonglong id) const ++{ ++ return d->idHash.contains(id); ++} ++ ++bool ImageModel::hasImage(const ImageInfo& info) const ++{ ++ return d->idHash.contains(info.id()); ++} ++ ++bool ImageModel::hasImage(const ImageInfo& info, const QVariant& extraValue) const ++{ ++ return hasImage(info.id(), extraValue); ++} ++ ++bool ImageModel::hasImage(qlonglong id, const QVariant& extraValue) const ++{ ++ if (d->extraValues.isEmpty()) ++ return hasImage(id); ++ ++ QHash::const_iterator it; ++ ++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) ++ { ++ if (d->extraValues.at(it.value()) == extraValue) ++ return true; ++ } ++ ++ return false;; ++} ++ ++QList ImageModel::uniqueImageInfos() const ++{ ++ if (d->extraValues.isEmpty()) ++ { ++ return d->infos; ++ } ++ ++ QList uniqueInfos; ++ const int size = d->infos.size(); ++ ++ for (int i=0; iinfos.at(i); ++ ++ if (d->idHash.value(info.id()) == i) ++ { ++ uniqueInfos << info; ++ } ++ } ++ ++ return uniqueInfos; ++} ++ ++void ImageModel::emitDataChangedForAll() ++{ ++ if (d->infos.isEmpty()) ++ { ++ return; ++ } ++ ++ QModelIndex first = createIndex(0, 0); ++ QModelIndex last = createIndex(d->infos.size() - 1, 0); ++ emit dataChanged(first, last); ++} ++ ++void ImageModel::emitDataChangedForSelection(const QItemSelection& selection) ++{ ++ if (!selection.isEmpty()) ++ { ++ foreach(const QItemSelectionRange& range, selection) ++ { ++ emit dataChanged(range.topLeft(), range.bottomRight()); ++ } ++ } ++} ++ ++void ImageModel::ensureHasGroupedImages(const ImageInfo& groupLeader) ++{ ++ ensureHasImageInfos(groupLeader.groupedImages()); ++} ++ ++// ------------ Preprocessing ------------- ++ ++void ImageModel::setPreprocessor(QObject* preprocessor) ++{ ++ unsetPreprocessor(d->preprocessor); ++ d->preprocessor = preprocessor; ++} ++ ++void ImageModel::unsetPreprocessor(QObject* preprocessor) ++{ ++ if (preprocessor && d->preprocessor == preprocessor) ++ { ++ disconnect(this, SIGNAL(preprocess(QList,QList)), 0, 0); ++ disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList,QList))); ++ disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished())); ++ } ++} ++ ++void ImageModel::appendInfos(const QList& infos, const QList& extraValues) ++{ ++ if (infos.isEmpty()) ++ { ++ return; ++ } ++ ++ if (d->preprocessor) ++ { ++ d->reAdding = true; ++ emit preprocess(infos, extraValues); ++ } ++ else ++ { ++ publiciseInfos(infos, extraValues); ++ } ++} ++ ++void ImageModel::appendInfosChecked(const QList& infos, const QList& extraValues) ++{ ++ // This method does deduplication. It is private because in context of readding or refreshing it is of no use. ++ ++ if (extraValues.isEmpty()) ++ { ++ QList checkedInfos; ++ ++ foreach (const ImageInfo& info, infos) ++ { ++ if (!hasImage(info)) ++ { ++ checkedInfos << info; ++ } ++ } ++ ++ appendInfos(checkedInfos, QList()); ++ } ++ else ++ { ++ QList checkedInfos; ++ QList checkedExtraValues; ++ const int size = infos.size(); ++ ++ for (int i=0; i& infos, const QList& extraValues) ++{ ++ // addImageInfos -> appendInfos -> preprocessor -> reAddImageInfos ++ publiciseInfos(infos, extraValues); ++} ++ ++void ImageModel::reAddingFinished() ++{ ++ d->reAdding = false; ++ cleanSituationChecks(); ++} ++ ++void ImageModel::startRefresh() ++{ ++ d->refreshing = true; ++} ++ ++void ImageModel::finishRefresh() ++{ ++ d->refreshing = false; ++ cleanSituationChecks(); ++} ++ ++bool ImageModel::isRefreshing() const ++{ ++ return d->refreshing; ++} ++ ++void ImageModel::cleanSituationChecks() ++{ ++ // For starting an incremental refresh we want a clear situation: ++ // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(), ++ // any batches sent to preprocessor for re-adding have been re-added. ++ if (d->refreshing || d->reAdding) ++ { ++ return; ++ } ++ ++ if (!d->pendingInfos.isEmpty()) ++ { ++ appendInfosChecked(d->pendingInfos, d->pendingExtraValues); ++ d->pendingInfos.clear(); ++ d->pendingExtraValues.clear(); ++ cleanSituationChecks(); ++ return; ++ } ++ ++ if (d->incrementalRefreshRequested) ++ { ++ d->incrementalRefreshRequested = false; ++ emit readyForIncrementalRefresh(); ++ } ++ else ++ { ++ emit allRefreshingFinished(); ++ } ++} ++ ++void ImageModel::publiciseInfos(const QList& infos, const QList& extraValues) ++{ ++ if (infos.isEmpty()) ++ { ++ return; ++ } ++ ++ Q_ASSERT(infos.size() == extraValues.size() || (extraValues.isEmpty() && d->extraValues.isEmpty())); ++ ++ emit imageInfosAboutToBeAdded(infos); ++ const int firstNewIndex = d->infos.size(); ++ const int lastNewIndex = d->infos.size() + infos.size() - 1; ++ beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex); ++ d->infos << infos; ++ d->extraValues << extraValues; ++ ++ for (int i=firstNewIndex; i<=lastNewIndex; ++i) ++ { ++ const ImageInfo& info = d->infos.at(i); ++ qlonglong id = info.id(); ++ d->idHash.insertMulti(id, i); ++ ++ if (d->keepFilePathCache) ++ { ++ d->filePathHash[info.filePath()] = id; ++ } ++ } ++ ++ endInsertRows(); ++ emit imageInfosAdded(infos); ++} ++ ++void ImageModel::requestIncrementalRefresh() ++{ ++ if (d->reAdding) ++ { ++ d->incrementalRefreshRequested = true; ++ } ++ else ++ { ++ emit readyForIncrementalRefresh(); ++ } ++} ++ ++bool ImageModel::hasIncrementalRefreshPending() const ++{ ++ return d->incrementalRefreshRequested; ++} ++ ++void ImageModel::startIncrementalRefresh() ++{ ++ delete d->incrementalUpdater; ++ ++ d->incrementalUpdater = new ImageModelIncrementalUpdater(d); ++} ++ ++void ImageModel::finishIncrementalRefresh() ++{ ++ if (!d->incrementalUpdater) ++ { ++ return; ++ } ++ ++ // remove old entries ++ QList > pairs = d->incrementalUpdater->oldIndexes(); ++ removeRowPairs(pairs); ++ ++ // add new indexes ++ appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues); ++ ++ delete d->incrementalUpdater; ++ d->incrementalUpdater = 0; ++} ++ ++void ImageModel::removeIndex(const QModelIndex& index) ++{ ++ removeIndexes(QList() << index); ++} ++ ++void ImageModel::removeIndexes(const QList& indexes) ++{ ++ QList listIndexes; ++ ++ foreach(const QModelIndex& index, indexes) ++ { ++ if (d->isValid(index)) ++ { ++ listIndexes << index.row(); ++ } ++ } ++ ++ if (listIndexes.isEmpty()) ++ { ++ return; ++ } ++ ++ removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); ++} ++ ++void ImageModel::removeImageInfo(const ImageInfo& info) ++{ ++ removeImageInfos(QList() << info); ++} ++ ++void ImageModel::removeImageInfos(const QList& infos) ++{ ++ QList listIndexes; ++ ++ foreach(const ImageInfo& info, infos) ++ { ++ QModelIndex index = indexForImageId(info.id()); ++ ++ if (index.isValid()) ++ { ++ listIndexes << index.row(); ++ } ++ } ++ removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); ++} ++ ++void ImageModel::removeImageInfos(const QList& infos, const QList& extraValues) ++{ ++ if (extraValues.isEmpty()) ++ { ++ removeImageInfos(infos); ++ return; ++ } ++ ++ QList listIndexes; ++ ++ for (int i=0; isendRemovalSignals = send; ++} ++ ++template ++static bool pairsContain(const List& list, T value) ++{ ++ typename List::const_iterator middle; ++ typename List::const_iterator begin = list.begin(); ++ typename List::const_iterator end = list.end(); ++ int n = int(end - begin); ++ int half; ++ ++ while (n > 0) ++ { ++ half = n >> 1; ++ middle = begin + half; ++ ++ if (middle->first <= value && middle->second >= value) ++ { ++ return true; ++ } ++ else if (middle->second < value) ++ { ++ begin = middle + 1; ++ n -= half + 1; ++ } ++ else ++ { ++ n = half; ++ } ++ } ++ ++ return false; ++} ++ ++void ImageModel::removeRowPairsWithCheck(const QList >& toRemove) ++{ ++ if (d->incrementalUpdater) ++ { ++ d->incrementalUpdater->aboutToBeRemovedInModel(toRemove); ++ } ++ ++ removeRowPairs(toRemove); ++} ++ ++void ImageModel::removeRowPairs(const QList >& toRemove) ++{ ++ if (toRemove.isEmpty()) ++ { ++ return; ++ } ++ ++ // Remove old indexes ++ // Keep in mind that when calling beginRemoveRows all structures announced to be removed ++ // must still be valid, and this includes our hashes as well, which limits what we can optimize ++ ++ int removedRows = 0, offset = 0; ++ typedef QPair IntPair; // to make foreach macro happy ++ ++ foreach(const IntPair& pair, toRemove) ++ { ++ const int begin = pair.first - offset; ++ const int end = pair.second - offset; // inclusive ++ removedRows = end - begin + 1; ++ ++ // when removing from the list, all subsequent indexes are affected ++ offset += removedRows; ++ ++ QList removedInfos; ++ ++ if (d->sendRemovalSignals) ++ { ++ qCopy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin()); ++ emit imageInfosAboutToBeRemoved(removedInfos); ++ } ++ ++ imageInfosAboutToBeRemoved(begin, end); ++ beginRemoveRows(QModelIndex(), begin, end); ++ ++ // update idHash - which points to indexes of d->infos, and these change now! ++ QHash::iterator it; ++ ++ for (it = d->idHash.begin(); it != d->idHash.end(); ) ++ { ++ if (it.value() >= begin) ++ { ++ if (it.value() > end) ++ { ++ // after the removed interval: adjust index ++ it.value() -= removedRows; ++ } ++ else ++ { ++ // in the removed interval ++ it = d->idHash.erase(it); ++ continue; ++ } ++ } ++ ++ ++it; ++ } ++ ++ // remove from list ++ d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1)); ++ ++ if (!d->extraValues.isEmpty()) ++ { ++ d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1)); ++ } ++ ++ endRemoveRows(); ++ ++ if (d->sendRemovalSignals) ++ { ++ emit imageInfosRemoved(removedInfos); ++ } ++ } ++ ++ // tidy up: remove old indexes from file path hash now ++ if (d->keepFilePathCache) ++ { ++ QHash::iterator it; ++ ++ for (it = d->filePathHash.begin(); it != d->filePathHash.end(); ) ++ { ++ if (pairsContain(toRemove, it.value())) ++ { ++ it = d->filePathHash.erase(it); ++ } ++ else ++ { ++ ++it; ++ } ++ } ++ } ++} ++ ++ImageModelIncrementalUpdater::ImageModelIncrementalUpdater(ImageModel::Private* d) ++{ ++ oldIds = d->idHash; ++ oldExtraValues = d->extraValues; ++} ++ ++void ImageModelIncrementalUpdater::appendInfos(const QList& infos, const QList& extraValues) ++{ ++ if (extraValues.isEmpty()) ++ { ++ foreach(const ImageInfo& info, infos) ++ { ++ QHash::iterator it = oldIds.find(info.id()); ++ ++ if (it != oldIds.end()) ++ { ++ oldIds.erase(it); ++ } ++ else ++ { ++ newInfos << info; ++ } ++ } ++ } ++ else ++ { ++ for (int i=0; i::iterator it; ++ ++ for (it = oldIds.find(info.id()); it != oldIds.end() && it.key() == info.id(); ++it) ++ { ++ // first check is for bug #262596. Not sure if needed. ++ if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value())) ++ { ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) ++ { ++ oldIds.erase(it); ++ // do not erase from oldExtraValues - oldIds is a hash id -> index. ++ } ++ else ++ { ++ newInfos << info; ++ newExtraValues << extraValues.at(i); ++ } ++ } ++ } ++} ++ ++void ImageModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove) ++{ ++ modelRemovals << toRemove; ++} ++ ++QList > ImageModelIncrementalUpdater::oldIndexes() ++{ ++ // first, apply all changes to indexes by direct removal in model ++ // while the updater was active ++ foreach(const IntPairList& list, modelRemovals) ++ { ++ int removedRows = 0, offset = 0; ++ ++ foreach(const IntPair& pair, list) ++ { ++ const int begin = pair.first - offset; ++ const int end = pair.second - offset; // inclusive ++ removedRows = end - begin + 1; ++ ++ // when removing from the list, all subsequent indexes are affected ++ offset += removedRows; ++ ++ // update idHash - which points to indexes of d->infos, and these change now! ++ QHash::iterator it; ++ ++ for (it = oldIds.begin(); it != oldIds.end(); ) ++ { ++ if (it.value() >= begin) ++ { ++ if (it.value() > end) ++ { ++ // after the removed interval: adjust index ++ it.value() -= removedRows; ++ } ++ else ++ { ++ // in the removed interval ++ it = oldIds.erase(it); ++ continue; ++ } ++ } ++ ++ ++it; ++ } ++ } ++ } ++ ++ modelRemovals.clear(); ++ ++ return toContiguousPairs(oldIds.values()); ++} ++ ++QList > ImageModelIncrementalUpdater::toContiguousPairs(const QList& unsorted) ++{ ++ // Take the given indices and return them as contiguous pairs [begin, end] ++ ++ QList > pairs; ++ ++ if (unsorted.isEmpty()) ++ { ++ return pairs; ++ } ++ ++ QList indices(unsorted); ++ qSort(indices); ++ ++ QPair pair(indices.first(), indices.first()); ++ ++ for (int i=1; iisValid(index)) ++ { ++ return QVariant(); ++ } ++ ++ switch (role) ++ { ++ case Qt::DisplayRole: ++ case Qt::ToolTipRole: ++ return d->infos.at(index.row()).name(); ++ ++ case ImageModelPointerRole: ++ return QVariant::fromValue(const_cast(this)); ++ ++ case ImageModelInternalId: ++ return index.row(); ++ ++ case CreationDateRole: ++ return d->infos.at(index.row()).dateTime(); ++ ++ case ExtraDataRole: ++ ++ if (d->extraValueValid(index)) ++ { ++ return d->extraValues.at(index.row()); ++ } ++ else ++ { ++ return QVariant(); ++ } ++ ++ case ExtraDataDuplicateCount: ++ { ++ qlonglong id = d->infos.at(index.row()).id(); ++ return numberOfIndexesForImageId(id); ++ } ++ } ++ ++ return QVariant(); ++} ++ ++QVariant ImageModel::headerData(int section, Qt::Orientation orientation, int role) const ++{ ++ Q_UNUSED(section) ++ Q_UNUSED(orientation) ++ Q_UNUSED(role) ++ return QVariant(); ++} ++ ++int ImageModel::rowCount(const QModelIndex& parent) const ++{ ++ if (parent.isValid()) ++ { ++ return 0; ++ } ++ ++ return d->infos.size(); ++} ++ ++Qt::ItemFlags ImageModel::flags(const QModelIndex& index) const ++{ ++ if (!d->isValid(index)) ++ { ++ return 0; ++ } ++ ++ Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; ++ ++ f |= dragDropFlags(index); ++ ++ return f; ++} ++ ++QModelIndex ImageModel::index(int row, int column, const QModelIndex& parent) const ++{ ++ if (column != 0 || row < 0 || parent.isValid() || row >= d->infos.size()) ++ { ++ return QModelIndex(); ++ } ++ ++ return createIndex(row, 0); ++} ++ ++// ------------ Database watch ------------- ++ ++void ImageModel::slotImageChange(const ImageChangeset& changeset) ++{ ++ if (d->infos.isEmpty()) ++ { ++ return; ++ } ++ ++ if (d->watchFlags & changeset.changes()) ++ { ++ QItemSelection items; ++ ++ foreach(const qlonglong& id, changeset.ids()) ++ { ++ QModelIndex index = indexForImageId(id); ++ ++ if (index.isValid()) ++ { ++ items.select(index, index); ++ } ++ } ++ ++ if (!items.isEmpty()) ++ { ++ emitDataChangedForSelection(items); ++ emit imageChange(changeset, items); ++ } ++ } ++} ++ ++void ImageModel::slotImageTagChange(const ImageTagChangeset& changeset) ++{ ++ if (d->infos.isEmpty()) ++ { ++ return; ++ } ++ ++ QItemSelection items; ++ ++ foreach(const qlonglong& id, changeset.ids()) ++ { ++ QModelIndex index = indexForImageId(id); ++ ++ if (index.isValid()) ++ { ++ items.select(index, index); ++ } ++ } ++ ++ if (!items.isEmpty()) ++ { ++ emitDataChangedForSelection(items); ++ emit imageTagChange(changeset, items); ++ } ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagemodel.h b/libs/database/models/imagemodel.h +new file mode 100644 +index 0000000..dcf94c2 +--- /dev/null ++++ b/libs/database/models/imagemodel.h +@@ -0,0 +1,364 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGEMODEL_H ++#define IMAGEMODEL_H ++ ++// Qt includes ++ ++#include ++ ++// Local includes ++ ++#include "dragdropimplementations.h" ++#include "imageinfo.h" ++#include "digikam_export.h" ++ ++class QItemSelection; ++ ++namespace Digikam ++{ ++ ++class ImageChangeset; ++class ImageTagChangeset; ++ ++namespace DatabaseFields ++{ ++class Set; ++} ++ ++class DIGIKAM_DATABASE_EXPORT ImageModel : public QAbstractListModel, public DragDropModelImplementation ++{ ++ Q_OBJECT ++ ++public: ++ ++ enum ImageModelRoles ++ { ++ /// An ImageModel* pointer to this model ++ ImageModelPointerRole = Qt::UserRole, ++ ImageModelInternalId = Qt::UserRole + 1, ++ /// Returns a thumbnail pixmap. May be implemented by subclasses. ++ /// Returns either a valid pixmap or a null QVariant. ++ ThumbnailRole = Qt::UserRole + 2, ++ /// Returns a QDateTime with the creation date ++ CreationDateRole = Qt::UserRole + 3, ++ /// Return (optional) extraData field ++ ExtraDataRole = Qt::UserRole + 5, ++ /// Returns the number of duplicate indexes for the same image id ++ ExtraDataDuplicateCount = Qt::UserRole + 6, ++ ++ // Roles which are defined here but not implemented by ImageModel ++ /// Returns position of item in Left Light Table preview. ++ LTLeftPanelRole = Qt::UserRole + 50, ++ /// Returns position of item in Right Light Table preview. ++ LTRightPanelRole = Qt::UserRole + 51, ++ ++ // For use by subclasses ++ SubclassRoles = Qt::UserRole + 100, ++ // For use by filter models ++ FilterModelRoles = Qt::UserRole + 500 ++ }; ++ ++public: ++ ++ explicit ImageModel(QObject* parent = 0); ++ ~ImageModel(); ++ ++ /** If a cache is kept, lookup by file path is fast, ++ * without a cache it is O(n). Default is false. ++ */ ++ void setKeepsFilePathCache(bool keepCache); ++ bool keepsFilePathCache() const; ++ ++ /** Set a set of database fields to watch. ++ * If either of these is changed, dataChanged() will be emitted. ++ * Default is no flag (no signal will be emitted). ++ */ ++ void setWatchFlags(const DatabaseFields::Set& set); ++ ++ /** Returns the ImageInfo object, reference or image id from the underlying data ++ * pointed to by the index. ++ * If the index is not valid, imageInfo will return a null ImageInfo, imageId will ++ * return 0, imageInfoRef must not be called with an invalid index. ++ */ ++ ImageInfo imageInfo(const QModelIndex& index) const; ++ ImageInfo& imageInfoRef(const QModelIndex& index) const; ++ qlonglong imageId(const QModelIndex& index) const; ++ QList imageInfos(const QList& indexes) const; ++ QList imageIds(const QList& indexes) const; ++ ++ /** Returns the ImageInfo object, reference or image id from the underlying data ++ * of the given row (parent is the invalid QModelIndex, column is 0). ++ * Note that imageInfoRef will crash if index is invalid. ++ */ ++ ImageInfo imageInfo(int row) const; ++ ImageInfo& imageInfoRef(int row) const; ++ qlonglong imageId(int row) const; ++ ++ /** Return the index for the given ImageInfo or id, if contained in this model. ++ */ ++ QModelIndex indexForImageInfo(const ImageInfo& info) const; ++ QModelIndex indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const; ++ QModelIndex indexForImageId(qlonglong id) const; ++ QModelIndex indexForImageId(qlonglong id, const QVariant& extraValue) const; ++ QList indexesForImageInfo(const ImageInfo& info) const; ++ QList indexesForImageId(qlonglong id) const; ++ ++ int numberOfIndexesForImageInfo(const ImageInfo& info) const; ++ int numberOfIndexesForImageId(qlonglong id) const; ++ ++ /** Returns the index or ImageInfo object from the underlying data ++ * for the given file path. This is fast if keepsFilePathCache is enabled. ++ * The file path is as returned by ImageInfo.filePath(). ++ * In case of multiple occurrences of the same file, the simpler variants return ++ * any one found first, use the QList methods to retrieve all occurrences. ++ */ ++ QModelIndex indexForPath(const QString& filePath) const; ++ ImageInfo imageInfo(const QString& filePath) const; ++ QList indexesForPath(const QString& filePath) const; ++ QList imageInfos(const QString& filePath) const; ++ ++ /** Main entry point for subclasses adding image infos to the model. ++ * If you list entries not unique per image id, you must add an extraValue ++ * so that every entry is unique by imageId and extraValues. ++ * Please note that these methods do not prevent addition of duplicate entries. ++ */ ++ void addImageInfo(const ImageInfo& info); ++ void addImageInfos(const QList& infos); ++ void addImageInfos(const QList& infos, const QList& extraValues); ++ ++ /** Clears image infos and resets model. ++ */ ++ void clearImageInfos(); ++ ++ /** Clears and adds the infos. ++ */ ++ void setImageInfos(const QList& infos); ++ ++ /** ++ * Directly remove the given indexes or infos from the model. ++ */ ++ void removeIndex(const QModelIndex& indexes); ++ void removeIndexes(const QList& indexes); ++ void removeImageInfo(const ImageInfo& info); ++ void removeImageInfos(const QList& infos); ++ void removeImageInfos(const QList& infos, const QList& extraValues); ++ ++ /** ++ * addImageInfo() is asynchronous if a prepocessor is set. ++ * This method first adds the info, synchronously. ++ * Only afterwards, the preprocessor will have the opportunity to process it. ++ * This method also bypasses any incremental updates. ++ * Please note that these methods do not prevent addition of duplicate entries. ++ */ ++ void addImageInfoSynchronously(const ImageInfo& info); ++ void addImageInfosSynchronously(const QList& infos); ++ void addImageInfosSynchronously(const QList& infos, const QList& extraValues); ++ ++ /** ++ * Add the given entries. Method returns immediately, the ++ * addition may happen later asynchronously. ++ * These methods prevent the addition of duplicate entries. ++ */ ++ void ensureHasImageInfo(const ImageInfo& info); ++ void ensureHasImageInfos(const QList& infos); ++ void ensureHasImageInfos(const QList& infos, const QList& extraValues); ++ ++ /** ++ * Ensure that all images grouped on the given leader are contained in the model. ++ */ ++ void ensureHasGroupedImages(const ImageInfo& groupLeader); ++ ++ QList imageInfos() const; ++ QList imageIds() const; ++ QList uniqueImageInfos() const; ++ ++ bool hasImage(qlonglong id) const; ++ bool hasImage(const ImageInfo& info) const; ++ bool hasImage(const ImageInfo& info, const QVariant& extraValue) const; ++ bool hasImage(qlonglong id, const QVariant& extraValue) const; ++ ++ bool isEmpty() const; ++ ++ // Drag and Drop ++ DECLARE_MODEL_DRAG_DROP_METHODS ++ ++ /** ++ * Install an object as a preprocessor for ImageInfos added to this model. ++ * For every QList of ImageInfos added to addImageInfo, the signal preprocess() ++ * will be emitted. The preprocessor may process the items and shall then readd ++ * them by calling reAddImageInfos(). It may take some time to process. ++ * It shall discard any held infos when the modelReset() signal is sent. ++ * It shall call readdFinished() when no reset occurred and all infos on the way have been readded. ++ * This means that only after calling this method, you shall make three connections ++ * (preprocess -> your slot, your signal -> reAddImageInfos, your signal -> reAddingFinished) ++ * and make or already hold a connection modelReset() -> your slot. ++ * There is only one preprocessor at a time, a previously set object will be disconnected. ++ */ ++ void setPreprocessor(QObject* processor); ++ void unsetPreprocessor(QObject* processor); ++ ++ /** ++ * Returns true if this model is currently refreshing. ++ * For a preprocessor this means that, although the preprocessor may currently have ++ * processed all it got, more batches are to be expected. ++ */ ++ bool isRefreshing() const; ++ ++ /** ++ * Enable sending of imageInfosAboutToBeRemoved and imageInfosRemoved signals. ++ * Default: false ++ */ ++ void setSendRemovalSignals(bool send); ++ ++ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; ++ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; ++ virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; ++ virtual Qt::ItemFlags flags(const QModelIndex& index) const; ++ virtual QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const; ++ ++ /** Retrieves the imageInfo object from the data() method of the given index. ++ * The index may be from a QSortFilterProxyModel as long as an ImageModel is at the end. */ ++ static ImageInfo retrieveImageInfo(const QModelIndex& index); ++ static qlonglong retrieveImageId(const QModelIndex& index); ++ ++Q_SIGNALS: ++ ++ /** Informs that ImageInfos will be added to the model. ++ * This signal is sent before the model data is changed and views are informed. ++ */ ++ void imageInfosAboutToBeAdded(const QList& infos); ++ ++ /** Informs that ImageInfos have been added to the model. ++ * This signal is sent after the model data is changed and views are informed. ++ */ ++ void imageInfosAdded(const QList& infos); ++ ++ /** Informs that ImageInfos will be removed from the model. ++ * This signal is sent before the model data is changed and views are informed. ++ * Note: You need to explicitly enable sending of this signal. It is not sent ++ * in clearImageInfos(). ++ */ ++ void imageInfosAboutToBeRemoved(const QList& infos); ++ ++ /** Informs that ImageInfos have been removed from the model. ++ * This signal is sent after the model data is changed and views are informed. * ++ * Note: You need to explicitly enable sending of this signal. It is not sent ++ * in clearImageInfos(). ++ */ ++ void imageInfosRemoved(const QList& infos); ++ ++ /** Connect to this signal only if you are the current preprocessor. ++ */ ++ void preprocess(const QList& infos, const QList&); ++ void processAdded(const QList& infos, const QList&); ++ ++ /** If an ImageChangeset affected indexes of this model with changes as set in watchFlags(), ++ * this signal contains the changeset and the affected indexes. ++ */ ++ void imageChange(const ImageChangeset&, const QItemSelection&); ++ ++ /** If an ImageTagChangeset affected indexes of this model, ++ * this signal contains the changeset and the affected indexes. ++ */ ++ void imageTagChange(const ImageTagChangeset&, const QItemSelection&); ++ ++ /** Signals that the model is right now ready to start an incremental refresh. ++ * This is guaranteed only for the scope of emitting this signal. ++ */ ++ void readyForIncrementalRefresh(); ++ ++ /** Signals that the model has finished currently with all scheduled ++ * refreshing, full or incremental, and all preprocessing. ++ * The model is in polished, clean situation right now. ++ */ ++ void allRefreshingFinished(); ++ ++public Q_SLOTS: ++ ++ void reAddImageInfos(const QList& infos, const QList& extraValues); ++ void reAddingFinished(); ++ ++protected: ++ ++ /** Subclasses that add ImageInfos in batches shall call startRefresh() ++ * when they start sending batches and finishRefresh() when they have finished. ++ * No incremental refreshes will be started while listing. ++ * A clearImageInfos() always stops listing, calling finishRefresh() is then not necessary. ++ */ ++ void startRefresh(); ++ void finishRefresh(); ++ ++ /** As soon as the model is ready to start an incremental refresh, the signal ++ * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline ++ * if the model is ready right now. ++ */ ++ void requestIncrementalRefresh(); ++ bool hasIncrementalRefreshPending() const; ++ ++ /** Starts an incremental refresh operation. You shall only call this method from a slot ++ * connected to readyForIncrementalRefresh(). To initiate an incremental refresh, ++ * call requestIncrementalRefresh(). ++ */ ++ void startIncrementalRefresh(); ++ void finishIncrementalRefresh(); ++ ++ void emitDataChangedForAll(); ++ void emitDataChangedForSelection(const QItemSelection& selection); ++ ++ // Called when the internal storage is cleared ++ virtual void imageInfosCleared() {}; ++ ++ // Called before rowsAboutToBeRemoved ++ virtual void imageInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {}; ++ ++protected Q_SLOTS: ++ ++ virtual void slotImageChange(const ImageChangeset& changeset); ++ virtual void slotImageTagChange(const ImageTagChangeset& changeset); ++ ++private: ++ ++ void appendInfos(const QList& infos, const QList& extraValues); ++ void appendInfosChecked(const QList& infos, const QList& extraValues); ++ void publiciseInfos(const QList& infos, const QList& extraValues); ++ void cleanSituationChecks(); ++ void removeRowPairsWithCheck(const QList >& toRemove); ++ void removeRowPairs(const QList >& toRemove); ++ ++public: ++ ++ // Declared public because it's used in ImageModelIncrementalUpdater class ++ class Private; ++ ++private: ++ ++ Private* const d; ++}; ++ ++} // namespace Digikam ++ ++Q_DECLARE_METATYPE(Digikam::ImageModel*) ++ ++#endif // IMAGEMODEL_H +diff --git a/libs/database/models/imagesortsettings.cpp b/libs/database/models/imagesortsettings.cpp +new file mode 100644 +index 0000000..39ee6e1 +--- /dev/null ++++ b/libs/database/models/imagesortsettings.cpp +@@ -0,0 +1,400 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Filter values for use with ImageFilterModel ++ * ++ * Copyright (C) 2009 by Marcel Wiesweg ++ * Copyright (C) 2014 by Mohamed Anwer ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagesortsettings.h" ++ ++// Qt includes ++ ++#include ++#include ++ ++// Local includes ++ ++#include "coredbfields.h" ++#include "imageinfo.h" ++ ++namespace Digikam ++{ ++ ++ImageSortSettings::ImageSortSettings() ++{ ++ categorizationMode = NoCategories; ++ categorizationSortOrder = DefaultOrder; ++ categorizationCaseSensitivity = Qt::CaseSensitive; ++ sortRole = SortByFileName; ++ sortOrder = DefaultOrder; ++ strTypeNatural = true; ++ sortCaseSensitivity = Qt::CaseSensitive; ++ currentCategorizationSortOrder = Qt::AscendingOrder; ++ currentSortOrder = Qt::AscendingOrder; ++} ++ ++bool ImageSortSettings::operator==(const ImageSortSettings& other) const ++{ ++ return ++ categorizationMode == other.categorizationMode && ++ categorizationSortOrder == other.categorizationSortOrder && ++ categorizationCaseSensitivity == other.categorizationCaseSensitivity && ++ sortRole == other.sortRole && ++ sortOrder == other.sortOrder && ++ sortCaseSensitivity == other.sortCaseSensitivity; ++} ++ ++void ImageSortSettings::setCategorizationMode(CategorizationMode mode) ++{ ++ categorizationMode = mode; ++ ++ if (categorizationSortOrder == DefaultOrder) ++ { ++ currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); ++ } ++} ++ ++void ImageSortSettings::setCategorizationSortOrder(SortOrder order) ++{ ++ categorizationSortOrder = order; ++ ++ if (categorizationSortOrder == DefaultOrder) ++ { ++ currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); ++ } ++ else ++ { ++ currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder; ++ } ++} ++ ++void ImageSortSettings::setSortRole(SortRole role) ++{ ++ sortRole = role; ++ ++ if (sortOrder == DefaultOrder) ++ { ++ currentSortOrder = defaultSortOrderForSortRole(sortRole); ++ } ++} ++ ++void ImageSortSettings::setSortOrder(SortOrder order) ++{ ++ sortOrder = order; ++ ++ if (sortOrder == DefaultOrder) ++ { ++ currentSortOrder = defaultSortOrderForSortRole(sortRole); ++ } ++ else ++ { ++ currentSortOrder = (Qt::SortOrder)order; ++ } ++} ++ ++void ImageSortSettings::setStringTypeNatural(bool natural) ++{ ++ strTypeNatural = natural; ++} ++ ++Qt::SortOrder ImageSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode) ++{ ++ switch (mode) ++ { ++ case NoCategories: ++ case OneCategory: ++ case CategoryByAlbum: ++ case CategoryByFormat: ++ default: ++ return Qt::AscendingOrder; ++ } ++} ++ ++Qt::SortOrder ImageSortSettings::defaultSortOrderForSortRole(SortRole role) ++{ ++ switch (role) ++ { ++ case SortByFileName: ++ case SortByFilePath: ++ return Qt::AscendingOrder; ++ case SortByFileSize: ++ return Qt::DescendingOrder; ++ case SortByModificationDate: ++ case SortByCreationDate: ++ return Qt::AscendingOrder; ++ case SortByRating: ++ case SortByImageSize: ++ return Qt::DescendingOrder; ++ case SortByAspectRatio: ++ return Qt::DescendingOrder; ++ case SortBySimilarity: ++ return Qt::DescendingOrder; ++ default: ++ return Qt::AscendingOrder; ++ } ++} ++ ++int ImageSortSettings::compareCategories(const ImageInfo& left, const ImageInfo& right) const ++{ ++ switch (categorizationMode) ++ { ++ case NoCategories: ++ case OneCategory: ++ return 0; ++ case CategoryByAlbum: ++ { ++ int leftAlbum = left.albumId(); ++ int rightAlbum = right.albumId(); ++ ++ // return comparation result ++ if (leftAlbum == rightAlbum) ++ { ++ return 0; ++ } ++ else if (lessThanByOrder(leftAlbum, rightAlbum, currentCategorizationSortOrder)) ++ { ++ return -1; ++ } ++ else ++ { ++ return 1; ++ } ++ } ++ case CategoryByFormat: ++ { ++ return naturalCompare(left.format(), right.format(), ++ currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural); ++ } ++ default: ++ return 0; ++ } ++} ++ ++bool ImageSortSettings::lessThan(const ImageInfo& left, const ImageInfo& right) const ++{ ++ int result = compare(left, right, sortRole); ++ ++ if (result != 0) ++ { ++ return result < 0; ++ } ++ ++ // are they identical? ++ if (left == right) ++ { ++ return false; ++ } ++ ++ // If left and right equal for first sort order, use a hierarchy of all sort orders ++ if ( (result = compare(left, right, SortByFileName)) != 0) ++ { ++ return result < 0; ++ } ++ ++ if ( (result = compare(left, right, SortByCreationDate)) != 0) ++ { ++ return result < 0; ++ } ++ ++ if ( (result = compare(left, right, SortByModificationDate)) != 0) ++ { ++ return result < 0; ++ } ++ ++ if ( (result = compare(left, right, SortByFilePath)) != 0) ++ { ++ return result < 0; ++ } ++ ++ if ( (result = compare(left, right, SortByFileSize)) != 0) ++ { ++ return result < 0; ++ } ++ ++ if ( (result = compare(left, right, SortBySimilarity)) != 0) ++ { ++ return result < 0; ++ } ++ ++ return false; ++} ++ ++int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right) const ++{ ++ return compare(left, right, sortRole); ++} ++ ++int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right, SortRole role) const ++{ ++ switch (role) ++ { ++ case SortByFileName: ++ { ++ bool versioning = (left.name().contains(QLatin1String("_v"), Qt::CaseInsensitive) || ++ right.name().contains(QLatin1String("_v"), Qt::CaseInsensitive)); ++ return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural, versioning); ++ } ++ case SortByFilePath: ++ return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural); ++ case SortByFileSize: ++ return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder); ++ case SortByModificationDate: ++ return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder); ++ case SortByCreationDate: ++ return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder); ++ case SortByRating: ++ // I have the feeling that inverting the sort order for rating is the natural order ++ return - compareByOrder(left.rating(), right.rating(), currentSortOrder); ++ case SortByImageSize: ++ { ++ QSize leftSize = left.dimensions(); ++ QSize rightSize = right.dimensions(); ++ int leftPixels = leftSize.width() * leftSize.height(); ++ int rightPixels = rightSize.width() * rightSize.height(); ++ return compareByOrder(leftPixels, rightPixels, currentSortOrder); ++ } ++ case SortByAspectRatio: ++ { ++ QSize leftSize = left.dimensions(); ++ QSize rightSize = right.dimensions(); ++ int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000; ++ int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000; ++ return compareByOrder(leftAR, rightAR, currentSortOrder); ++ } ++ case SortBySimilarity: ++ { ++ qlonglong leftReferenceImageId = left.currentReferenceImage(); ++ qlonglong rightReferenceImageId = right.currentReferenceImage(); ++ // make sure that the original image has always the highest similarity. ++ double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity(); ++ double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity(); ++ return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder); ++ } ++ default: ++ return 1; ++ } ++} ++ ++bool ImageSortSettings::lessThan(const QVariant& left, const QVariant& right) const ++{ ++ if (left.type() != right.type()) ++ { ++ return false; ++ } ++ ++ switch (left.type()) ++ { ++ case QVariant::Int: ++ return compareByOrder(left.toInt(), right.toInt(), currentSortOrder); ++ case QVariant::UInt: ++ return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder); ++ case QVariant::LongLong: ++ return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder); ++ case QVariant::ULongLong: ++ return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder); ++ case QVariant::Double: ++ return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder); ++ case QVariant::Date: ++ return compareByOrder(left.toDate(), right.toDate(), currentSortOrder); ++ case QVariant::DateTime: ++ return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder); ++ case QVariant::Time: ++ return compareByOrder(left.toTime(), right.toTime(), currentSortOrder); ++ case QVariant::Rect: ++ case QVariant::RectF: ++ { ++ QRectF rectLeft = left.toRectF(); ++ QRectF rectRight = right.toRectF(); ++ int result; ++ ++ if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0) ++ { ++ return result < 0; ++ } ++ ++ if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0) ++ { ++ return result < 0; ++ } ++ ++ QSizeF sizeLeft = rectLeft.size(), sizeRight = rectRight.size(); ++ ++ if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0) ++ { ++ return result < 0; ++ } ++ // FIXME: fall through?? If not, add "break" here ++ } ++ default: ++ return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural); ++ } ++} ++ ++DatabaseFields::Set ImageSortSettings::watchFlags() const ++{ ++ DatabaseFields::Set set; ++ ++ switch (sortRole) ++ { ++ case SortByFileName: ++ set |= DatabaseFields::Name; ++ break; ++ case SortByFilePath: ++ set |= DatabaseFields::Name; ++ break; ++ case SortByFileSize: ++ set |= DatabaseFields::FileSize; ++ break; ++ case SortByModificationDate: ++ set |= DatabaseFields::ModificationDate; ++ break; ++ case SortByCreationDate: ++ set |= DatabaseFields::CreationDate; ++ break; ++ case SortByRating: ++ set |= DatabaseFields::Rating; ++ break; ++ case SortByImageSize: ++ set |= DatabaseFields::Width | DatabaseFields::Height; ++ break; ++ case SortByAspectRatio: ++ set |= DatabaseFields::Width | DatabaseFields::Height; ++ break; ++ case SortBySimilarity: ++ // TODO: Not sure what to do here.... ++ set |= DatabaseFields::Name; ++ break; ++ } ++ ++ switch (categorizationMode) ++ { ++ case NoCategories: ++ case OneCategory: ++ case CategoryByAlbum: ++ break; ++ case CategoryByFormat: ++ set |= DatabaseFields::Format; ++ break; ++ } ++ ++ return set; ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagesortsettings.h b/libs/database/models/imagesortsettings.h +new file mode 100644 +index 0000000..2a5fd8c +--- /dev/null ++++ b/libs/database/models/imagesortsettings.h +@@ -0,0 +1,225 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-05-31 ++ * Description : Sort settings for use with ImageFilterModel ++ * ++ * Copyright (C) 2009 by Marcel Wiesweg ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGESORTSETTINGS_H ++#define IMAGESORTSETTINGS_H ++ ++// Qt includes ++ ++#include ++#include ++#include ++#include ++#include ++ ++// Local includes ++ ++#include "digikam_export.h" ++ ++namespace Digikam ++{ ++ ++class ImageInfo; ++ ++namespace DatabaseFields ++{ ++ class Set; ++} ++ ++class DIGIKAM_DATABASE_EXPORT ImageSortSettings ++{ ++public: ++ ++ ImageSortSettings(); ++ ++ bool operator==(const ImageSortSettings& other) const; ++ ++ /** Compares the categories of left and right. ++ * Return -1 if left is less than right, 0 if both fall in the same category, ++ * and 1 if left is greater than right. ++ * Adheres to set categorization mode and current category sort order. ++ */ ++ int compareCategories(const ImageInfo& left, const ImageInfo& right) const; ++ ++ /** Returns true if left is less than right. ++ * Adheres to current sort role and sort order. ++ */ ++ bool lessThan(const ImageInfo& left, const ImageInfo& right) const; ++ ++ /** Compares the ImageInfos left and right. ++ * Return -1 if left is less than right, 1 if left is greater than right, ++ * and 0 if left equals right comparing the current sort role's value. ++ * Adheres to set sort role and sort order. ++ */ ++ int compare(const ImageInfo& left, const ImageInfo& right) const; ++ ++ /** Returns true if left QVariant is less than right. ++ * Adheres to current sort role and sort order. ++ * Use for extraValue, if necessary. ++ */ ++ bool lessThan(const QVariant& left, const QVariant& right) const; ++ ++ enum SortOrder ++ { ++ AscendingOrder = Qt::AscendingOrder, ++ DescendingOrder = Qt::DescendingOrder, ++ DefaultOrder /// sort order depends on the chosen sort role ++ }; ++ ++ /// --- Categories --- ++ ++ enum CategorizationMode ++ { ++ NoCategories, /// categorization switched off ++ OneCategory, /// all items in one global category ++ CategoryByAlbum, ++ CategoryByFormat ++ }; ++ ++ CategorizationMode categorizationMode; ++ SortOrder categorizationSortOrder; ++ ++ void setCategorizationMode(CategorizationMode mode); ++ void setCategorizationSortOrder(SortOrder order); ++ ++ /// Only Ascending or Descending, never DefaultOrder ++ Qt::SortOrder currentCategorizationSortOrder; ++ Qt::CaseSensitivity categorizationCaseSensitivity; ++ ++ bool isCategorized() const { return categorizationMode >= CategoryByAlbum; } ++ ++ /// --- Image Sorting --- ++ ++ enum SortRole ++ { ++ // Note: For legacy reasons, the order of the first five entries must remain unchanged ++ SortByFileName, ++ SortByFilePath, ++ SortByCreationDate, ++ SortByFileSize, ++ SortByRating, ++ SortByModificationDate, ++ SortByImageSize, // pixel number ++ SortByAspectRatio, // width / height * 100000 ++ SortBySimilarity ++ }; ++ ++ SortRole sortRole; ++ SortOrder sortOrder; ++ bool strTypeNatural; ++ ++ void setSortRole(SortRole role); ++ void setSortOrder(SortOrder order); ++ void setStringTypeNatural(bool natural); ++ ++ Qt::SortOrder currentSortOrder; ++ Qt::CaseSensitivity sortCaseSensitivity; ++ ++ int compare(const ImageInfo& left, const ImageInfo& right, SortRole sortRole) const; ++ ++ // --- --- ++ ++ static Qt::SortOrder defaultSortOrderForCategorizationMode(CategorizationMode mode); ++ static Qt::SortOrder defaultSortOrderForSortRole(SortRole role); ++ ++ /// --- Change notification --- ++ ++ /** Returns database fields a change in which would affect the current sorting. ++ */ ++ DatabaseFields::Set watchFlags() const; ++ ++ /// --- Utilities --- ++ ++ /** Returns a < b if sortOrder is Ascending, or b < a if order is descending. ++ */ ++ template ++ static inline bool lessThanByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) ++ { ++ if (sortOrder == Qt::AscendingOrder) ++ { ++ return a < b; ++ } ++ else ++ { ++ return b < a; ++ } ++ } ++ ++ /** Returns the usual compare result of -1, 0, or 1 for lessThan, equals and greaterThan. ++ */ ++ template ++ static inline int compareValue(const T& a, const T& b) ++ { ++ if (a == b) ++ { ++ return 0; ++ } ++ ++ if (a < b) ++ { ++ return -1; ++ } ++ else ++ { ++ return 1; ++ } ++ } ++ ++ /** Takes a typical result from a compare method (0 is equal, -1 is less than, 1 is greater than) ++ * and applies the given sort order to it. ++ */ ++ static inline int compareByOrder(int compareResult, Qt::SortOrder sortOrder) ++ { ++ if (sortOrder == Qt::AscendingOrder) ++ { ++ return compareResult; ++ } ++ else ++ { ++ return - compareResult; ++ } ++ } ++ ++ template ++ static inline int compareByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) ++ { ++ return compareByOrder(compareValue(a, b), sortOrder); ++ } ++ ++ /** Compares the two string by natural comparison and adheres to given sort order ++ */ ++ static inline int naturalCompare(const QString& a, const QString& b, Qt::SortOrder sortOrder, ++ Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive, ++ bool natural = true, bool versioning = false) ++ { ++ QCollator collator; ++ collator.setNumericMode(natural); ++ collator.setIgnorePunctuation(versioning); ++ collator.setCaseSensitivity(caseSensitive); ++ return (compareByOrder(collator.compare(a, b), sortOrder)); ++ } ++}; ++ ++} // namespace Digikam ++ ++#endif // IMAGESORTSETTINGS_H +diff --git a/libs/database/models/imagethumbnailmodel.cpp b/libs/database/models/imagethumbnailmodel.cpp +new file mode 100644 +index 0000000..b7f5661 +--- /dev/null ++++ b/libs/database/models/imagethumbnailmodel.cpp +@@ -0,0 +1,323 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries with support for thumbnail loading ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011-2017 by Gilles Caulier ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imagethumbnailmodel.h" ++ ++// Qt includes ++ ++#include ++ ++// Local includes ++ ++#include "digikam_debug.h" ++#include "thumbnailloadthread.h" ++#include "digikam_export.h" ++#include "digikam_globals.h" ++ ++namespace Digikam ++{ ++ ++class ImageThumbnailModel::ImageThumbnailModelPriv ++{ ++public: ++ ++ ImageThumbnailModelPriv() : ++ thread(0), ++ preloadThread(0), ++ thumbSize(0), ++ lastGlobalThumbSize(0), ++ preloadThumbSize(0), ++ emitDataChanged(true) ++ { ++ staticListContainingThumbnailRole << ImageModel::ThumbnailRole; ++ } ++ ++ ThumbnailLoadThread* thread; ++ ThumbnailLoadThread* preloadThread; ++ ThumbnailSize thumbSize; ++ ThumbnailSize lastGlobalThumbSize; ++ ThumbnailSize preloadThumbSize; ++ QRect detailRect; ++ QVector staticListContainingThumbnailRole; ++ ++ bool emitDataChanged; ++ ++ int preloadThumbnailSize() const ++ { ++ if (preloadThumbSize.size()) ++ { ++ return preloadThumbSize.size(); ++ } ++ ++ return thumbSize.size(); ++ } ++}; ++ ++ImageThumbnailModel::ImageThumbnailModel(QObject* parent) ++ : ImageModel(parent), d(new ImageThumbnailModelPriv) ++{ ++ setKeepsFilePathCache(true); ++} ++ ++ImageThumbnailModel::~ImageThumbnailModel() ++{ ++ delete d->preloadThread; ++ delete d; ++} ++ ++void ImageThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) ++{ ++ d->thread = thread; ++ ++ connect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), ++ this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); ++} ++ ++ThumbnailLoadThread* ImageThumbnailModel::thumbnailLoadThread() const ++{ ++ return d->thread; ++} ++ ++ThumbnailSize ImageThumbnailModel::thumbnailSize() const ++{ ++ return d->thumbSize; ++} ++ ++void ImageThumbnailModel::setThumbnailSize(const ThumbnailSize& size) ++{ ++ d->lastGlobalThumbSize = size; ++ d->thumbSize = size; ++} ++ ++void ImageThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) ++{ ++ d->preloadThumbSize = size; ++} ++ ++void ImageThumbnailModel::setEmitDataChanged(bool emitSignal) ++{ ++ d->emitDataChanged = emitSignal; ++} ++ ++void ImageThumbnailModel::setPreloadThumbnails(bool preload) ++{ ++ if (preload) ++ { ++ if (!d->preloadThread) ++ { ++ d->preloadThread = new ThumbnailLoadThread; ++ d->preloadThread->setPixmapRequested(false); ++ d->preloadThread->setPriority(QThread::LowestPriority); ++ } ++ ++ connect(this, SIGNAL(allRefreshingFinished()), ++ this, SLOT(preloadAllThumbnails())); ++ } ++ else ++ { ++ delete d->preloadThread; ++ d->preloadThread = 0; ++ disconnect(this, SIGNAL(allRefreshingFinished()), ++ this, SLOT(preloadAllThumbnails())); ++ } ++} ++ ++void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare) ++{ ++ prepareThumbnails(indexesToPrepare, d->thumbSize); ++} ++ ++void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize) ++{ ++ if (!d->thread) ++ { ++ return; ++ } ++ ++ QList ids; ++ foreach(const QModelIndex& index, indexesToPrepare) ++ { ++ ids << imageInfoRef(index).thumbnailIdentifier(); ++ } ++ d->thread->findGroup(ids, thumbSize.size()); ++} ++ ++void ImageThumbnailModel::preloadThumbnails(const QList& infos) ++{ ++ if (!d->preloadThread) ++ { ++ return; ++ } ++ ++ QList ids; ++ foreach(const ImageInfo& info, infos) ++ { ++ ids << info.thumbnailIdentifier(); ++ } ++ d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); ++} ++ ++void ImageThumbnailModel::preloadThumbnails(const QList& indexesToPreload) ++{ ++ if (!d->preloadThread) ++ { ++ return; ++ } ++ ++ QList ids; ++ foreach(const QModelIndex& index, indexesToPreload) ++ { ++ ids << imageInfoRef(index).thumbnailIdentifier(); ++ } ++ d->preloadThread->stopAllTasks(); ++ d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); ++} ++ ++void ImageThumbnailModel::preloadAllThumbnails() ++{ ++ preloadThumbnails(imageInfos()); ++} ++ ++void ImageThumbnailModel::imageInfosCleared() ++{ ++ if (d->preloadThread) ++ { ++ d->preloadThread->stopAllTasks(); ++ } ++} ++ ++QVariant ImageThumbnailModel::data(const QModelIndex& index, int role) const ++{ ++ if (role == ThumbnailRole && d->thread && index.isValid()) ++ { ++ QPixmap thumbnail; ++ ImageInfo info = imageInfo(index); ++ QString path = info.filePath(); ++ ++ if (info.isNull()) ++ { ++ return QVariant(QVariant::Pixmap); ++ } ++ ++ if (!d->detailRect.isNull()) ++ { ++ if (d->thread->find(info.thumbnailIdentifier(), d->detailRect, thumbnail, d->thumbSize.size())) ++ { ++ return thumbnail; ++ } ++ } ++ else ++ { ++ if (d->thread->find(info.thumbnailIdentifier(), thumbnail, d->thumbSize.size())) ++ { ++ return thumbnail; ++ } ++ } ++ ++ return QVariant(QVariant::Pixmap); ++ } ++ ++ return ImageModel::data(index, role); ++} ++ ++bool ImageThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) ++{ ++ if (role == ThumbnailRole) ++ { ++ switch (value.type()) ++ { ++ case QVariant::Invalid: ++ d->thumbSize = d->lastGlobalThumbSize; ++ d->detailRect = QRect(); ++ break; ++ ++ case QVariant::Int: ++ ++ if (value.isNull()) ++ { ++ d->thumbSize = d->lastGlobalThumbSize; ++ } ++ else ++ { ++ d->thumbSize = value.toInt(); ++ } ++ break; ++ ++ case QVariant::Rect: ++ ++ if (value.isNull()) ++ { ++ d->detailRect = QRect(); ++ } ++ else ++ { ++ d->detailRect = value.toRect(); ++ } ++ break; ++ ++ default: ++ break; ++ } ++ } ++ ++ return ImageModel::setData(index, value, role); ++} ++ ++void ImageThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) ++{ ++ if (thumb.isNull()) ++ { ++ return; ++ } ++ ++ // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. ++ QModelIndexList indexes; ++ ThumbnailIdentifier thumbId = loadingDescription.thumbnailIdentifier(); ++ if (thumbId.filePath.isEmpty()) ++ { ++ indexes = indexesForImageId(thumbId.id); ++ } ++ else ++ { ++ indexes = indexesForPath(thumbId.filePath); ++ } ++ foreach(const QModelIndex& index, indexes) ++ { ++ if (thumb.isNull()) ++ { ++ emit thumbnailFailed(index, loadingDescription.previewParameters.size); ++ } ++ else ++ { ++ emit thumbnailAvailable(index, loadingDescription.previewParameters.size); ++ ++ if (d->emitDataChanged) ++ { ++ emit dataChanged(index, index, d->staticListContainingThumbnailRole); ++ } ++ } ++ } ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imagethumbnailmodel.h b/libs/database/models/imagethumbnailmodel.h +new file mode 100644 +index 0000000..366ca65 +--- /dev/null ++++ b/libs/database/models/imagethumbnailmodel.h +@@ -0,0 +1,140 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2009-03-05 ++ * Description : Qt item model for database entries with support for thumbnail loading ++ * ++ * Copyright (C) 2009-2011 by Marcel Wiesweg ++ * Copyright (C) 2011 by Gilles Caulier ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGETHUMBNAILMODEL_H ++#define IMAGETHUMBNAILMODEL_H ++ ++// Local includes ++ ++#include "imagemodel.h" ++#include "thumbnailsize.h" ++#include "digikam_export.h" ++ ++namespace Digikam ++{ ++ ++class LoadingDescription; ++class ThumbnailLoadThread; ++ ++class DIGIKAM_DATABASE_EXPORT ImageThumbnailModel : public ImageModel ++{ ++ Q_OBJECT ++ ++public: ++ ++ /** ++ * An ImageModel that supports thumbnail loading. ++ * You need to set a ThumbnailLoadThread to enable thumbnail loading. ++ * Adjust the thumbnail size to your needs. ++ * Note that setKeepsFilePathCache is enabled per default. ++ */ ++ explicit ImageThumbnailModel(QObject* parent); ++ ~ImageThumbnailModel(); ++ ++ /** Enable thumbnail loading and set the thread that shall be used. ++ * The thumbnail size of this thread will be adjusted. ++ */ ++ void setThumbnailLoadThread(ThumbnailLoadThread* thread); ++ ThumbnailLoadThread* thumbnailLoadThread() const; ++ ++ /// Set the thumbnail size to use ++ void setThumbnailSize(const ThumbnailSize& thumbSize); ++ ++ /// If you want to fix a size for preloading, do it here. ++ void setPreloadThumbnailSize(const ThumbnailSize& thumbSize); ++ ++ void setExifRotate(bool rotate); ++ ++ /** ++ * Enable emitting dataChanged() when a thumbnail becomes available. ++ * The thumbnailAvailable() signal will be emitted in any case. ++ * Default is true. ++ */ ++ void setEmitDataChanged(bool emitSignal); ++ ++ /** ++ * Enable preloading of thumbnails: ++ * If preloading is enabled, for every entry in the model a thumbnail generation is started. ++ * Default: false. ++ */ ++ void setPreloadThumbnails(bool preload); ++ ++ ThumbnailSize thumbnailSize() const; ++ ++ /** ++ * Handles the ThumbnailRole. ++ * If the pixmap is available, returns it in the QVariant. ++ * If it still needs to be loaded, returns a null QVariant and emits ++ * thumbnailAvailable() as soon as it is available. ++ */ ++ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; ++ ++ /** ++ * You can override the current thumbnail size by giving an integer value for ThumbnailRole. ++ * Set a null QVariant to use the thumbnail size set by setThumbnailSize() again. ++ * The index given here is ignored for this purpose. ++ */ ++ virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole); ++ ++public Q_SLOTS: ++ ++ /** Prepare the thumbnail loading for the given indexes ++ */ ++ void prepareThumbnails(const QList& indexesToPrepare); ++ void prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize); ++ ++ /** ++ * Preload thumbnail for the given infos resp. indexes. ++ * Note: Use setPreloadThumbnails to automatically preload all entries in the model. ++ * Note: This only ensures thumbnail generation. It is not guaranteed that pixmaps ++ * are stored in the cache. For thumbnails that are expect to be drawn immediately, ++ * include them in prepareThumbnails(). ++ * Note: Stops preloading of previously added thumbnails. ++ */ ++ void preloadThumbnails(const QList&); ++ void preloadThumbnails(const QList&); ++ void preloadAllThumbnails(); ++ ++Q_SIGNALS: ++ ++ void thumbnailAvailable(const QModelIndex& index, int requestedSize); ++ void thumbnailFailed(const QModelIndex& index, int requestedSize); ++ ++protected: ++ ++ virtual void imageInfosCleared(); ++ ++protected Q_SLOTS: ++ ++ void slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb); ++ ++private: ++ ++ class ImageThumbnailModelPriv; ++ ImageThumbnailModelPriv* const d; ++}; ++ ++} // namespace Digikam ++ ++#endif /* IMAGETHUMBNAILMODEL_H */ +diff --git a/libs/database/models/imageversionsmodel.cpp b/libs/database/models/imageversionsmodel.cpp +new file mode 100644 +index 0000000..e6ba582 +--- /dev/null ++++ b/libs/database/models/imageversionsmodel.cpp +@@ -0,0 +1,183 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2010-07-13 ++ * Description : Model for image versions ++ * ++ * Copyright (C) 2010 by Martin Klapetek ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#include "imageversionsmodel.h" ++ ++// KDE includes ++ ++#include ++ ++// Local includes ++ ++#include "digikam_debug.h" ++#include "workingwidget.h" ++ ++namespace Digikam ++{ ++ ++class ImageVersionsModel::Private ++{ ++public: ++ ++ Private() ++ { ++ data = 0; ++ paintTree = false; ++ } ++ ++ ///Complete paths with filenames and tree level ++ QList >* data; ++ ///This is for delegate to paint it as selected ++ QString currentSelectedImage; ++ ///If true, the delegate will paint items as a tree ++ ///if false, it will be painted as a list ++ bool paintTree; ++}; ++ ++ImageVersionsModel::ImageVersionsModel(QObject* parent) ++ : QAbstractListModel(parent), ++ d(new Private) ++{ ++ d->data = new QList >; ++} ++ ++ImageVersionsModel::~ImageVersionsModel() ++{ ++ //qDeleteAll(d->data); ++ delete d; ++} ++ ++Qt::ItemFlags ImageVersionsModel::flags(const QModelIndex& index) const ++{ ++ if (!index.isValid()) ++ { ++ return 0; ++ } ++ ++ return Qt::ItemIsEnabled | Qt::ItemIsSelectable; ++} ++ ++QVariant ImageVersionsModel::data(const QModelIndex& index, int role) const ++{ ++ if (!index.isValid()) ++ { ++ return QVariant(); ++ } ++ ++ if (role == Qt::DisplayRole && !d->data->isEmpty()) ++ { ++ return d->data->at(index.row()).first; ++ } ++ else if (role == Qt::UserRole && !d->data->isEmpty()) ++ { ++ return d->data->at(index.row()).second; ++ } ++ else if (role == Qt::DisplayRole && d->data->isEmpty()) ++ { ++ //TODO: make this text Italic ++ return QVariant(QString(i18n("No image selected"))); ++ } ++ ++ return QVariant(); ++} ++ ++int ImageVersionsModel::rowCount(const QModelIndex& parent) const ++{ ++ Q_UNUSED(parent) ++ return d->data->count(); ++} ++ ++void ImageVersionsModel::setupModelData(QList >& data) ++{ ++ beginResetModel(); ++ ++ d->data->clear(); ++ ++ if (!data.isEmpty()) ++ { ++ d->data->append(data); ++ } ++ else ++ { ++ d->data->append(qMakePair(QString(i18n("This is the original image")), 0)); ++ } ++ ++ endResetModel(); ++} ++ ++void ImageVersionsModel::clearModelData() ++{ ++ beginResetModel(); ++ ++ if (!d->data->isEmpty()) ++ { ++ d->data->clear(); ++ } ++ ++ endResetModel(); ++} ++ ++void ImageVersionsModel::slotAnimationStep() ++{ ++ emit dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 1)); ++} ++ ++QString ImageVersionsModel::currentSelectedImage() const ++{ ++ return d->currentSelectedImage; ++} ++ ++void ImageVersionsModel::setCurrentSelectedImage(const QString& path) ++{ ++ d->currentSelectedImage = path; ++} ++ ++QModelIndex ImageVersionsModel::currentSelectedImageIndex() const ++{ ++ return index(listIndexOf(d->currentSelectedImage), 0); ++} ++ ++bool ImageVersionsModel::paintTree() const ++{ ++ return d->paintTree; ++} ++ ++void ImageVersionsModel::setPaintTree(bool paint) ++{ ++ d->paintTree = paint; ++} ++ ++int ImageVersionsModel::listIndexOf(const QString& item) const ++{ ++ for (int i = 0; i < d->data->size(); ++i) ++ { ++ if (d->data->at(i).first == item) ++ { ++ return i; ++ } ++ } ++ ++ return -1; ++} ++ ++} // namespace Digikam +diff --git a/libs/database/models/imageversionsmodel.h b/libs/database/models/imageversionsmodel.h +new file mode 100644 +index 0000000..ed08529 +--- /dev/null ++++ b/libs/database/models/imageversionsmodel.h +@@ -0,0 +1,75 @@ ++/* ============================================================ ++ * ++ * This file is a part of digiKam project ++ * http://www.digikam.org ++ * ++ * Date : 2010-07-13 ++ * Description : Model for image versions ++ * ++ * Copyright (C) 2010 by Martin Klapetek ++ * ++ * This program is free software; you can redistribute it ++ * and/or modify it under the terms of the GNU General ++ * Public License as published by the Free Software Foundation; ++ * either version 2, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * ============================================================ */ ++ ++#ifndef IMAGEVERSIONSMODEL_H ++#define IMAGEVERSIONSMODEL_H ++ ++// Qt includes ++ ++#include ++#include ++ ++// Local includes ++ ++#include "digikam_export.h" ++ ++namespace Digikam ++{ ++ ++class DIGIKAM_DATABASE_EXPORT ImageVersionsModel : public QAbstractListModel ++{ ++ Q_OBJECT ++ ++public: ++ ++ explicit ImageVersionsModel(QObject* parent = 0); ++ ~ImageVersionsModel(); ++ ++ Qt::ItemFlags flags(const QModelIndex& index) const; ++ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; ++ int rowCount(const QModelIndex& parent = QModelIndex()) const; ++ ++ void setupModelData(QList >& data); ++ void clearModelData(); ++ ++ QString currentSelectedImage() const; ++ void setCurrentSelectedImage(const QString& path); ++ QModelIndex currentSelectedImageIndex() const; ++ ++ bool paintTree() const; ++ int listIndexOf(const QString& item) const; ++ ++public Q_SLOTS: ++ ++ void slotAnimationStep(); ++ void setPaintTree(bool paint); ++ ++private: ++ ++ class Private; ++ Private* const d; ++}; ++ ++} // namespace Digikam ++ ++#endif // IMAGEVERSIONSMODEL_H +diff --git a/libs/models/CMakeLists.txt b/libs/models/CMakeLists.txt +index cbabfaa..804456b 100644 +--- a/libs/models/CMakeLists.txt ++++ b/libs/models/CMakeLists.txt +@@ -9,18 +9,6 @@ if (POLICY CMP0063) + cmake_policy(SET CMP0063 NEW) + endif (POLICY CMP0063) + +-set(libdatabasemodels_SRCS +- imagemodel.cpp +- imagefiltermodel.cpp +- imagefiltermodelpriv.cpp +- imagefiltermodelthreads.cpp +- imagefiltersettings.cpp +- imagelistmodel.cpp +- imagesortsettings.cpp +- imagethumbnailmodel.cpp +- imageversionsmodel.cpp +-) +- + set(libalbummodels_SRCS + imagealbummodel.cpp + imagealbumfiltermodel.cpp +@@ -52,5 +40,4 @@ endif() + #for digikam core lib + add_library(digikamgenericmodels_src OBJECT ${libgenericmodels_SRCS}) + +-add_library(digikamdatabasemodels_src OBJECT ${libdatabasemodels_SRCS}) +-add_library(digikammodels_src OBJECT ${libalbummodels_SRCS} ${libgenericmodels_SRCS}) ++add_library(digikammodels_src OBJECT ${libalbummodels_SRCS} ${libgenericmodels_SRCS}) +diff --git a/libs/models/imagefiltermodel.cpp b/libs/models/imagefiltermodel.cpp +deleted file mode 100644 +index 3d57e05..0000000 +--- a/libs/models/imagefiltermodel.cpp ++++ /dev/null +@@ -1,1116 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011-2017 by Gilles Caulier +- * Copyright (C) 2010 by Andi Clemens +- * Copyright (C) 2011 by Michael G. Hansen +- * Copyright (C) 2014 by Mohamed Anwer +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagefiltermodel.h" +-#include "imagefiltermodelpriv.h" +-#include "imagefiltermodelthreads.h" +- +-// Local includes +- +-#include "digikam_debug.h" +-#include "coredbaccess.h" +-#include "coredbchangesets.h" +-#include "coredbwatch.h" +-#include "imageinfolist.h" +-#include "imagemodel.h" +- +-namespace Digikam +-{ +- +-ImageSortFilterModel::ImageSortFilterModel(QObject* parent) +- : DCategorizedSortFilterProxyModel(parent), m_chainedModel(0) +-{ +-} +- +-void ImageSortFilterModel::setSourceImageModel(ImageModel* source) +-{ +- if (m_chainedModel) +- { +- m_chainedModel->setSourceImageModel(source); +- } +- else +- { +- setDirectSourceImageModel(source); +- } +-} +- +-void ImageSortFilterModel::setSourceFilterModel(ImageSortFilterModel* source) +-{ +- if (source) +- { +- ImageModel* const model = sourceImageModel(); +- +- if (model) +- { +- source->setSourceImageModel(model); +- } +- } +- +- m_chainedModel = source; +- setSourceModel(source); +-} +- +-void ImageSortFilterModel::setDirectSourceImageModel(ImageModel* model) +-{ +- setSourceModel(model); +-} +- +-void ImageSortFilterModel::setSourceModel(QAbstractItemModel* model) +-{ +- // made it protected, only setSourceImageModel is public +- DCategorizedSortFilterProxyModel::setSourceModel(model); +-} +- +-ImageModel* ImageSortFilterModel::sourceImageModel() const +-{ +- if (m_chainedModel) +- { +- return m_chainedModel->sourceImageModel(); +- } +- +- return static_cast(sourceModel()); +-} +- +-ImageSortFilterModel* ImageSortFilterModel::sourceFilterModel() const +-{ +- return m_chainedModel; +-} +- +-ImageFilterModel* ImageSortFilterModel::imageFilterModel() const +-{ +- // reimplemented in ImageFilterModel +- if (m_chainedModel) +- { +- return m_chainedModel->imageFilterModel(); +- } +- +- return 0; +-} +- +-QModelIndex ImageSortFilterModel::mapToSourceImageModel(const QModelIndex& index) const +-{ +- if (m_chainedModel) +- { +- return m_chainedModel->mapToSourceImageModel(mapToSource(index)); +- } +- +- return mapToSource(index); +-} +- +-QModelIndex ImageSortFilterModel::mapFromSourceImageModel(const QModelIndex& albummodel_index) const +-{ +- if (m_chainedModel) +- { +- return mapFromSource(m_chainedModel->mapFromSourceImageModel(albummodel_index)); +- } +- +- return mapFromSource(albummodel_index); +-} +- +- +-QModelIndex ImageSortFilterModel::mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const +-{ +- if (m_chainedModel) +- { +- return m_chainedModel->mapToSourceImageModel(sourceModel_index); +- } +- return sourceModel_index; +-} +- +-// -------------- Convenience mappers ------------------------------------------------------------------- +- +-QList ImageSortFilterModel::mapListToSource(const QList& indexes) const +-{ +- QList sourceIndexes; +- foreach(const QModelIndex& index, indexes) +- { +- sourceIndexes << mapToSourceImageModel(index); +- } +- return sourceIndexes; +-} +- +-QList ImageSortFilterModel::mapListFromSource(const QList& sourceIndexes) const +-{ +- QList indexes; +- foreach(const QModelIndex& index, sourceIndexes) +- { +- indexes << mapFromSourceImageModel(index); +- } +- return indexes; +-} +- +-ImageInfo ImageSortFilterModel::imageInfo(const QModelIndex& index) const +-{ +- return sourceImageModel()->imageInfo(mapToSourceImageModel(index)); +-} +- +-qlonglong ImageSortFilterModel::imageId(const QModelIndex& index) const +-{ +- return sourceImageModel()->imageId(mapToSourceImageModel(index)); +-} +- +-QList ImageSortFilterModel::imageInfos(const QList& indexes) const +-{ +- QList infos; +- ImageModel* const model = sourceImageModel(); +- +- foreach(const QModelIndex& index, indexes) +- { +- infos << model->imageInfo(mapToSourceImageModel(index)); +- } +- +- return infos; +-} +- +-QList ImageSortFilterModel::imageIds(const QList& indexes) const +-{ +- QList ids; +- ImageModel* const model = sourceImageModel(); +- +- foreach(const QModelIndex& index, indexes) +- { +- ids << model->imageId(mapToSourceImageModel(index)); +- } +- +- return ids; +-} +- +-QModelIndex ImageSortFilterModel::indexForPath(const QString& filePath) const +-{ +- return mapFromSourceImageModel(sourceImageModel()->indexForPath(filePath)); +-} +- +-QModelIndex ImageSortFilterModel::indexForImageInfo(const ImageInfo& info) const +-{ +- return mapFromSourceImageModel(sourceImageModel()->indexForImageInfo(info)); +-} +- +-QModelIndex ImageSortFilterModel::indexForImageId(qlonglong id) const +-{ +- return mapFromSourceImageModel(sourceImageModel()->indexForImageId(id)); +-} +- +-QList ImageSortFilterModel::imageInfosSorted() const +-{ +- QList infos; +- const int size = rowCount(); +- ImageModel* const model = sourceImageModel(); +- +- for (int i=0; iimageInfo(mapToSourceImageModel(index(i, 0))); +- } +- +- return infos; +-} +- +-// -------------------------------------------------------------------------------------------- +- +-ImageFilterModel::ImageFilterModel(QObject* parent) +- : ImageSortFilterModel(parent), +- d_ptr(new ImageFilterModelPrivate) +-{ +- d_ptr->init(this); +-} +- +-ImageFilterModel::ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent) +- : ImageSortFilterModel(parent), +- d_ptr(&dd) +-{ +- d_ptr->init(this); +-} +- +-ImageFilterModel::~ImageFilterModel() +-{ +- Q_D(ImageFilterModel); +- delete d; +-} +- +-void ImageFilterModel::setDirectSourceImageModel(ImageModel* sourceModel) +-{ +- Q_D(ImageFilterModel); +- +- if (d->imageModel) +- { +- d->imageModel->unsetPreprocessor(d); +- disconnect(d->imageModel, SIGNAL(modelReset()), +- this, SLOT(slotModelReset())); +- slotModelReset(); +- } +- +- d->imageModel = sourceModel; +- +- if (d->imageModel) +- { +- d->imageModel->setPreprocessor(d); +- +- connect(d->imageModel, SIGNAL(preprocess(QList,QList)), +- d, SLOT(preprocessInfos(QList,QList))); +- +- connect(d->imageModel, SIGNAL(processAdded(QList,QList)), +- d, SLOT(processAddedInfos(QList,QList))); +- +- connect(d, SIGNAL(reAddImageInfos(QList,QList)), +- d->imageModel, SLOT(reAddImageInfos(QList,QList))); +- +- connect(d, SIGNAL(reAddingFinished()), +- d->imageModel, SLOT(reAddingFinished())); +- +- connect(d->imageModel, SIGNAL(modelReset()), +- this, SLOT(slotModelReset())); +- +- connect(d->imageModel, SIGNAL(imageChange(ImageChangeset,QItemSelection)), +- this, SLOT(slotImageChange(ImageChangeset))); +- +- connect(d->imageModel, SIGNAL(imageTagChange(ImageTagChangeset,QItemSelection)), +- this, SLOT(slotImageTagChange(ImageTagChangeset))); +- } +- +- setSourceModel(d->imageModel); +-} +- +-QVariant ImageFilterModel::data(const QModelIndex& index, int role) const +-{ +- Q_D(const ImageFilterModel); +- +- if (!index.isValid()) +- { +- return QVariant(); +- } +- +- switch (role) +- { +- // Attention: This breaks should there ever be another filter model between this and the ImageModel +- +- case DCategorizedSortFilterProxyModel::CategoryDisplayRole: +- return categoryIdentifier(d->imageModel->imageInfoRef(mapToSource(index))); +- case CategorizationModeRole: +- return d->sorter.categorizationMode; +- case SortOrderRole: +- return d->sorter.sortRole; +- //case CategoryCountRole: +- // return categoryCount(d->imageModel->imageInfoRef(mapToSource(index))); +- case CategoryAlbumIdRole: +- return d->imageModel->imageInfoRef(mapToSource(index)).albumId(); +- case CategoryFormatRole: +- return d->imageModel->imageInfoRef(mapToSource(index)).format(); +- case GroupIsOpenRole: +- return d->groupFilter.isAllOpen() || +- d->groupFilter.isOpen(d->imageModel->imageInfoRef(mapToSource(index)).id()); +- case ImageFilterModelPointerRole: +- return QVariant::fromValue(const_cast(this)); +- } +- +- return DCategorizedSortFilterProxyModel::data(index, role); +-} +- +-ImageFilterModel* ImageFilterModel::imageFilterModel() const +-{ +- return const_cast(this); +-} +- +-DatabaseFields::Set ImageFilterModel::suggestedWatchFlags() const +-{ +- DatabaseFields::Set watchFlags; +- watchFlags |= DatabaseFields::Name | DatabaseFields::FileSize | DatabaseFields::ModificationDate; +- watchFlags |= DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::Orientation | +- DatabaseFields::Width | DatabaseFields::Height; +- watchFlags |= DatabaseFields::Comment; +- watchFlags |= DatabaseFields::ImageRelations; +- return watchFlags; +-} +- +-// -------------- Filter settings -------------- +- +-void ImageFilterModel::setDayFilter(const QList& days) +-{ +- Q_D(ImageFilterModel); +- d->filter.setDayFilter(days); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setTagFilter(const QList& includedTags, const QList& excludedTags, +- ImageFilterSettings::MatchingCondition matchingCond, +- bool showUnTagged, const QList& clTagIds, const QList& plTagIds) +-{ +- Q_D(ImageFilterModel); +- d->filter.setTagFilter(includedTags, excludedTags, matchingCond, showUnTagged, clTagIds, plTagIds); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded) +-{ +- Q_D(ImageFilterModel); +- d->filter.setRatingFilter(rating, ratingCond, isUnratedExcluded); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setUrlWhitelist(const QList urlList, const QString& id) +-{ +- Q_D(ImageFilterModel); +- d->filter.setUrlWhitelist(urlList, id); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setIdWhitelist(const QList& idList, const QString& id) +-{ +- Q_D(ImageFilterModel); +- d->filter.setIdWhitelist(idList, id); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setMimeTypeFilter(int mimeTypeFilter) +-{ +- Q_D(ImageFilterModel); +- d->filter.setMimeTypeFilter(mimeTypeFilter); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition) +-{ +- Q_D(ImageFilterModel); +- d->filter.setGeolocationFilter(condition); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setTextFilter(const SearchTextFilterSettings& settings) +-{ +- Q_D(ImageFilterModel); +- d->filter.setTextFilter(settings); +- setImageFilterSettings(d->filter); +-} +- +-void ImageFilterModel::setImageFilterSettings(const ImageFilterSettings& settings) +-{ +- Q_D(ImageFilterModel); +- +- { +- QMutexLocker lock(&d->mutex); +- d->version++; +- d->filter = settings; +- d->filterCopy = settings; +- d->versionFilterCopy = d->versionFilter; +- d->groupFilterCopy = d->groupFilter; +- +- d->needPrepareComments = settings.isFilteringByText(); +- d->needPrepareTags = settings.isFilteringByTags(); +- d->needPrepareGroups = true; +- d->needPrepare = d->needPrepareComments || d->needPrepareTags || d->needPrepareGroups; +- +- d->hasOneMatch = false; +- d->hasOneMatchForText = false; +- } +- +- d->filterResults.clear(); +- +- //d->categoryCountHashInt.clear(); +- //d->categoryCountHashString.clear(); +- if (d->imageModel) +- { +- d->infosToProcess(d->imageModel->imageInfos()); +- } +- +- emit filterSettingsChanged(settings); +-} +- +-void ImageFilterModel::setVersionManagerSettings(const VersionManagerSettings& settings) +-{ +- Q_D(ImageFilterModel); +- d->versionFilter.setVersionManagerSettings(settings); +- setVersionImageFilterSettings(d->versionFilter); +-} +- +-void ImageFilterModel::setExceptionList(const QList& idList, const QString& id) +-{ +- Q_D(ImageFilterModel); +- d->versionFilter.setExceptionList(idList, id); +- setVersionImageFilterSettings(d->versionFilter); +-} +- +-void ImageFilterModel::setVersionImageFilterSettings(const VersionImageFilterSettings& settings) +-{ +- Q_D(ImageFilterModel); +- d->versionFilter = settings; +- slotUpdateFilter(); +-} +- +-bool ImageFilterModel::isGroupOpen(qlonglong group) const +-{ +- Q_D(const ImageFilterModel); +- return d->groupFilter.isOpen(group); +-} +- +-bool ImageFilterModel::isAllGroupsOpen() const +-{ +- Q_D(const ImageFilterModel); +- return d->groupFilter.isAllOpen(); +-} +- +-void ImageFilterModel::setGroupOpen(qlonglong group, bool open) +-{ +- Q_D(ImageFilterModel); +- d->groupFilter.setOpen(group, open); +- setGroupImageFilterSettings(d->groupFilter); +-} +- +-void ImageFilterModel::toggleGroupOpen(qlonglong group) +-{ +- setGroupOpen(group, !isGroupOpen(group)); +-} +- +-void ImageFilterModel::setAllGroupsOpen(bool open) +-{ +- Q_D(ImageFilterModel); +- d->groupFilter.setAllOpen(open); +- setGroupImageFilterSettings(d->groupFilter); +-} +- +-void ImageFilterModel::setGroupImageFilterSettings(const GroupImageFilterSettings& settings) +-{ +- Q_D(ImageFilterModel); +- d->groupFilter = settings; +- slotUpdateFilter(); +-} +- +-void ImageFilterModel::slotUpdateFilter() +-{ +- Q_D(ImageFilterModel); +- setImageFilterSettings(d->filter); +-} +- +-ImageFilterSettings ImageFilterModel::imageFilterSettings() const +-{ +- Q_D(const ImageFilterModel); +- return d->filter; +-} +- +-ImageSortSettings ImageFilterModel::imageSortSettings() const +-{ +- Q_D(const ImageFilterModel); +- return d->sorter; +-} +- +-VersionImageFilterSettings ImageFilterModel::versionImageFilterSettings() const +-{ +- Q_D(const ImageFilterModel); +- return d->versionFilter; +-} +- +-GroupImageFilterSettings ImageFilterModel::groupImageFilterSettings() const +-{ +- Q_D(const ImageFilterModel); +- return d->groupFilter; +-} +- +-void ImageFilterModel::slotModelReset() +-{ +- Q_D(ImageFilterModel); +- { +- QMutexLocker lock(&d->mutex); +- // discard all packages on the way that are marked as send out for re-add +- d->lastDiscardVersion = d->version; +- d->sentOutForReAdd = 0; +- // discard all packages on the way +- d->version++; +- d->sentOut = 0; +- +- d->hasOneMatch = false; +- d->hasOneMatchForText = false; +- } +- d->filterResults.clear(); +-} +- +-bool ImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +-{ +- Q_D(const ImageFilterModel); +- +- if (source_parent.isValid()) +- { +- return false; +- } +- +- qlonglong id = d->imageModel->imageId(source_row); +- QHash::const_iterator it = d->filterResults.constFind(id); +- +- if (it != d->filterResults.constEnd()) +- { +- return it.value(); +- } +- +- // usually done in thread and cache, unless source model changed +- ImageInfo info = d->imageModel->imageInfo(source_row); +- bool match = d->filter.matches(info); +- match = match ? d->versionFilter.matches(info) : false; +- +- return match ? d->groupFilter.matches(info) : false; +-} +- +-void ImageFilterModel::setSendImageInfoSignals(bool sendSignals) +-{ +- if (sendSignals) +- { +- connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), +- this, SLOT(slotRowsInserted(QModelIndex,int,int))); +- +- connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), +- this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); +- } +- else +- { +- disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), +- this, SLOT(slotRowsInserted(QModelIndex,int,int))); +- +- disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), +- this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); +- } +-} +- +-void ImageFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end) +-{ +- QList infos; +- +- for (int i=start; i<=end; ++i) +- { +- infos << imageInfo(index(i, 0)); +- } +- +- emit imageInfosAdded(infos); +-} +- +-void ImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) +-{ +- QList infos; +- +- for (int i=start; i<=end; ++i) +- { +- infos << imageInfo(index(i, 0)); +- } +- +- emit imageInfosAboutToBeRemoved(infos); +-} +- +-// -------------- Threaded preparation & filtering -------------- +- +-void ImageFilterModel::addPrepareHook(ImageFilterModelPrepareHook* hook) +-{ +- Q_D(ImageFilterModel); +- QMutexLocker lock(&d->mutex); +- d->prepareHooks << hook; +-} +- +-void ImageFilterModel::removePrepareHook(ImageFilterModelPrepareHook* hook) +-{ +- Q_D(ImageFilterModel); +- QMutexLocker lock(&d->mutex); +- d->prepareHooks.removeAll(hook); +-} +- +-void ImageFilterModelPreparer::process(ImageFilterModelTodoPackage package) +-{ +- if (!checkVersion(package)) +- { +- emit discarded(package); +- return; +- } +- +- // get thread-local copy +- bool needPrepareTags, needPrepareComments, needPrepareGroups; +- QList prepareHooks; +- { +- QMutexLocker lock(&d->mutex); +- needPrepareTags = d->needPrepareTags; +- needPrepareComments = d->needPrepareComments; +- needPrepareGroups = d->needPrepareGroups; +- prepareHooks = d->prepareHooks; +- } +- +- //TODO: Make efficient!! +- if (needPrepareComments) +- { +- foreach(const ImageInfo& info, package.infos) +- { +- info.comment(); +- } +- } +- +- if (!checkVersion(package)) +- { +- emit discarded(package); +- return; +- } +- +- // The downside of QVector: At some point, we may need a QList for an API. +- // Nonetheless, QList and ImageInfo is fast. We could as well +- // reimplement ImageInfoList to ImageInfoVector (internally with templates?) +- ImageInfoList infoList; +- +- if (needPrepareTags || needPrepareGroups) +- { +- infoList = package.infos.toList(); +- } +- +- if (needPrepareTags) +- { +- infoList.loadTagIds(); +- } +- +- if (needPrepareGroups) +- { +- infoList.loadGroupImageIds(); +- } +- +- foreach(ImageFilterModelPrepareHook* hook, prepareHooks) +- { +- hook->prepare(package.infos); +- } +- +- emit processed(package); +-} +- +-void ImageFilterModelFilterer::process(ImageFilterModelTodoPackage package) +-{ +- if (!checkVersion(package)) +- { +- emit discarded(package); +- return; +- } +- +- // get thread-local copy +- ImageFilterSettings localFilter; +- VersionImageFilterSettings localVersionFilter; +- GroupImageFilterSettings localGroupFilter; +- bool hasOneMatch; +- bool hasOneMatchForText; +- { +- QMutexLocker lock(&d->mutex); +- localFilter = d->filterCopy; +- localVersionFilter = d->versionFilterCopy; +- localGroupFilter = d->groupFilterCopy; +- hasOneMatch = d->hasOneMatch; +- hasOneMatchForText = d->hasOneMatchForText; +- } +- +- // Actual filtering. The variants to spare checking hasOneMatch over and over again. +- if (hasOneMatch && hasOneMatchForText) +- { +- foreach(const ImageInfo& info, package.infos) +- { +- package.filterResults[info.id()] = localFilter.matches(info) && +- localVersionFilter.matches(info) && +- localGroupFilter.matches(info); +- } +- } +- else if (hasOneMatch) +- { +- bool matchForText; +- +- foreach(const ImageInfo& info, package.infos) +- { +- package.filterResults[info.id()] = localFilter.matches(info, &matchForText) && +- localVersionFilter.matches(info) && +- localGroupFilter.matches(info); +- +- if (matchForText) +- { +- hasOneMatchForText = true; +- } +- } +- } +- else +- { +- bool result, matchForText; +- +- foreach(const ImageInfo& info, package.infos) +- { +- result = localFilter.matches(info, &matchForText) && +- localVersionFilter.matches(info) && +- localGroupFilter.matches(info); +- package.filterResults[info.id()] = result; +- +- if (result) +- { +- hasOneMatch = true; +- } +- +- if (matchForText) +- { +- hasOneMatchForText = true; +- } +- } +- } +- +- if (checkVersion(package)) +- { +- QMutexLocker lock(&d->mutex); +- d->hasOneMatch = hasOneMatch; +- d->hasOneMatchForText = hasOneMatchForText; +- } +- +- emit processed(package); +-} +- +-// -------------- Sorting and Categorization ------------------------------------------------------- +- +-void ImageFilterModel::setImageSortSettings(const ImageSortSettings& sorter) +-{ +- Q_D(ImageFilterModel); +- d->sorter = sorter; +- setCategorizedModel(d->sorter.categorizationMode != ImageSortSettings::NoCategories); +- invalidate(); +-} +- +-void ImageFilterModel::setCategorizationMode(ImageSortSettings::CategorizationMode mode) +-{ +- Q_D(ImageFilterModel); +- d->sorter.setCategorizationMode(mode); +- setImageSortSettings(d->sorter); +-} +- +-void ImageFilterModel::setCategorizationSortOrder(ImageSortSettings::SortOrder order) +-{ +- Q_D(ImageFilterModel); +- d->sorter.setCategorizationSortOrder(order); +- setImageSortSettings(d->sorter); +-} +- +-void ImageFilterModel::setSortRole(ImageSortSettings::SortRole role) +-{ +- Q_D(ImageFilterModel); +- d->sorter.setSortRole(role); +- setImageSortSettings(d->sorter); +-} +- +-void ImageFilterModel::setSortOrder(ImageSortSettings::SortOrder order) +-{ +- Q_D(ImageFilterModel); +- d->sorter.setSortOrder(order); +- setImageSortSettings(d->sorter); +-} +- +-void ImageFilterModel::setStringTypeNatural(bool natural) +-{ +- Q_D(ImageFilterModel); +- d->sorter.setStringTypeNatural(natural); +- setImageSortSettings(d->sorter); +-} +- +-int ImageFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const +-{ +- // source indexes +- Q_D(const ImageFilterModel); +- +- if (!d->sorter.isCategorized()) +- { +- return 0; +- } +- +- if (!left.isValid() || !right.isValid()) +- { +- return -1; +- } +- +- const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); +- const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); +- +- // Check grouping +- qlonglong leftGroupImageId = leftInfo.groupImageId(); +- qlonglong rightGroupImageId = rightInfo.groupImageId(); +- +- return compareInfosCategories(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), +- rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); +-} +- +-bool ImageFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const +-{ +- // source indexes +- Q_D(const ImageFilterModel); +- +- if (!left.isValid() || !right.isValid()) +- { +- return true; +- } +- +- if (left == right) +- { +- return false; +- } +- +- const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left); +- const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right); +- +- if (leftInfo == rightInfo) +- { +- return d->sorter.lessThan(left.data(ImageModel::ExtraDataRole), right.data(ImageModel::ExtraDataRole)); +- } +- +- // Check grouping +- qlonglong leftGroupImageId = leftInfo.groupImageId(); +- qlonglong rightGroupImageId = rightInfo.groupImageId(); +- +- // Either no grouping (-1), or same group image, or same image +- if (leftGroupImageId == rightGroupImageId) +- { +- return infosLessThan(leftInfo, rightInfo); +- } +- +- // We have grouping to handle +- +- // Is one grouped on the other? Sort behind leader. +- if (leftGroupImageId == rightInfo.id()) +- { +- return false; +- } +- if (rightGroupImageId == leftInfo.id()) +- { +- return true; +- } +- +- // Use the group leader for sorting +- return infosLessThan(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId), +- rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId)); +-} +- +-int ImageFilterModel::compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const +-{ +- // Note: reimplemented in ImageAlbumFilterModel +- Q_D(const ImageFilterModel); +- return d->sorter.compareCategories(left, right); +-} +- +-// Feel free to optimize. QString::number is 3x slower. +-static inline QString fastNumberToString(int id) +-{ +- const int size = sizeof(int) * 2; +- char c[size+1]; +- c[size] = '\0'; +- char* p = c; +- int number = id; +- +- for (int i=0; i>= 4; +- ++p; +- } +- +- return QString::fromLatin1(c); +-} +- +-QString ImageFilterModel::categoryIdentifier(const ImageInfo& i) const +-{ +- Q_D(const ImageFilterModel); +- +- if (!d->sorter.isCategorized()) +- { +- return QString(); +- } +- +- qlonglong groupedImageId = i.groupImageId(); +- ImageInfo info = groupedImageId == -1 ? i : ImageInfo(groupedImageId); +- +- switch (d->sorter.categorizationMode) +- { +- case ImageSortSettings::NoCategories: +- return QString(); +- case ImageSortSettings::OneCategory: +- return QString(); +- case ImageSortSettings::CategoryByAlbum: +- return fastNumberToString(info.albumId()); +- case ImageSortSettings::CategoryByFormat: +- return info.format(); +- default: +- return QString(); +- } +-} +- +-bool ImageFilterModel::infosLessThan(const ImageInfo& left, const ImageInfo& right) const +-{ +- Q_D(const ImageFilterModel); +- return d->sorter.lessThan(left, right); +-} +- +-// -------------- Watching changes ----------------------------------------------------------------- +- +-void ImageFilterModel::slotImageTagChange(const ImageTagChangeset& changeset) +-{ +- Q_D(ImageFilterModel); +- +- if (!d->imageModel || d->imageModel->isEmpty()) +- { +- return; +- } +- +- // already scheduled to re-filter? +- if (d->updateFilterTimer->isActive()) +- { +- return; +- } +- +- // do we filter at all? +- if (!d->versionFilter.isFilteringByTags() && +- !d->filter.isFilteringByTags() && +- !d->filter.isFilteringByText()) +- { +- return; +- } +- +- // is one of our images affected? +- foreach(const qlonglong& id, changeset.ids()) +- { +- // if one matching image id is found, trigger a refresh +- if (d->imageModel->hasImage(id)) +- { +- d->updateFilterTimer->start(); +- return; +- } +- } +-} +- +-void ImageFilterModel::slotImageChange(const ImageChangeset& changeset) +-{ +- Q_D(ImageFilterModel); +- +- if (!d->imageModel || d->imageModel->isEmpty()) +- { +- return; +- } +- +- // already scheduled to re-filter? +- if (d->updateFilterTimer->isActive()) +- { +- return; +- } +- +- // is one of the values affected that we filter or sort by? +- DatabaseFields::Set set = changeset.changes(); +- bool sortAffected = (set & d->sorter.watchFlags()); +- bool filterAffected = (set & d->filter.watchFlags()) || (set & d->groupFilter.watchFlags()); +- +- if (!sortAffected && !filterAffected) +- { +- return; +- } +- +- // is one of our images affected? +- bool imageAffected = false; +- +- foreach(const qlonglong& id, changeset.ids()) +- { +- // if one matching image id is found, trigger a refresh +- if (d->imageModel->hasImage(id)) +- { +- imageAffected = true; +- break; +- } +- } +- +- if (!imageAffected) +- { +- return; +- } +- +- if (filterAffected) +- { +- d->updateFilterTimer->start(); +- } +- else +- { +- invalidate(); // just resort, reuse filter results +- } +-} +- +-// ------------------------------------------------------------------------------------------------------- +- +-NoDuplicatesImageFilterModel::NoDuplicatesImageFilterModel(QObject* parent) +- : ImageSortFilterModel(parent) +-{ +-} +- +-bool NoDuplicatesImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +-{ +- QModelIndex index = sourceModel()->index(source_row, 0, source_parent); +- +- if (index.data(ImageModel::ExtraDataDuplicateCount).toInt() <= 1) +- { +- return true; +- } +- +- QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent); +- +- if (!previousIndex.isValid()) +- { +- return true; +- } +- +- if (sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(index)) == sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(previousIndex))) +- { +- return false; +- } +- return true; +-} +- +-/* +-void NoDuplicatesImageFilterModel::setSourceModel(QAbstractItemModel* model) +-{ +- if (sourceModel()) +- { +- } +- +- ImageSortFilterModel::setSourceModel(model); +- +- if (sourceModel()) +- { +- connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), +- this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); +- } +-} +- +-void NoDuplicatesImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end) +-{ +- bool needInvalidate = false; +- +- for (int i = begin; i<=end; ++i) +- { +- QModelIndex index = sourceModel()->index(i, 0, parent); +- +- // filtered out by us? +- if (!mapFromSource(index).isValid()) +- { +- continue; +- } +- +- QModelIndex sourceIndex = mapFromDirectSourceToSourceImageModel(index); +- qlonglong id = sourceImageModel()->imageId(sourceIndex); +- +- if (sourceImageModel()->numberOfIndexesForImageId(id) > 1) +- { +- needInvalidate = true; +- } +- } +-}*/ +- +-} // namespace Digikam +diff --git a/libs/models/imagefiltermodel.h b/libs/models/imagefiltermodel.h +deleted file mode 100644 +index d131b3e..0000000 +--- a/libs/models/imagefiltermodel.h ++++ /dev/null +@@ -1,299 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011 by Gilles Caulier +- * Copyright (C) 2010 by Andi Clemens +- * Copyright (C) 2011 by Michael G. Hansen +- * Copyright (C) 2014 by Mohamed Anwer +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGEFILTERMODEL_H +-#define IMAGEFILTERMODEL_H +- +-// Local includes +- +-#include "dcategorizedsortfilterproxymodel.h" +-#include "textfilter.h" +-#include "imagefiltersettings.h" +-#include "imagemodel.h" +-#include "imagesortsettings.h" +-#include "digikam_export.h" +- +-namespace Digikam +-{ +- +-class ImageChangeset; +-class ImageFilterModel; +-class ImageTagChangeset; +- +-class DIGIKAM_DATABASE_EXPORT ImageFilterModelPrepareHook +-{ +-public: +- +- virtual ~ImageFilterModelPrepareHook() {}; +- virtual void prepare(const QVector& infos) = 0; +-}; +- +-// ----------------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT ImageSortFilterModel : public DCategorizedSortFilterProxyModel +-{ +- Q_OBJECT +- +-public: +- +- explicit ImageSortFilterModel(QObject* parent = 0); +- +- void setSourceImageModel(ImageModel* model); +- ImageModel* sourceImageModel() const; +- +- void setSourceFilterModel(ImageSortFilterModel* model); +- ImageSortFilterModel* sourceFilterModel() const; +- +- QModelIndex mapToSourceImageModel(const QModelIndex& index) const; +- QModelIndex mapFromSourceImageModel(const QModelIndex& imagemodel_index) const; +- QModelIndex mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const; +- +- /// Convenience methods mapped to ImageModel. +- /// Mentioned indexes returned come from the source image model. +- QList mapListToSource(const QList& indexes) const; +- QList mapListFromSource(const QList& sourceIndexes) const; +- +- ImageInfo imageInfo(const QModelIndex& index) const; +- qlonglong imageId(const QModelIndex& index) const; +- QList imageInfos(const QList& indexes) const; +- QList imageIds(const QList& indexes) const; +- +- QModelIndex indexForPath(const QString& filePath) const; +- QModelIndex indexForImageInfo(const ImageInfo& info) const; +- QModelIndex indexForImageId(qlonglong id) const; +- +- /** Returns a list of all image infos, sorted according to this model. +- * If you do not need a sorted list, use ImageModel's imageInfos() method. +- */ +- QList imageInfosSorted() const; +- +- /// Returns this, any chained ImageFilterModel, or 0. +- virtual ImageFilterModel* imageFilterModel() const; +- +-protected: +- +- /// Reimplement if needed. Called only when model shall be set as (direct) sourceModel. +- virtual void setDirectSourceImageModel(ImageModel* model); +- +- // made protected +- virtual void setSourceModel(QAbstractItemModel* model); +- +-protected: +- +- ImageSortFilterModel* m_chainedModel; +-}; +- +-// ----------------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT ImageFilterModel : public ImageSortFilterModel +-{ +- Q_OBJECT +- +-public: +- +- enum ImageFilterModelRoles +- { +- /// Returns the current categorization mode +- CategorizationModeRole = ImageModel::FilterModelRoles + 1, +- /// Returns the current sort order +- SortOrderRole = ImageModel::FilterModelRoles + 2, +- // / Returns the number of items in the index' category +- //CategoryCountRole = ImageModel::FilterModelRoles + 3, +- /// Returns the id of the PAlbum of the index which is used for category +- CategoryAlbumIdRole = ImageModel::FilterModelRoles + 3, +- /// Returns the format of the index which is used for category +- CategoryFormatRole = ImageModel::FilterModelRoles + 4, +- /// Returns true if the given image is a group leader, and the group is opened +- GroupIsOpenRole = ImageModel::FilterModelRoles + 5, +- ImageFilterModelPointerRole = ImageModel::FilterModelRoles + 50 +- }; +- +-public: +- +- explicit ImageFilterModel(QObject* parent = 0); +- ~ImageFilterModel(); +- +- /** Add a hook to get added images for preparation tasks before they are added in the model */ +- void addPrepareHook(ImageFilterModelPrepareHook* hook); +- void removePrepareHook(ImageFilterModelPrepareHook* hook); +- +- /** Returns a set of DatabaseFields suggested to set as watch flags on the source ImageModel. +- * The contained flags will be those that this model can sort or filter by. */ +- DatabaseFields::Set suggestedWatchFlags() const; +- +- ImageFilterSettings imageFilterSettings() const; +- VersionImageFilterSettings versionImageFilterSettings() const; +- GroupImageFilterSettings groupImageFilterSettings() const; +- ImageSortSettings imageSortSettings() const; +- +- // group is identified by the id of its group leader +- bool isGroupOpen(qlonglong group) const; +- bool isAllGroupsOpen() const; +- +- /// Enables sending imageInfosAdded and imageInfosAboutToBeRemoved +- void setSendImageInfoSignals(bool sendSignals); +- +- virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; +- virtual ImageFilterModel* imageFilterModel() const; +- +-public Q_SLOTS: +- +- /** Changes the current version image filter settings and refilters. */ +- void setVersionImageFilterSettings(const VersionImageFilterSettings& settings); +- +- /** Changes the current version image filter settings and refilters. */ +- void setGroupImageFilterSettings(const GroupImageFilterSettings& settings); +- +- /** Adjust the current ImageFilterSettings. +- * Equivalent to retrieving the current filter settings, adjusting the parameter +- * and calling setImageFilterSettings. +- * Provided for convenience. +- * It is encouraged to use setImageFilterSettings if you change more than one +- * parameter at a time. +- */ +- void setDayFilter(const QList& days); +- void setTagFilter(const QList& includedTags, const QList& excludedTags, +- ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged, +- const QList& clTagIds, const QList& plTagIds); +- void setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded); +- void setMimeTypeFilter(int mimeTypeFilter); +- void setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition); +- void setTextFilter(const SearchTextFilterSettings& settings); +- +- void setCategorizationMode(ImageSortSettings::CategorizationMode mode); +- void setCategorizationSortOrder(ImageSortSettings::SortOrder order); +- void setSortRole(ImageSortSettings::SortRole role); +- void setSortOrder(ImageSortSettings::SortOrder order); +- void setStringTypeNatural(bool natural); +- void setUrlWhitelist(const QList urlList, const QString& id); +- void setIdWhitelist(const QList& idList, const QString& id); +- +- void setVersionManagerSettings(const VersionManagerSettings& settings); +- void setExceptionList(const QList& idlist, const QString& id); +- +- void setGroupOpen(qlonglong group, bool open); +- void toggleGroupOpen(qlonglong group); +- void setAllGroupsOpen(bool open); +- +- /** Changes the current image filter settings and refilters. */ +- virtual void setImageFilterSettings(const ImageFilterSettings& settings); +- +- /** Changes the current image sort settings and resorts. */ +- virtual void setImageSortSettings(const ImageSortSettings& settings); +- +-Q_SIGNALS: +- +- /// Signals that the set filter matches at least one index +- void filterMatches(bool matches); +- +- /** Signals that the set text filter matches at least one entry. +- If no text filter is set, this signal is emitted +- with 'false' when filterMatches() is emitted. +- */ +- void filterMatchesForText(bool matchesByText); +- +- /** Emitted when the filter settings have been changed +- (the model may not yet have been updated) +- */ +- void filterSettingsChanged(const ImageFilterSettings& settings); +- +- /** These signals need to be explicitly enabled with setSendImageInfoSignals() +- */ +- void imageInfosAdded(const QList& infos); +- void imageInfosAboutToBeRemoved(const QList& infos); +- +-public: +- +- // Declared as public because of use in sub-classes. +- class ImageFilterModelPrivate; +- +-protected: +- +- ImageFilterModelPrivate* const d_ptr; +- +-protected: +- +- ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent); +- +- virtual void setDirectSourceImageModel(ImageModel* model); +- +- virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; +- +- virtual int compareCategories(const QModelIndex& left, const QModelIndex& right) const; +- virtual bool subSortLessThan(const QModelIndex& left, const QModelIndex& right) const; +- //virtual int categoryCount(const ImageInfo& info) const; +- +- /** Reimplement to customize category sorting, +- * Return negative if category of left < category right, +- * Return 0 if left and right are in the same category, else return positive. +- */ +- virtual int compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const; +- +- /** Reimplement to customize sorting. Do not take categories into account here. +- */ +- virtual bool infosLessThan(const ImageInfo& left, const ImageInfo& right) const; +- +- /** Returns a unique identifier for the category if info. The string need not be for user display. +- */ +- virtual QString categoryIdentifier(const ImageInfo& info) const; +- +-protected Q_SLOTS: +- +- void slotModelReset(); +- void slotUpdateFilter(); +- +- void slotImageTagChange(const ImageTagChangeset& changeset); +- void slotImageChange(const ImageChangeset& changeset); +- +- void slotRowsInserted(const QModelIndex& parent, int start, int end); +- void slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); +- +-private: +- +- Q_DECLARE_PRIVATE(ImageFilterModel) +-}; +- +-// ----------------------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT NoDuplicatesImageFilterModel : public ImageSortFilterModel +-{ +- Q_OBJECT +- +-public: +- +- explicit NoDuplicatesImageFilterModel(QObject* parent = 0); +- +-protected: +- +- virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; +-}; +- +-} // namespace Digikam +- +-Q_DECLARE_METATYPE(Digikam::ImageFilterModel*) +- +-#endif // IMAGEMODEL_H +diff --git a/libs/models/imagefiltermodelpriv.cpp b/libs/models/imagefiltermodelpriv.cpp +deleted file mode 100644 +index 07d9e79..0000000 +--- a/libs/models/imagefiltermodelpriv.cpp ++++ /dev/null +@@ -1,258 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011-2017 by Gilles Caulier +- * Copyright (C) 2010 by Andi Clemens +- * Copyright (C) 2011 by Michael G. Hansen +- * Copyright (C) 2014 by Mohamed Anwer +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagefiltermodelpriv.h" +- +-// Local includes +- +-#include "digikam_debug.h" +-#include "imagefiltermodelthreads.h" +- +-namespace Digikam +-{ +- +-ImageFilterModel::ImageFilterModelPrivate::ImageFilterModelPrivate() +-{ +- imageModel = 0; +- version = 0; +- lastDiscardVersion = 0; +- sentOut = 0; +- sentOutForReAdd = 0; +- updateFilterTimer = 0; +- needPrepare = false; +- needPrepareComments = false; +- needPrepareTags = false; +- needPrepareGroups = false; +- preparer = 0; +- filterer = 0; +- hasOneMatch = false; +- hasOneMatchForText = false; +- +- setupWorkers(); +-} +- +-ImageFilterModel::ImageFilterModelPrivate::~ImageFilterModelPrivate() +-{ +- // facilitate thread stopping +- ++version; +- preparer->deactivate(); +- filterer->deactivate(); +- delete preparer; +- delete filterer; +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::init(ImageFilterModel* _q) +-{ +- q = _q; +- +- updateFilterTimer = new QTimer(this); +- updateFilterTimer->setSingleShot(true); +- updateFilterTimer->setInterval(250); +- +- connect(updateFilterTimer, SIGNAL(timeout()), +- q, SLOT(slotUpdateFilter())); +- +- // inter-thread redirection +- qRegisterMetaType("ImageFilterModelTodoPackage"); +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList& infos, const QList& extraValues) +-{ +- infosToProcess(infos, extraValues, true); +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList& infos, const QList& extraValues) +-{ +- // These have already been added, we just process them afterwards +- infosToProcess(infos, extraValues, false); +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::setupWorkers() +-{ +- preparer = new ImageFilterModelPreparer(this); +- filterer = new ImageFilterModelFilterer(this); +- +- // A package in constructed in infosToProcess. +- // Normal flow is infosToProcess -> preparer::process -> filterer::process -> packageFinished. +- // If no preparation is needed, the first step is skipped. +- // If filter version changes, both will discard old package and send them to packageDiscarded. +- +- connect(this, SIGNAL(packageToPrepare(ImageFilterModelTodoPackage)), +- preparer, SLOT(process(ImageFilterModelTodoPackage))); +- +- connect(this, SIGNAL(packageToFilter(ImageFilterModelTodoPackage)), +- filterer, SLOT(process(ImageFilterModelTodoPackage))); +- +- connect(preparer, SIGNAL(processed(ImageFilterModelTodoPackage)), +- filterer, SLOT(process(ImageFilterModelTodoPackage))); +- +- connect(filterer, SIGNAL(processed(ImageFilterModelTodoPackage)), +- this, SLOT(packageFinished(ImageFilterModelTodoPackage))); +- +- connect(preparer, SIGNAL(discarded(ImageFilterModelTodoPackage)), +- this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); +- +- connect(filterer, SIGNAL(discarded(ImageFilterModelTodoPackage)), +- this, SLOT(packageDiscarded(ImageFilterModelTodoPackage))); +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos) +-{ +- infosToProcess(infos, QList(), false); +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd) +-{ +- if (infos.isEmpty()) +- { +- return; +- } +- +- filterer->schedule(); +- +- if (needPrepare) +- { +- preparer->schedule(); +- } +- +- Q_ASSERT(extraValues.isEmpty() || infos.size() == extraValues.size()); +- +- // prepare and filter in chunks +- const int size = infos.size(); +- const int maxChunkSize = needPrepare ? PrepareChunkSize : FilterChunkSize; +- const bool hasExtraValues = !extraValues.isEmpty(); +- QList::const_iterator it = infos.constBegin(), end; +- QList::const_iterator xit = extraValues.constBegin(), xend; +- int index = 0; +- QVector infoVector; +- QVector extraValueVector; +- +- while (it != infos.constEnd()) +- { +- const int chunkSize = qMin(maxChunkSize, size - index); +- infoVector.resize(chunkSize); +- end = it + chunkSize; +- qCopy(it, end, infoVector.begin()); +- +- if (hasExtraValues) +- { +- extraValueVector.resize(chunkSize); +- xend = xit + chunkSize; +- qCopy(xit, xend, extraValueVector.begin()); +- xit = xend; +- } +- +- it = end; +- index += chunkSize; +- +- ++sentOut; +- +- if (forReAdd) +- { +- ++sentOutForReAdd; +- } +- +- if (needPrepare) +- { +- emit packageToPrepare(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); +- } +- else +- { +- emit packageToFilter(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd)); +- } +- } +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::packageFinished(const ImageFilterModelTodoPackage& package) +-{ +- // check if it got discarded on the journey +- if (package.version != version) +- { +- packageDiscarded(package); +- return; +- } +- +- // incorporate result +- QHash::const_iterator it = package.filterResults.constBegin(); +- +- for (; it != package.filterResults.constEnd(); ++it) +- { +- filterResults.insert(it.key(), it.value()); +- } +- +- // re-add if necessary +- if (package.isForReAdd) +- { +- emit reAddImageInfos(package.infos.toList(), package.extraValues.toList()); +- +- if (sentOutForReAdd == 1) // last package +- { +- emit reAddingFinished(); +- } +- } +- +- // decrement counters +- --sentOut; +- +- if (package.isForReAdd) +- { +- --sentOutForReAdd; +- } +- +- // If all packages have returned, filtered and readded, and no more are expected, +- // and there is need to tell the filter result to the view, do that +- if (sentOut == 0 && sentOutForReAdd == 0 && !imageModel->isRefreshing()) +- { +- q->invalidate(); // use invalidate, not invalidateFilter only. Sorting may have changed as well. +- emit (q->filterMatches(hasOneMatch)); +- emit (q->filterMatchesForText(hasOneMatchForText)); +- filterer->deactivate(); +- preparer->deactivate(); +- } +-} +- +-void ImageFilterModel::ImageFilterModelPrivate::packageDiscarded(const ImageFilterModelTodoPackage& package) +-{ +- // Either, the model was reset, or the filter changed +- // In the former case throw all away, in the latter case, recycle +- if (package.version > lastDiscardVersion) +- { +- // Recycle packages: Send again with current version +- // Do not increment sentOut or sentOutForReAdd here: it was not decremented! +- +- if (needPrepare) +- { +- emit packageToPrepare(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); +- } +- else +- { +- emit packageToFilter(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd)); +- } +- } +-} +- +-} // namespace Digikam +diff --git a/libs/models/imagefiltermodelpriv.h b/libs/models/imagefiltermodelpriv.h +deleted file mode 100644 +index a9e3f22..0000000 +--- a/libs/models/imagefiltermodelpriv.h ++++ /dev/null +@@ -1,159 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-11 +- * Description : Qt item model for database entries - private shared header +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGEFILTERMODELPRIV_H +-#define IMAGEFILTERMODELPRIV_H +- +-// Qt includes +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-// Local includes +- +-#include "imageinfo.h" +-#include "imagefiltermodel.h" +- +-#include "digikam_export.h" +-// Yes, we need the EXPORT macro in a private header because +-// this private header is shared across binary objects. +-// This does NOT make this classes here any more public! +- +-namespace Digikam +-{ +- +-const int PrepareChunkSize = 101; +-const int FilterChunkSize = 2001; +- +-class ImageFilterModelTodoPackage +-{ +-public: +- +- ImageFilterModelTodoPackage() +- : version(0), isForReAdd(false) +- { +- } +- +- ImageFilterModelTodoPackage(const QVector& infos, const QVector& extraValues, int version, bool isForReAdd) +- : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd) +- { +- } +- +- QVector infos; +- QVector extraValues; +- unsigned int version; +- bool isForReAdd; +- QHash filterResults; +-}; +- +-// ------------------------------------------------------------------------------------------------ +- +-class ImageFilterModelPreparer; +-class ImageFilterModelFilterer; +- +-class DIGIKAM_DATABASE_EXPORT ImageFilterModel::ImageFilterModelPrivate : public QObject +-{ +- Q_OBJECT +- +-public: +- +- ImageFilterModelPrivate(); +- ~ImageFilterModelPrivate(); +- +- void init(ImageFilterModel* q); +- void setupWorkers(); +- void infosToProcess(const QList& infos); +- void infosToProcess(const QList& infos, const QList& extraValues, bool forReAdd = true); +- +-public: +- +- ImageFilterModel* q; +- +- ImageModel* imageModel; +- +- ImageFilterSettings filter; +- ImageSortSettings sorter; +- VersionImageFilterSettings versionFilter; +- GroupImageFilterSettings groupFilter; +- +- volatile unsigned int version; +- unsigned int lastDiscardVersion; +- unsigned int lastFilteredVersion; +- int sentOut; +- int sentOutForReAdd; +- +- QTimer* updateFilterTimer; +- +- bool needPrepare; +- bool needPrepareComments; +- bool needPrepareTags; +- bool needPrepareGroups; +- +- QMutex mutex; +- ImageFilterSettings filterCopy; +- VersionImageFilterSettings versionFilterCopy; +- GroupImageFilterSettings groupFilterCopy; +- ImageFilterModelPreparer* preparer; +- ImageFilterModelFilterer* filterer; +- +- QHash filterResults; +- bool hasOneMatch; +- bool hasOneMatchForText; +- +- QList prepareHooks; +- +-/* +- QHash > categoryCountHashInt; +- QHash > categoryCountHashString; +- +-public: +- +- void cacheCategoryCount(int id, qlonglong imageid) const +- { const_cast(this)->categoryCountHashInt[id].insert(imageid); } +- void cacheCategoryCount(const QString& id, qlonglong imageid) const +- { const_cast(this)->categoryCountHashString[id].insert(imageid); } +-*/ +- +-public Q_SLOTS: +- +- void preprocessInfos(const QList& infos, const QList& extraValues); +- void processAddedInfos(const QList& infos, const QList& extraValues); +- void packageFinished(const ImageFilterModelTodoPackage& package); +- void packageDiscarded(const ImageFilterModelTodoPackage& package); +- +-Q_SIGNALS: +- +- void packageToPrepare(const ImageFilterModelTodoPackage& package); +- void packageToFilter(const ImageFilterModelTodoPackage& package); +- void reAddImageInfos(const QList& infos, const QList& extraValues); +- void reAddingFinished(); +-}; +- +-} // namespace Digikam +- +-#endif // IMAGEFILTERMODELPRIV_H +diff --git a/libs/models/imagefiltermodelthreads.cpp b/libs/models/imagefiltermodelthreads.cpp +deleted file mode 100644 +index aa5c462..0000000 +--- a/libs/models/imagefiltermodelthreads.cpp ++++ /dev/null +@@ -1,40 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011-2017 by Gilles Caulier +- * Copyright (C) 2010 by Andi Clemens +- * Copyright (C) 2011 by Michael G. Hansen +- * Copyright (C) 2014 by Mohamed Anwer +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagefiltermodel.h" +-#include "imagefiltermodelpriv.h" +-#include "imagefiltermodelthreads.h" +- +-namespace Digikam +-{ +- +-ImageFilterModelWorker::ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d) +- : d(d) +-{ +-} +- +-} // namespace Digikam +diff --git a/libs/models/imagefiltermodelthreads.h b/libs/models/imagefiltermodelthreads.h +deleted file mode 100644 +index 83fa987..0000000 +--- a/libs/models/imagefiltermodelthreads.h ++++ /dev/null +@@ -1,100 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-11 +- * Description : Qt item model for database entries - private header +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGEFILTERMODELTHREADS_H +-#define IMAGEFILTERMODELTHREADS_H +- +-// Qt includes +- +-#include +- +-// Local includes +- +-#include "digikam_export.h" +-#include "workerobject.h" +- +-namespace Digikam +-{ +- +-class DIGIKAM_DATABASE_EXPORT ImageFilterModelWorker : public WorkerObject +-{ +- Q_OBJECT +- +-public: +- +- explicit ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d); +- +- bool checkVersion(const ImageFilterModelTodoPackage& package) +- { +- return d->version == package.version; +- } +- +-public Q_SLOTS: +- +- virtual void process(ImageFilterModelTodoPackage package) = 0; +- +-Q_SIGNALS: +- +- void processed(const ImageFilterModelTodoPackage& package); +- void discarded(const ImageFilterModelTodoPackage& package); +- +-protected: +- +- ImageFilterModel::ImageFilterModelPrivate* d; +-}; +- +-// ----------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT ImageFilterModelPreparer : public ImageFilterModelWorker +-{ +- Q_OBJECT +- +-public: +- +- explicit ImageFilterModelPreparer(ImageFilterModel::ImageFilterModelPrivate* const d) +- : ImageFilterModelWorker(d) +- { +- } +- +- void process(ImageFilterModelTodoPackage package); +-}; +- +-// ---------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT ImageFilterModelFilterer : public ImageFilterModelWorker +-{ +- Q_OBJECT +- +-public: +- +- explicit ImageFilterModelFilterer(ImageFilterModel::ImageFilterModelPrivate* const d) +- : ImageFilterModelWorker(d) +- { +- } +- +- void process(ImageFilterModelTodoPackage package); +-}; +- +-} // namespace Digikam +- +-#endif // IMAGEFILTERMODELTHREADS_H +diff --git a/libs/models/imagefiltersettings.cpp b/libs/models/imagefiltersettings.cpp +deleted file mode 100644 +index b61e7f9..0000000 +--- a/libs/models/imagefiltersettings.cpp ++++ /dev/null +@@ -1,952 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Filter values for use with ImageFilterModel +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011-2017 by Gilles Caulier +- * Copyright (C) 2010 by Andi Clemens +- * Copyright (C) 2011 by Michael G. Hansen +- * Copyright (C) 2014 by Mohamed Anwer +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagefiltersettings.h" +- +-// C++ includes +- +-#include +- +-// Qt includes +- +-#include +- +-// Local includes +- +-#include "digikam_debug.h" +-#include "coredbfields.h" +-#include "digikam_globals.h" +-#include "imageinfo.h" +-#include "tagscache.h" +-#include "versionmanagersettings.h" +- +-namespace Digikam +-{ +- +-ImageFilterSettings::ImageFilterSettings() +-{ +- m_untaggedFilter = false; +- m_isUnratedExcluded = false; +- m_ratingFilter = 0; +- m_mimeTypeFilter = MimeFilter::AllFiles; +- m_ratingCond = GreaterEqualCondition; +- m_matchingCond = OrCondition; +- m_geolocationCondition = GeolocationNoFilter; +-} +- +-DatabaseFields::Set ImageFilterSettings::watchFlags() const +-{ +- DatabaseFields::Set set; +- +- if (isFilteringByDay()) +- { +- set |= DatabaseFields::CreationDate; +- } +- +- if (isFilteringByText()) +- { +- set |= DatabaseFields::Name; +- set |= DatabaseFields::Comment; +- } +- +- if (isFilteringByRating()) +- { +- set |= DatabaseFields::Rating; +- } +- +- if (isFilteringByTypeMime()) +- { +- set |= DatabaseFields::Category; +- set |= DatabaseFields::Format; +- } +- +- if (isFilteringByGeolocation()) +- { +- set |= DatabaseFields::ImagePositionsAll; +- } +- +- if (isFilteringByColorLabels()) +- { +- set |= DatabaseFields::ColorLabel; +- } +- +- if (isFilteringByPickLabels()) +- { +- set |= DatabaseFields::PickLabel; +- } +- +- return set; +-} +- +-bool ImageFilterSettings::isFilteringByDay() const +-{ +- if (!m_dayFilter.isEmpty()) +- { +- return true; +- } +- +- return false; +-} +- +-bool ImageFilterSettings::isFilteringByTags() const +-{ +- if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter) +- { +- return true; +- } +- +- return false; +-} +- +-bool ImageFilterSettings::isFilteringByColorLabels() const +-{ +- if (!m_colorLabelTagFilter.isEmpty()) +- { +- return true; +- } +- +- return false; +-} +- +-bool ImageFilterSettings::isFilteringByPickLabels() const +-{ +- if (!m_pickLabelTagFilter.isEmpty()) +- { +- return true; +- } +- +- return false; +-} +- +-bool ImageFilterSettings::isFilteringByText() const +-{ +- if (!m_textFilterSettings.text.isEmpty()) +- { +- return true; +- } +- +- return false; +-} +- +-bool ImageFilterSettings::isFilteringByTypeMime() const +-{ +- if (m_mimeTypeFilter != MimeFilter::AllFiles) +- { +- return true; +- } +- +- return false; +-} +- +-bool ImageFilterSettings::isFilteringByGeolocation() const +-{ +- return (m_geolocationCondition != GeolocationNoFilter); +-} +- +-bool ImageFilterSettings::isFilteringByRating() const +-{ +- if (m_ratingFilter != 0 || m_ratingCond != GreaterEqualCondition || m_isUnratedExcluded) +- { +- return true; +- } +- +- return false; +-} +- +-bool ImageFilterSettings::isFilteringInternally() const +-{ +- return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty()); +-} +- +-bool ImageFilterSettings::isFiltering() const +-{ +- return isFilteringByDay() || +- isFilteringByTags() || +- isFilteringByText() || +- isFilteringByRating() || +- isFilteringByTypeMime() || +- isFilteringByColorLabels() || +- isFilteringByPickLabels() || +- isFilteringByGeolocation(); +-} +- +-void ImageFilterSettings::setDayFilter(const QList& days) +-{ +- m_dayFilter.clear(); +- +- for (QList::const_iterator it = days.constBegin(); it != days.constEnd(); ++it) +- { +- m_dayFilter.insert(*it, true); +- } +-} +- +-void ImageFilterSettings::setTagFilter(const QList& includedTags, +- const QList& excludedTags, +- MatchingCondition matchingCondition, +- bool showUnTagged, +- const QList& clTagIds, +- const QList& plTagIds) +-{ +- m_includeTagFilter = includedTags; +- m_excludeTagFilter = excludedTags; +- m_matchingCond = matchingCondition; +- m_untaggedFilter = showUnTagged; +- m_colorLabelTagFilter = clTagIds; +- m_pickLabelTagFilter = plTagIds; +-} +- +-void ImageFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded) +-{ +- m_ratingFilter = rating; +- m_ratingCond = ratingCondition; +- m_isUnratedExcluded = isUnratedExcluded; +-} +- +-void ImageFilterSettings::setMimeTypeFilter(int mime) +-{ +- m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime; +-} +- +-void ImageFilterSettings::setGeolocationFilter(const GeolocationCondition& condition) +-{ +- m_geolocationCondition = condition; +-} +- +-void ImageFilterSettings::setTextFilter(const SearchTextFilterSettings& settings) +-{ +- m_textFilterSettings = settings; +-} +- +-void ImageFilterSettings::setTagNames(const QHash& hash) +-{ +- m_tagNameHash = hash; +-} +- +-void ImageFilterSettings::setAlbumNames(const QHash& hash) +-{ +- m_albumNameHash = hash; +-} +- +-void ImageFilterSettings::setUrlWhitelist(const QList& urlList, const QString& id) +-{ +- if (urlList.isEmpty()) +- { +- m_urlWhitelists.remove(id); +- } +- else +- { +- m_urlWhitelists.insert(id, urlList); +- } +-} +- +-void ImageFilterSettings::setIdWhitelist(const QList& idList, const QString& id) +-{ +- if (idList.isEmpty()) +- { +- m_idWhitelists.remove(id); +- } +- else +- { +- m_idWhitelists.insert(id, idList); +- } +-} +- +-template +-bool containsAnyOf(const ContainerA& listA, const ContainerB& listB) +-{ +- foreach (const typename ContainerA::value_type& a, listA) +- { +- if (listB.contains(a)) +- { +- return true; +- } +- } +- return false; +-} +- +-template +-bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception) +-{ +- foreach (const typename ContainerB::value_type& n, noneOfList) +- { +- if (n != exception && list.contains(n)) +- { +- return false; +- } +- } +- return true; +-} +- +-bool ImageFilterSettings::matches(const ImageInfo& info, bool* const foundText) const +-{ +- if (foundText) +- { +- *foundText = false; +- } +- +- if (!isFilteringInternally()) +- { +- return true; +- } +- +- bool match = false; +- +- if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty()) +- { +- QList tagIds = info.tagIds(); +- QList::const_iterator it; +- +- match = m_includeTagFilter.isEmpty(); +- +- if (m_matchingCond == OrCondition) +- { +- for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) +- { +- if (tagIds.contains(*it)) +- { +- match = true; +- break; +- } +- } +- +- match |= (m_untaggedFilter && tagIds.isEmpty()); +- } +- else // AND matching condition... +- { +- // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match +- if (!m_untaggedFilter) +- { +- for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) +- { +- if (!tagIds.contains(*it)) +- { +- break; +- } +- } +- +- if (it == m_includeTagFilter.end()) +- { +- match = true; +- } +- } +- } +- +- for (it = m_excludeTagFilter.begin(); it != m_excludeTagFilter.end(); ++it) +- { +- if (tagIds.contains(*it)) +- { +- match = false; +- break; +- } +- } +- } +- else if (m_untaggedFilter) +- { +- match = !TagsCache::instance()->containsPublicTags(info.tagIds()); +- } +- else +- { +- match = true; +- } +- +- //-- Filter by pick labels ------------------------------------------------ +- +- if (!m_pickLabelTagFilter.isEmpty()) +- { +- QList tagIds = info.tagIds(); +- bool matchPL = false; +- +- if (containsAnyOf(m_pickLabelTagFilter, tagIds)) +- { +- matchPL = true; +- } +- else if (!matchPL) +- { +- int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel); +- +- if (m_pickLabelTagFilter.contains(noPickLabelTagId)) +- { +- // Searching for "has no ColorLabel" requires special handling: +- // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag +- matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId); +- } +- } +- +- match &= matchPL; +- } +- +- //-- Filter by color labels ------------------------------------------------ +- +- if (!m_colorLabelTagFilter.isEmpty()) +- { +- QList tagIds = info.tagIds(); +- bool matchCL = false; +- +- if (containsAnyOf(m_colorLabelTagFilter, tagIds)) +- { +- matchCL = true; +- } +- else if (!matchCL) +- { +- int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel); +- +- if (m_colorLabelTagFilter.contains(noColorLabelTagId)) +- { +- // Searching for "has no ColorLabel" requires special handling: +- // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag +- matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId); +- } +- } +- +- match &= matchCL; +- } +- +- //-- Filter by date ----------------------------------------------------------- +- +- if (!m_dayFilter.isEmpty()) +- { +- match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime())); +- } +- +- //-- Filter by rating --------------------------------------------------------- +- +- if (m_ratingFilter >= 0) +- { +- // for now we treat -1 (no rating) just like a rating of 0. +- int rating = info.rating(); +- +- if (rating == -1) +- { +- rating = 0; +- } +- +- if(m_isUnratedExcluded && rating == 0) +- { +- match = false; +- } +- else +- { +- if (m_ratingCond == GreaterEqualCondition) +- { +- // If the rating is not >=, i.e it is <, then it does not match. +- if (rating < m_ratingFilter) +- { +- match = false; +- } +- } +- else if (m_ratingCond == EqualCondition) +- { +- // If the rating is not =, i.e it is !=, then it does not match. +- if (rating != m_ratingFilter) +- { +- match = false; +- } +- } +- else +- { +- // If the rating is not <=, i.e it is >, then it does not match. +- if (rating > m_ratingFilter) +- { +- match = false; +- } +- } +- } +- } +- +- // -- Filter by mime type ----------------------------------------------------- +- +- switch (m_mimeTypeFilter) +- { +- // info.format is a standardized string: Only one possibility per mime type +- case MimeFilter::ImageFiles: +- { +- if (info.category() != DatabaseItem::Image) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::JPGFiles: +- { +- if (info.format() != QLatin1String("JPG")) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::PNGFiles: +- { +- if (info.format() != QLatin1String("PNG")) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::TIFFiles: +- { +- if (info.format() != QLatin1String("TIFF")) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::DNGFiles: +- { +- if (info.format() != QLatin1String("RAW-DNG")) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::NoRAWFiles: +- { +- if (info.format().startsWith(QLatin1String("RAW"))) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::RAWFiles: +- { +- if (!info.format().startsWith(QLatin1String("RAW"))) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::MoviesFiles: +- { +- if (info.category() != DatabaseItem::Video) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::AudioFiles: +- { +- if (info.category() != DatabaseItem::Audio) +- { +- match = false; +- } +- +- break; +- } +- case MimeFilter::RasterFiles: +- { +- if (info.format() != QLatin1String("PSD") && // Adobe Photoshop Document +- info.format() != QLatin1String("PSB") && // Adobe Photoshop Big +- info.format() != QLatin1String("XCF") && // Gimp +- info.format() != QLatin1String("KRA") && // Krita +- info.format() != QLatin1String("ORA") // Open Raster +- ) +- { +- match = false; +- } +- +- break; +- } +- default: +- { +- // All Files: do nothing... +- break; +- } +- } +- +- //-- Filter by geolocation ---------------------------------------------------- +- +- if (m_geolocationCondition!=GeolocationNoFilter) +- { +- if (m_geolocationCondition==GeolocationNoCoordinates) +- { +- if (info.hasCoordinates()) +- { +- match = false; +- } +- } +- else if (m_geolocationCondition==GeolocationHasCoordinates) +- { +- if (!info.hasCoordinates()) +- { +- match = false; +- } +- } +- } +- +- //-- Filter by text ----------------------------------------------------------- +- +- if (!m_textFilterSettings.text.isEmpty()) +- { +- bool textMatch = false; +- +- // Image name +- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName && +- info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) +- { +- textMatch = true; +- } +- +- // Image title +- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle && +- info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) +- { +- textMatch = true; +- } +- +- // Image comment +- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment && +- info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) +- { +- textMatch = true; +- } +- +- // Tag names +- foreach(int id, info.tagIds()) +- { +- if (m_textFilterSettings.textFields & SearchTextFilterSettings::TagName && +- m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) +- { +- textMatch = true; +- } +- } +- +- // Album names +- if (m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName && +- m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) +- { +- textMatch = true; +- } +- +- // Image Aspect Ratio +- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio) +- { +- QRegExp expRatio (QLatin1String("^\\d+:\\d+$")); +- QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$")); +- +- if (expRatio.indexIn(m_textFilterSettings.text) > -1 && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+")))) +- { +- QString trimmedTextFilterSettingsText = m_textFilterSettings.text; +- QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1String(":"), QString::SkipEmptyParts); +- +- if (numberStringList.length() == 2) +- { +- QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1); +- bool canConverseNum = false; +- bool canConverseDenom = false; +- int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10); +- +- if (canConverseNum && canConverseDenom) +- { +- if (fabs(info.aspectRatio() - (double)num / denom) < 0.1) +- textMatch = true; +- } +- } +- } +- else if (expFloat.indexIn(m_textFilterSettings.text) > -1) +- { +- QString trimmedTextFilterSettingsText = m_textFilterSettings.text; +- bool canConverse = false; +- double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse); +- +- if (canConverse) +- { +- if (fabs(info.aspectRatio() - ratio) < 0.1) +- textMatch = true; +- } +- } +- } +- +- // Image Pixel Size +- // See bug #341053 for details. +- +- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize) +- { +- QSize size = info.dimensions(); +- int pixelSize = size.height()*size.width(); +- QString text = m_textFilterSettings.text; +- +- if(text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && pixelSize > (text.remove(0,1)).toInt()) +- { +- textMatch = true; +- } +- else if(text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && pixelSize < (text.remove(0,1)).toInt()) +- { +- textMatch = true; +- } +- else if(text.contains(QRegExp(QLatin1String("^\\d+$"))) && pixelSize == text.toInt()) +- { +- textMatch = true; +- } +- } +- +- match &= textMatch; +- +- if (foundText) +- { +- *foundText = textMatch; +- } +- } +- +- // -- filter by URL-whitelists ------------------------------------------------ +- // NOTE: whitelists are always AND for now. +- +- if (match) +- { +- const QUrl url = info.fileUrl(); +- +- for (QHash>::const_iterator it = m_urlWhitelists.constBegin(); +- it!=m_urlWhitelists.constEnd(); ++it) +- { +- match = it->contains(url); +- +- if (!match) +- { +- break; +- } +- } +- } +- +- if (match) +- { +- const qlonglong id = info.id(); +- +- for (QHash >::const_iterator it = m_idWhitelists.constBegin(); +- it!=m_idWhitelists.constEnd(); ++it) +- { +- match = it->contains(id); +- +- if (!match) +- { +- break; +- } +- } +- } +- +- return match; +-} +- +-// ------------------------------------------------------------------------------------------------- +- +-VersionImageFilterSettings::VersionImageFilterSettings() +-{ +- m_includeTagFilter = 0; +- m_exceptionTagFilter = 0; +-} +- +-VersionImageFilterSettings::VersionImageFilterSettings(const VersionManagerSettings& settings) +-{ +- setVersionManagerSettings(settings); +-} +- +-bool VersionImageFilterSettings::operator==(const VersionImageFilterSettings& other) const +-{ +- return m_excludeTagFilter == other.m_excludeTagFilter && +- m_exceptionLists == other.m_exceptionLists; +-} +- +-bool VersionImageFilterSettings::matches(const ImageInfo& info) const +-{ +- if (!isFiltering()) +- { +- return true; +- } +- +- const qlonglong id = info.id(); +- +- for (QHash >::const_iterator it = m_exceptionLists.constBegin(); +- it != m_exceptionLists.constEnd(); ++it) +- { +- if (it->contains(id)) +- { +- return true; +- } +- } +- +- bool match = true; +- QList tagIds = info.tagIds(); +- +- if (!tagIds.contains(m_includeTagFilter)) +- { +- for (QList::const_iterator it = m_excludeTagFilter.begin(); +- it != m_excludeTagFilter.end(); ++it) +- { +- if (tagIds.contains(*it)) +- { +- match = false; +- break; +- } +- } +- } +- +- if (!match) +- { +- if (tagIds.contains(m_exceptionTagFilter)) +- { +- match = true; +- } +- } +- +- return match; +-} +- +-bool VersionImageFilterSettings::isHiddenBySettings(const ImageInfo& info) const +-{ +- QList tagIds = info.tagIds(); +- +- foreach(int tagId, m_excludeTagFilter) +- { +- if (tagIds.contains(tagId)) +- { +- return true; +- } +- } +- +- return false; +-} +- +-bool VersionImageFilterSettings::isExemptedBySettings(const ImageInfo& info) const +-{ +- return info.tagIds().contains(m_exceptionTagFilter); +-} +- +-void VersionImageFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings) +-{ +- m_excludeTagFilter.clear(); +- +- if (!settings.enabled) +- { +- return; +- } +- +- if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal)) +- { +- m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); +- } +- +- if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates)) +- { +- m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion()); +- } +- +- m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion()); +- m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible()); +-} +- +-void VersionImageFilterSettings::setExceptionList(const QList& idList, const QString& id) +-{ +- if (idList.isEmpty()) +- { +- m_exceptionLists.remove(id); +- } +- else +- { +- m_exceptionLists.insert(id, idList); +- } +-} +- +-bool VersionImageFilterSettings::isFiltering() const +-{ +- return !m_excludeTagFilter.isEmpty(); +-} +- +-bool VersionImageFilterSettings::isFilteringByTags() const +-{ +- return isFiltering(); +-} +- +-// ------------------------------------------------------------------------------------------------- +- +-GroupImageFilterSettings::GroupImageFilterSettings() +- : m_allOpen(false) +-{ +-} +- +-bool GroupImageFilterSettings::operator==(const GroupImageFilterSettings& other) const +-{ +- return (m_allOpen == other.m_allOpen && +- m_openGroups == other.m_openGroups); +-} +- +-bool GroupImageFilterSettings::matches(const ImageInfo& info) const +-{ +- if (m_allOpen) +- { +- return true; +- } +- +- if (info.isGrouped()) +- { +- return m_openGroups.contains(info.groupImage().id()); +- } +- return true; +-} +- +-void GroupImageFilterSettings::setOpen(qlonglong group, bool open) +-{ +- if (open) +- { +- m_openGroups << group; +- } +- else +- { +- m_openGroups.remove(group); +- } +-} +- +-bool GroupImageFilterSettings::isOpen(qlonglong group) const +-{ +- return m_openGroups.contains(group); +-} +- +-void GroupImageFilterSettings::setAllOpen(bool open) +-{ +- m_allOpen = open; +-} +- +-bool GroupImageFilterSettings::isAllOpen() const +-{ +- return m_allOpen; +-} +- +-bool GroupImageFilterSettings::isFiltering() const +-{ +- return !m_allOpen; +-} +- +-DatabaseFields::Set GroupImageFilterSettings::watchFlags() const +-{ +- return DatabaseFields::ImageRelations; +-} +- +-} // namespace Digikam +diff --git a/libs/models/imagefiltersettings.h b/libs/models/imagefiltersettings.h +deleted file mode 100644 +index 0e7beae..0000000 +--- a/libs/models/imagefiltersettings.h ++++ /dev/null +@@ -1,349 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Filter values for use with ImageFilterModel +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011-2017 by Gilles Caulier +- * Copyright (C) 2010 by Andi Clemens +- * Copyright (C) 2011 by Michael G. Hansen +- * Copyright (C) 2014 by Mohamed Anwer +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGEFILTERSETTINGS_H +-#define IMAGEFILTERSETTINGS_H +- +-// Qt includes +- +-#include +-#include +-#include +-#include +-#include +-#include +- +-// Local includes +- +-#include "searchtextbar.h" +-#include "mimefilter.h" +-#include "digikam_export.h" +- +-namespace Digikam +-{ +- +-class ImageInfo; +-class VersionManagerSettings; +- +-namespace DatabaseFields +-{ +- class Set; +-} +- +-// --------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT SearchTextFilterSettings : public SearchTextSettings +-{ +- +-public: +- +- enum TextFilterFields +- { +- None = 0x00, +- ImageName = 0x01, +- ImageTitle = 0x02, +- ImageComment = 0x04, +- TagName = 0x08, +- AlbumName = 0x10, +- ImageAspectRatio = 0x20, +- ImagePixelSize = 0x40, +- All = ImageName | ImageTitle | ImageComment | TagName | AlbumName | ImageAspectRatio | ImagePixelSize +- }; +- +-public: +- +- SearchTextFilterSettings() +- { +- textFields = None; +- } +- +- explicit SearchTextFilterSettings(const SearchTextSettings& settings) +- { +- caseSensitive = settings.caseSensitive; +- text = settings.text; +- textFields = None; +- } +- +- TextFilterFields textFields; +-}; +- +-// --------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT ImageFilterSettings +-{ +-public: +- +- ImageFilterSettings(); +- +- /** +- * Returns true if the given ImageInfo matches the filter criteria. +- * Optionally, foundText is set to true if it matched by text search. +- */ +- bool matches(const ImageInfo& info, bool* const foundText = 0) const; +- +-public: +- +- /// --- Tags filter --- +- +- /// Possible logical matching condition used to sort tags id. +- enum MatchingCondition +- { +- OrCondition, +- AndCondition +- }; +- +- void setTagFilter(const QList& includedTags, +- const QList& excludedTags, +- MatchingCondition matchingCond, +- bool showUnTagged, +- const QList& clTagIds, +- const QList& plTagIds); +- +-public: +- +- /// --- Rating filter --- +- +- /// Possible conditions used to filter rating: >=, =, <= +- enum RatingCondition +- { +- GreaterEqualCondition, +- EqualCondition, +- LessEqualCondition +- }; +- +- void setRatingFilter(int rating, RatingCondition ratingCond, bool isUnratedExcluded); +- +-public: +- +- /// --- Date filter --- +- void setDayFilter(const QList& days); +- +-public: +- +- /// --- Text filter --- +- void setTextFilter(const SearchTextFilterSettings& settings); +- void setTagNames(const QHash& tagNameHash); +- void setAlbumNames(const QHash& albumNameHash); +- +-public: +- +- /// --- Mime filter --- +- void setMimeTypeFilter(int mimeTypeFilter); +- +-public: +- +- /// --- Geolocation filter +- enum GeolocationCondition +- { +- GeolocationNoFilter = 0, +- GeolocationNoCoordinates = 1 << 1, +- GeolocationHasCoordinates = 1 << 2 +- }; +- +- void setGeolocationFilter(const GeolocationCondition& condition); +- +-public: +- +- /// Returns if the day is a filter criteria +- bool isFilteringByDay() const; +- +- /// Returns if the type mime is a filter criteria +- bool isFilteringByTypeMime() const; +- +- /// Returns whether geolocation is a filter criteria +- bool isFilteringByGeolocation() const; +- +- /// Returns if the rating is a filter criteria +- bool isFilteringByRating() const; +- +- /// Returns if the pick labels is a filter criteria +- bool isFilteringByPickLabels() const; +- +- /// Returns if the color labels is a filter criteria +- bool isFilteringByColorLabels() const; +- +- /// Returns if the tag is a filter criteria +- bool isFilteringByTags() const; +- +- /// Returns if the text (including comment) is a filter criteria +- bool isFilteringByText() const; +- +- /// Returns if images will be filtered by these criteria at all +- bool isFiltering() const; +- +-public: +- +- /// --- URL whitelist filter +- void setUrlWhitelist(const QList& urlList, const QString& id); +- +-public: +- +- /// --- ID whitelist filter +- void setIdWhitelist(const QList& idList, const QString& id); +- +-public: +- +- /// --- Change notification --- +- +- /** Returns database fields a change in which would affect the current filtering. +- * To find out if an image tag change affects filtering, test isFilteringByTags(). +- * The text filter will also be affected by changes in tags and album names. +- */ +- DatabaseFields::Set watchFlags() const; +- +-private: +- +- /** +- * @brief Returns whether some internal filtering (whitelist by id or URL) or normal filtering is going on +- */ +- bool isFilteringInternally() const; +- +-private: +- +- /// --- Tags filter --- +- bool m_untaggedFilter; +- QList m_includeTagFilter; +- QList m_excludeTagFilter; +- MatchingCondition m_matchingCond; +- QList m_colorLabelTagFilter; +- QList m_pickLabelTagFilter; +- +- /// --- Rating filter --- +- int m_ratingFilter; +- RatingCondition m_ratingCond; +- bool m_isUnratedExcluded; +- +- /// --- Date filter --- +- QMap m_dayFilter; +- +- /// --- Text filter --- +- SearchTextFilterSettings m_textFilterSettings; +- +- /// Helpers for text search: Set these if you want to search album or tag names with text search +- QHash m_tagNameHash; +- QHash m_albumNameHash; +- +- /// --- Mime filter --- +- MimeFilter::TypeMimeFilter m_mimeTypeFilter; +- +- /// --- Geolocation filter +- GeolocationCondition m_geolocationCondition; +- +- /// --- URL whitelist filter +- QHash> m_urlWhitelists; +- +- /// --- ID whitelist filter +- QHash > m_idWhitelists; +-}; +- +-// --------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT VersionImageFilterSettings +-{ +-public: +- +- VersionImageFilterSettings(); +- explicit VersionImageFilterSettings(const VersionManagerSettings& settings); +- +- bool operator==(const VersionImageFilterSettings& other) const; +- +- /** +- * Returns true if the given ImageInfo matches the filter criteria. +- */ +- bool matches(const ImageInfo& info) const; +- +- bool isHiddenBySettings(const ImageInfo& info) const; +- bool isExemptedBySettings(const ImageInfo& info) const; +- +- /// --- Tags filter --- +- +- void setVersionManagerSettings(const VersionManagerSettings& settings); +- +- /** +- * Add list with exceptions: These images will be exempted from filtering by this filter +- */ +- void setExceptionList(const QList& idlist, const QString& id); +- +- /// Returns if images will be filtered by these criteria at all +- bool isFiltering() const; +- +- /// Returns if the tag is a filter criteria +- bool isFilteringByTags() const; +- +- /// DatabaseFields::Set watchFlags() const: Would return 0 +- +-protected: +- +- QList m_excludeTagFilter; +- int m_includeTagFilter; +- int m_exceptionTagFilter; +- QHash > m_exceptionLists; +-}; +- +-// --------------------------------------------------------------------------------------- +- +-class DIGIKAM_DATABASE_EXPORT GroupImageFilterSettings +-{ +-public: +- +- GroupImageFilterSettings(); +- +- bool operator==(const GroupImageFilterSettings& other) const; +- +- /** +- * Returns true if the given ImageInfo matches the filter criteria. +- */ +- bool matches(const ImageInfo& info) const; +- +- /** +- * Open or close a group. +- */ +- void setOpen(qlonglong group, bool open); +- bool isOpen(qlonglong group) const; +- +- /** +- * Open all groups +- */ +- void setAllOpen(bool open); +- bool isAllOpen() const; +- +- /// Returns if images will be filtered by these criteria at all +- bool isFiltering() const; +- +- DatabaseFields::Set watchFlags() const; +- +-protected: +- +- bool m_allOpen; +- QSet m_openGroups; +-}; +- +-} // namespace Digikam +- +-Q_DECLARE_METATYPE(Digikam::ImageFilterSettings::GeolocationCondition) +- +-#endif // IMAGEFILTERSETTINGS_H +diff --git a/libs/models/imagelistmodel.cpp b/libs/models/imagelistmodel.cpp +deleted file mode 100644 +index fafce34..0000000 +--- a/libs/models/imagelistmodel.cpp ++++ /dev/null +@@ -1,70 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2010-12-06 +- * Description : An image model based on a static list +- * +- * Copyright (C) 2010-2011 by Marcel Wiesweg +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagelistmodel.h" +- +-// Local includes +- +-#include "digikam_debug.h" +-#include "coredbaccess.h" +-#include "coredbchangesets.h" +-#include "coredbwatch.h" +-#include "imageinfo.h" +-#include "imageinfolist.h" +- +-namespace Digikam +-{ +- +-ImageListModel::ImageListModel(QObject* parent) +- : ImageThumbnailModel(parent) +-{ +- connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)), +- this, SLOT(slotCollectionImageChange(CollectionImageChangeset))); +-} +- +-ImageListModel::~ImageListModel() +-{ +-} +- +-void ImageListModel::slotCollectionImageChange(const CollectionImageChangeset& changeset) +-{ +- if (isEmpty()) +- { +- return; +- } +- +- switch (changeset.operation()) +- { +- case CollectionImageChangeset::Added: +- break; +- case CollectionImageChangeset::Removed: +- case CollectionImageChangeset::RemovedAll: +- removeImageInfos(ImageInfoList(changeset.ids())); +- break; +- +- default: +- break; +- } +-} +- +-} // namespace Digikam +diff --git a/libs/models/imagelistmodel.h b/libs/models/imagelistmodel.h +deleted file mode 100644 +index a225b1b..0000000 +--- a/libs/models/imagelistmodel.h ++++ /dev/null +@@ -1,63 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2010-12-06 +- * Description : An image model based on a static list +- * +- * Copyright (C) 2010-2011 by Marcel Wiesweg +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGELISTMODEL_H +-#define IMAGELISTMODEL_H +- +-// Local includes +- +-#include "imagethumbnailmodel.h" +-#include "digikam_export.h" +- +-namespace Digikam +-{ +- +-class ImageChangeset; +-class CollectionImageChangeset; +- +-class DIGIKAM_DATABASE_EXPORT ImageListModel : public ImageThumbnailModel +-{ +- Q_OBJECT +- +-public: +- +- explicit ImageListModel(QObject* parent = 0); +- ~ImageListModel(); +- +- // NOTE: necessary methods to add and remove ImageInfos to the model are inherited from ImageModel +- +-Q_SIGNALS: +- +- /** +- * Emitted when images are removed from the model because they are removed in the database +- */ +- void imageInfosRemoved(const QList& infos); +- +-protected Q_SLOTS: +- +- void slotCollectionImageChange(const CollectionImageChangeset& changeset); +-}; +- +-} // namespace Digikam +- +-#endif // IMAGELISTMODEL_H +diff --git a/libs/models/imagemodel.cpp b/libs/models/imagemodel.cpp +deleted file mode 100644 +index 41b43cf..0000000 +--- a/libs/models/imagemodel.cpp ++++ /dev/null +@@ -1,1368 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagemodel.h" +- +-// Qt includes +- +-#include +-#include +- +-// Local includes +- +-#include "digikam_debug.h" +-#include "coredbchangesets.h" +-#include "coredbfields.h" +-#include "coredbwatch.h" +-#include "imageinfo.h" +-#include "imageinfolist.h" +-#include "abstractitemdragdrophandler.h" +- +-namespace Digikam +-{ +- +-class ImageModel::Private +-{ +-public: +- +- Private() +- { +- preprocessor = 0; +- keepFilePathCache = false; +- sendRemovalSignals = false; +- incrementalUpdater = 0; +- refreshing = false; +- reAdding = false; +- incrementalRefreshRequested = false; +- } +- +- ImageInfoList infos; +- QList extraValues; +- QHash idHash; +- +- bool keepFilePathCache; +- QHash filePathHash; +- +- bool sendRemovalSignals; +- +- QObject* preprocessor; +- bool refreshing; +- bool reAdding; +- bool incrementalRefreshRequested; +- +- DatabaseFields::Set watchFlags; +- +- class ImageModelIncrementalUpdater* incrementalUpdater; +- +- ImageInfoList pendingInfos; +- QList pendingExtraValues; +- +- inline bool isValid(const QModelIndex& index) +- { +- if (!index.isValid()) +- { +- return false; +- } +- +- if (index.row() < 0 || index.row() >= infos.size()) +- { +- qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index; +- return false; +- } +- +- return true; +- } +- inline bool extraValueValid(const QModelIndex& index) +- { +- // we assume isValid() being called before, no duplicate checks +- if (index.row() >= extraValues.size()) +- { +- qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index; +- return false; +- } +- +- return true; +- } +-}; +- +-typedef QPair IntPair; // to make foreach macro happy +-typedef QList IntPairList; +- +-class ImageModelIncrementalUpdater +-{ +-public: +- +- explicit ImageModelIncrementalUpdater(ImageModel::Private* d); +- +- void appendInfos(const QList& infos, const QList& extraValues); +- void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved); +- QList oldIndexes(); +- +- static QList toContiguousPairs(const QList& ids); +- +-public: +- +- QHash oldIds; +- QList oldExtraValues; +- QList newInfos; +- QList newExtraValues; +- QList modelRemovals; +-}; +- +-ImageModel::ImageModel(QObject* parent) +- : QAbstractListModel(parent), +- d(new Private) +-{ +- connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), +- this, SLOT(slotImageChange(ImageChangeset))); +- +- connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), +- this, SLOT(slotImageTagChange(ImageTagChangeset))); +-} +- +-ImageModel::~ImageModel() +-{ +- delete d->incrementalUpdater; +- delete d; +-} +- +-// ------------ Access methods ------------- +- +-void ImageModel::setKeepsFilePathCache(bool keepCache) +-{ +- d->keepFilePathCache = keepCache; +-} +- +-bool ImageModel::keepsFilePathCache() const +-{ +- return d->keepFilePathCache; +-} +- +-bool ImageModel::isEmpty() const +-{ +- return d->infos.isEmpty(); +-} +- +-void ImageModel::setWatchFlags(const DatabaseFields::Set& set) +-{ +- d->watchFlags = set; +-} +- +-ImageInfo ImageModel::imageInfo(const QModelIndex& index) const +-{ +- if (!d->isValid(index)) +- { +- return ImageInfo(); +- } +- +- return d->infos.at(index.row()); +-} +- +-ImageInfo& ImageModel::imageInfoRef(const QModelIndex& index) const +-{ +- return d->infos[index.row()]; +-} +- +-qlonglong ImageModel::imageId(const QModelIndex& index) const +-{ +- if (!d->isValid(index)) +- { +- return 0; +- } +- +- return d->infos.at(index.row()).id(); +-} +- +-QList ImageModel::imageInfos(const QList& indexes) const +-{ +- QList infos; +- +- foreach(const QModelIndex& index, indexes) +- { +- infos << imageInfo(index); +- } +- +- return infos; +-} +- +-QList ImageModel::imageIds(const QList& indexes) const +-{ +- QList ids; +- +- foreach(const QModelIndex& index, indexes) +- { +- ids << imageId(index); +- } +- +- return ids; +-} +- +-ImageInfo ImageModel::imageInfo(int row) const +-{ +- if (row >= d->infos.size()) +- { +- return ImageInfo(); +- } +- +- return d->infos.at(row); +-} +- +-ImageInfo& ImageModel::imageInfoRef(int row) const +-{ +- return d->infos[row]; +-} +- +-qlonglong ImageModel::imageId(int row) const +-{ +- if (row < 0 || row >= d->infos.size()) +- { +- return -1; +- } +- +- return d->infos.at(row).id(); +-} +- +-QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info) const +-{ +- return indexForImageId(info.id()); +-} +- +-QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const +-{ +- return indexForImageId(info.id(), extraValue); +-} +- +-QList ImageModel::indexesForImageInfo(const ImageInfo& info) const +-{ +- return indexesForImageId(info.id()); +-} +- +-QModelIndex ImageModel::indexForImageId(qlonglong id) const +-{ +- int index = d->idHash.value(id, -1); +- +- if (index != -1) +- { +- return createIndex(index, 0); +- } +- +- return QModelIndex(); +-} +- +-QModelIndex ImageModel::indexForImageId(qlonglong id, const QVariant& extraValue) const +-{ +- if (d->extraValues.isEmpty()) +- return indexForImageId(id); +- +- QHash::const_iterator it; +- +- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) +- { +- if (d->extraValues.at(it.value()) == extraValue) +- return createIndex(it.value(), 0); +- } +- +- return QModelIndex(); +-} +- +-QList ImageModel::indexesForImageId(qlonglong id) const +-{ +- QList indexes; +- QHash::const_iterator it; +- +- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) +- { +- indexes << createIndex(it.value(), 0); +- } +- +- return indexes; +-} +- +-int ImageModel::numberOfIndexesForImageInfo(const ImageInfo& info) const +-{ +- return numberOfIndexesForImageId(info.id()); +-} +- +-int ImageModel::numberOfIndexesForImageId(qlonglong id) const +-{ +- if (d->extraValues.isEmpty()) +- { +- return 0; +- } +- +- int count = 0; +- QHash::const_iterator it; +- +- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) +- { +- ++count; +- } +- +- return count; +-} +- +-// static method +-ImageInfo ImageModel::retrieveImageInfo(const QModelIndex& index) +-{ +- if (!index.isValid()) +- { +- return ImageInfo(); +- } +- +- ImageModel* const model = index.data(ImageModelPointerRole).value(); +- int row = index.data(ImageModelInternalId).toInt(); +- +- if (!model) +- { +- return ImageInfo(); +- } +- +- return model->imageInfo(row); +-} +- +-// static method +-qlonglong ImageModel::retrieveImageId(const QModelIndex& index) +-{ +- if (!index.isValid()) +- { +- return 0; +- } +- +- ImageModel* const model = index.data(ImageModelPointerRole).value(); +- int row = index.data(ImageModelInternalId).toInt(); +- +- if (!model) +- { +- return 0; +- } +- +- return model->imageId(row); +-} +- +-QModelIndex ImageModel::indexForPath(const QString& filePath) const +-{ +- if (d->keepFilePathCache) +- { +- return indexForImageId(d->filePathHash.value(filePath)); +- } +- else +- { +- const int size = d->infos.size(); +- +- for (int i=0; iinfos.at(i).filePath() == filePath) +- { +- return createIndex(i, 0); +- } +- } +- } +- +- return QModelIndex(); +-} +- +-QList ImageModel::indexesForPath(const QString& filePath) const +-{ +- if (d->keepFilePathCache) +- { +- return indexesForImageId(d->filePathHash.value(filePath)); +- } +- else +- { +- QList indexes; +- const int size = d->infos.size(); +- +- for (int i=0; iinfos.at(i).filePath() == filePath) +- { +- indexes << createIndex(i, 0); +- } +- } +- +- return indexes; +- } +-} +- +-ImageInfo ImageModel::imageInfo(const QString& filePath) const +-{ +- if (d->keepFilePathCache) +- { +- qlonglong id = d->filePathHash.value(filePath); +- +- if (id) +- { +- int index = d->idHash.value(id, -1); +- +- if (index != -1) +- { +- return d->infos.at(index); +- } +- } +- } +- else +- { +- foreach(const ImageInfo& info, d->infos) +- { +- if (info.filePath() == filePath) +- { +- return info; +- } +- } +- } +- +- return ImageInfo(); +-} +- +-QList ImageModel::imageInfos(const QString& filePath) const +-{ +- QList infos; +- +- if (d->keepFilePathCache) +- { +- qlonglong id = d->filePathHash.value(filePath); +- +- if (id) +- { +- foreach(int index, d->idHash.values(id)) +- { +- infos << d->infos.at(index); +- } +- } +- } +- else +- { +- foreach(const ImageInfo& info, d->infos) +- { +- if (info.filePath() == filePath) +- { +- infos << info; +- } +- } +- } +- +- return infos; +-} +- +-void ImageModel::addImageInfo(const ImageInfo& info) +-{ +- addImageInfos(QList() << info, QList()); +-} +- +-void ImageModel::addImageInfos(const QList& infos) +-{ +- addImageInfos(infos, QList()); +-} +- +-void ImageModel::addImageInfos(const QList& infos, const QList& extraValues) +-{ +- if (infos.isEmpty()) +- { +- return; +- } +- +- if (d->incrementalUpdater) +- { +- d->incrementalUpdater->appendInfos(infos, extraValues); +- } +- else +- { +- appendInfos(infos, extraValues); +- } +-} +- +-void ImageModel::addImageInfoSynchronously(const ImageInfo& info) +-{ +- addImageInfosSynchronously(QList() << info, QList()); +-} +- +-void ImageModel::addImageInfosSynchronously(const QList& infos) +-{ +- addImageInfos(infos, QList()); +-} +- +-void ImageModel::addImageInfosSynchronously(const QList& infos, const QList& extraValues) +-{ +- if (infos.isEmpty()) +- { +- return; +- } +- +- publiciseInfos(infos, extraValues); +- emit processAdded(infos, extraValues); +-} +- +-void ImageModel::ensureHasImageInfo(const ImageInfo& info) +-{ +- ensureHasImageInfos(QList() << info, QList()); +-} +- +-void ImageModel::ensureHasImageInfos(const QList& infos) +-{ +- ensureHasImageInfos(infos, QList()); +-} +- +-void ImageModel::ensureHasImageInfos(const QList& infos, const QList& extraValues) +-{ +- if (extraValues.isEmpty()) +- { +- if (!d->pendingExtraValues.isEmpty()) +- { +- qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; +- return; +- } +- } +- else +- { +- if (d->pendingInfos.size() != d->pendingExtraValues.size()) +- { +- qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos."; +- return; +- } +- } +- +- d->pendingInfos << infos; +- d->pendingExtraValues << extraValues; +- cleanSituationChecks(); +-} +- +-void ImageModel::clearImageInfos() +-{ +- d->infos.clear(); +- d->extraValues.clear(); +- d->idHash.clear(); +- d->filePathHash.clear(); +- delete d->incrementalUpdater; +- d->incrementalUpdater = 0; +- d->pendingInfos.clear(); +- d->pendingExtraValues.clear(); +- d->refreshing = false; +- d->reAdding = false; +- d->incrementalRefreshRequested = false; +- +- beginResetModel(); +- endResetModel(); +- +- imageInfosCleared(); +-} +- +-void ImageModel::setImageInfos(const QList& infos) +-{ +- clearImageInfos(); +- addImageInfos(infos); +-} +- +-QList ImageModel::imageInfos() const +-{ +- return d->infos; +-} +- +-QList ImageModel::imageIds() const +-{ +- return d->idHash.keys(); +-} +- +-bool ImageModel::hasImage(qlonglong id) const +-{ +- return d->idHash.contains(id); +-} +- +-bool ImageModel::hasImage(const ImageInfo& info) const +-{ +- return d->idHash.contains(info.id()); +-} +- +-bool ImageModel::hasImage(const ImageInfo& info, const QVariant& extraValue) const +-{ +- return hasImage(info.id(), extraValue); +-} +- +-bool ImageModel::hasImage(qlonglong id, const QVariant& extraValue) const +-{ +- if (d->extraValues.isEmpty()) +- return hasImage(id); +- +- QHash::const_iterator it; +- +- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it) +- { +- if (d->extraValues.at(it.value()) == extraValue) +- return true; +- } +- +- return false;; +-} +- +-QList ImageModel::uniqueImageInfos() const +-{ +- if (d->extraValues.isEmpty()) +- { +- return d->infos; +- } +- +- QList uniqueInfos; +- const int size = d->infos.size(); +- +- for (int i=0; iinfos.at(i); +- +- if (d->idHash.value(info.id()) == i) +- { +- uniqueInfos << info; +- } +- } +- +- return uniqueInfos; +-} +- +-void ImageModel::emitDataChangedForAll() +-{ +- if (d->infos.isEmpty()) +- { +- return; +- } +- +- QModelIndex first = createIndex(0, 0); +- QModelIndex last = createIndex(d->infos.size() - 1, 0); +- emit dataChanged(first, last); +-} +- +-void ImageModel::emitDataChangedForSelection(const QItemSelection& selection) +-{ +- if (!selection.isEmpty()) +- { +- foreach(const QItemSelectionRange& range, selection) +- { +- emit dataChanged(range.topLeft(), range.bottomRight()); +- } +- } +-} +- +-void ImageModel::ensureHasGroupedImages(const ImageInfo& groupLeader) +-{ +- ensureHasImageInfos(groupLeader.groupedImages()); +-} +- +-// ------------ Preprocessing ------------- +- +-void ImageModel::setPreprocessor(QObject* preprocessor) +-{ +- unsetPreprocessor(d->preprocessor); +- d->preprocessor = preprocessor; +-} +- +-void ImageModel::unsetPreprocessor(QObject* preprocessor) +-{ +- if (preprocessor && d->preprocessor == preprocessor) +- { +- disconnect(this, SIGNAL(preprocess(QList,QList)), 0, 0); +- disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList,QList))); +- disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished())); +- } +-} +- +-void ImageModel::appendInfos(const QList& infos, const QList& extraValues) +-{ +- if (infos.isEmpty()) +- { +- return; +- } +- +- if (d->preprocessor) +- { +- d->reAdding = true; +- emit preprocess(infos, extraValues); +- } +- else +- { +- publiciseInfos(infos, extraValues); +- } +-} +- +-void ImageModel::appendInfosChecked(const QList& infos, const QList& extraValues) +-{ +- // This method does deduplication. It is private because in context of readding or refreshing it is of no use. +- +- if (extraValues.isEmpty()) +- { +- QList checkedInfos; +- +- foreach (const ImageInfo& info, infos) +- { +- if (!hasImage(info)) +- { +- checkedInfos << info; +- } +- } +- +- appendInfos(checkedInfos, QList()); +- } +- else +- { +- QList checkedInfos; +- QList checkedExtraValues; +- const int size = infos.size(); +- +- for (int i=0; i& infos, const QList& extraValues) +-{ +- // addImageInfos -> appendInfos -> preprocessor -> reAddImageInfos +- publiciseInfos(infos, extraValues); +-} +- +-void ImageModel::reAddingFinished() +-{ +- d->reAdding = false; +- cleanSituationChecks(); +-} +- +-void ImageModel::startRefresh() +-{ +- d->refreshing = true; +-} +- +-void ImageModel::finishRefresh() +-{ +- d->refreshing = false; +- cleanSituationChecks(); +-} +- +-bool ImageModel::isRefreshing() const +-{ +- return d->refreshing; +-} +- +-void ImageModel::cleanSituationChecks() +-{ +- // For starting an incremental refresh we want a clear situation: +- // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(), +- // any batches sent to preprocessor for re-adding have been re-added. +- if (d->refreshing || d->reAdding) +- { +- return; +- } +- +- if (!d->pendingInfos.isEmpty()) +- { +- appendInfosChecked(d->pendingInfos, d->pendingExtraValues); +- d->pendingInfos.clear(); +- d->pendingExtraValues.clear(); +- cleanSituationChecks(); +- return; +- } +- +- if (d->incrementalRefreshRequested) +- { +- d->incrementalRefreshRequested = false; +- emit readyForIncrementalRefresh(); +- } +- else +- { +- emit allRefreshingFinished(); +- } +-} +- +-void ImageModel::publiciseInfos(const QList& infos, const QList& extraValues) +-{ +- if (infos.isEmpty()) +- { +- return; +- } +- +- Q_ASSERT(infos.size() == extraValues.size() || (extraValues.isEmpty() && d->extraValues.isEmpty())); +- +- emit imageInfosAboutToBeAdded(infos); +- const int firstNewIndex = d->infos.size(); +- const int lastNewIndex = d->infos.size() + infos.size() - 1; +- beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex); +- d->infos << infos; +- d->extraValues << extraValues; +- +- for (int i=firstNewIndex; i<=lastNewIndex; ++i) +- { +- const ImageInfo& info = d->infos.at(i); +- qlonglong id = info.id(); +- d->idHash.insertMulti(id, i); +- +- if (d->keepFilePathCache) +- { +- d->filePathHash[info.filePath()] = id; +- } +- } +- +- endInsertRows(); +- emit imageInfosAdded(infos); +-} +- +-void ImageModel::requestIncrementalRefresh() +-{ +- if (d->reAdding) +- { +- d->incrementalRefreshRequested = true; +- } +- else +- { +- emit readyForIncrementalRefresh(); +- } +-} +- +-bool ImageModel::hasIncrementalRefreshPending() const +-{ +- return d->incrementalRefreshRequested; +-} +- +-void ImageModel::startIncrementalRefresh() +-{ +- delete d->incrementalUpdater; +- +- d->incrementalUpdater = new ImageModelIncrementalUpdater(d); +-} +- +-void ImageModel::finishIncrementalRefresh() +-{ +- if (!d->incrementalUpdater) +- { +- return; +- } +- +- // remove old entries +- QList > pairs = d->incrementalUpdater->oldIndexes(); +- removeRowPairs(pairs); +- +- // add new indexes +- appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues); +- +- delete d->incrementalUpdater; +- d->incrementalUpdater = 0; +-} +- +-void ImageModel::removeIndex(const QModelIndex& index) +-{ +- removeIndexes(QList() << index); +-} +- +-void ImageModel::removeIndexes(const QList& indexes) +-{ +- QList listIndexes; +- +- foreach(const QModelIndex& index, indexes) +- { +- if (d->isValid(index)) +- { +- listIndexes << index.row(); +- } +- } +- +- if (listIndexes.isEmpty()) +- { +- return; +- } +- +- removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); +-} +- +-void ImageModel::removeImageInfo(const ImageInfo& info) +-{ +- removeImageInfos(QList() << info); +-} +- +-void ImageModel::removeImageInfos(const QList& infos) +-{ +- QList listIndexes; +- +- foreach(const ImageInfo& info, infos) +- { +- QModelIndex index = indexForImageId(info.id()); +- +- if (index.isValid()) +- { +- listIndexes << index.row(); +- } +- } +- removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes)); +-} +- +-void ImageModel::removeImageInfos(const QList& infos, const QList& extraValues) +-{ +- if (extraValues.isEmpty()) +- { +- removeImageInfos(infos); +- return; +- } +- +- QList listIndexes; +- +- for (int i=0; isendRemovalSignals = send; +-} +- +-template +-static bool pairsContain(const List& list, T value) +-{ +- typename List::const_iterator middle; +- typename List::const_iterator begin = list.begin(); +- typename List::const_iterator end = list.end(); +- int n = int(end - begin); +- int half; +- +- while (n > 0) +- { +- half = n >> 1; +- middle = begin + half; +- +- if (middle->first <= value && middle->second >= value) +- { +- return true; +- } +- else if (middle->second < value) +- { +- begin = middle + 1; +- n -= half + 1; +- } +- else +- { +- n = half; +- } +- } +- +- return false; +-} +- +-void ImageModel::removeRowPairsWithCheck(const QList >& toRemove) +-{ +- if (d->incrementalUpdater) +- { +- d->incrementalUpdater->aboutToBeRemovedInModel(toRemove); +- } +- +- removeRowPairs(toRemove); +-} +- +-void ImageModel::removeRowPairs(const QList >& toRemove) +-{ +- if (toRemove.isEmpty()) +- { +- return; +- } +- +- // Remove old indexes +- // Keep in mind that when calling beginRemoveRows all structures announced to be removed +- // must still be valid, and this includes our hashes as well, which limits what we can optimize +- +- int removedRows = 0, offset = 0; +- typedef QPair IntPair; // to make foreach macro happy +- +- foreach(const IntPair& pair, toRemove) +- { +- const int begin = pair.first - offset; +- const int end = pair.second - offset; // inclusive +- removedRows = end - begin + 1; +- +- // when removing from the list, all subsequent indexes are affected +- offset += removedRows; +- +- QList removedInfos; +- +- if (d->sendRemovalSignals) +- { +- qCopy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin()); +- emit imageInfosAboutToBeRemoved(removedInfos); +- } +- +- imageInfosAboutToBeRemoved(begin, end); +- beginRemoveRows(QModelIndex(), begin, end); +- +- // update idHash - which points to indexes of d->infos, and these change now! +- QHash::iterator it; +- +- for (it = d->idHash.begin(); it != d->idHash.end(); ) +- { +- if (it.value() >= begin) +- { +- if (it.value() > end) +- { +- // after the removed interval: adjust index +- it.value() -= removedRows; +- } +- else +- { +- // in the removed interval +- it = d->idHash.erase(it); +- continue; +- } +- } +- +- ++it; +- } +- +- // remove from list +- d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1)); +- +- if (!d->extraValues.isEmpty()) +- { +- d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1)); +- } +- +- endRemoveRows(); +- +- if (d->sendRemovalSignals) +- { +- emit imageInfosRemoved(removedInfos); +- } +- } +- +- // tidy up: remove old indexes from file path hash now +- if (d->keepFilePathCache) +- { +- QHash::iterator it; +- +- for (it = d->filePathHash.begin(); it != d->filePathHash.end(); ) +- { +- if (pairsContain(toRemove, it.value())) +- { +- it = d->filePathHash.erase(it); +- } +- else +- { +- ++it; +- } +- } +- } +-} +- +-ImageModelIncrementalUpdater::ImageModelIncrementalUpdater(ImageModel::Private* d) +-{ +- oldIds = d->idHash; +- oldExtraValues = d->extraValues; +-} +- +-void ImageModelIncrementalUpdater::appendInfos(const QList& infos, const QList& extraValues) +-{ +- if (extraValues.isEmpty()) +- { +- foreach(const ImageInfo& info, infos) +- { +- QHash::iterator it = oldIds.find(info.id()); +- +- if (it != oldIds.end()) +- { +- oldIds.erase(it); +- } +- else +- { +- newInfos << info; +- } +- } +- } +- else +- { +- for (int i=0; i::iterator it; +- +- for (it = oldIds.find(info.id()); it != oldIds.end() && it.key() == info.id(); ++it) +- { +- // first check is for bug #262596. Not sure if needed. +- if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value())) +- { +- found = true; +- break; +- } +- } +- +- if (found) +- { +- oldIds.erase(it); +- // do not erase from oldExtraValues - oldIds is a hash id -> index. +- } +- else +- { +- newInfos << info; +- newExtraValues << extraValues.at(i); +- } +- } +- } +-} +- +-void ImageModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove) +-{ +- modelRemovals << toRemove; +-} +- +-QList > ImageModelIncrementalUpdater::oldIndexes() +-{ +- // first, apply all changes to indexes by direct removal in model +- // while the updater was active +- foreach(const IntPairList& list, modelRemovals) +- { +- int removedRows = 0, offset = 0; +- +- foreach(const IntPair& pair, list) +- { +- const int begin = pair.first - offset; +- const int end = pair.second - offset; // inclusive +- removedRows = end - begin + 1; +- +- // when removing from the list, all subsequent indexes are affected +- offset += removedRows; +- +- // update idHash - which points to indexes of d->infos, and these change now! +- QHash::iterator it; +- +- for (it = oldIds.begin(); it != oldIds.end(); ) +- { +- if (it.value() >= begin) +- { +- if (it.value() > end) +- { +- // after the removed interval: adjust index +- it.value() -= removedRows; +- } +- else +- { +- // in the removed interval +- it = oldIds.erase(it); +- continue; +- } +- } +- +- ++it; +- } +- } +- } +- +- modelRemovals.clear(); +- +- return toContiguousPairs(oldIds.values()); +-} +- +-QList > ImageModelIncrementalUpdater::toContiguousPairs(const QList& unsorted) +-{ +- // Take the given indices and return them as contiguous pairs [begin, end] +- +- QList > pairs; +- +- if (unsorted.isEmpty()) +- { +- return pairs; +- } +- +- QList indices(unsorted); +- qSort(indices); +- +- QPair pair(indices.first(), indices.first()); +- +- for (int i=1; iisValid(index)) +- { +- return QVariant(); +- } +- +- switch (role) +- { +- case Qt::DisplayRole: +- case Qt::ToolTipRole: +- return d->infos.at(index.row()).name(); +- +- case ImageModelPointerRole: +- return QVariant::fromValue(const_cast(this)); +- +- case ImageModelInternalId: +- return index.row(); +- +- case CreationDateRole: +- return d->infos.at(index.row()).dateTime(); +- +- case ExtraDataRole: +- +- if (d->extraValueValid(index)) +- { +- return d->extraValues.at(index.row()); +- } +- else +- { +- return QVariant(); +- } +- +- case ExtraDataDuplicateCount: +- { +- qlonglong id = d->infos.at(index.row()).id(); +- return numberOfIndexesForImageId(id); +- } +- } +- +- return QVariant(); +-} +- +-QVariant ImageModel::headerData(int section, Qt::Orientation orientation, int role) const +-{ +- Q_UNUSED(section) +- Q_UNUSED(orientation) +- Q_UNUSED(role) +- return QVariant(); +-} +- +-int ImageModel::rowCount(const QModelIndex& parent) const +-{ +- if (parent.isValid()) +- { +- return 0; +- } +- +- return d->infos.size(); +-} +- +-Qt::ItemFlags ImageModel::flags(const QModelIndex& index) const +-{ +- if (!d->isValid(index)) +- { +- return 0; +- } +- +- Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; +- +- f |= dragDropFlags(index); +- +- return f; +-} +- +-QModelIndex ImageModel::index(int row, int column, const QModelIndex& parent) const +-{ +- if (column != 0 || row < 0 || parent.isValid() || row >= d->infos.size()) +- { +- return QModelIndex(); +- } +- +- return createIndex(row, 0); +-} +- +-// ------------ Database watch ------------- +- +-void ImageModel::slotImageChange(const ImageChangeset& changeset) +-{ +- if (d->infos.isEmpty()) +- { +- return; +- } +- +- if (d->watchFlags & changeset.changes()) +- { +- QItemSelection items; +- +- foreach(const qlonglong& id, changeset.ids()) +- { +- QModelIndex index = indexForImageId(id); +- +- if (index.isValid()) +- { +- items.select(index, index); +- } +- } +- +- if (!items.isEmpty()) +- { +- emitDataChangedForSelection(items); +- emit imageChange(changeset, items); +- } +- } +-} +- +-void ImageModel::slotImageTagChange(const ImageTagChangeset& changeset) +-{ +- if (d->infos.isEmpty()) +- { +- return; +- } +- +- QItemSelection items; +- +- foreach(const qlonglong& id, changeset.ids()) +- { +- QModelIndex index = indexForImageId(id); +- +- if (index.isValid()) +- { +- items.select(index, index); +- } +- } +- +- if (!items.isEmpty()) +- { +- emitDataChangedForSelection(items); +- emit imageTagChange(changeset, items); +- } +-} +- +-} // namespace Digikam +diff --git a/libs/models/imagemodel.h b/libs/models/imagemodel.h +deleted file mode 100644 +index dcf94c2..0000000 +--- a/libs/models/imagemodel.h ++++ /dev/null +@@ -1,364 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGEMODEL_H +-#define IMAGEMODEL_H +- +-// Qt includes +- +-#include +- +-// Local includes +- +-#include "dragdropimplementations.h" +-#include "imageinfo.h" +-#include "digikam_export.h" +- +-class QItemSelection; +- +-namespace Digikam +-{ +- +-class ImageChangeset; +-class ImageTagChangeset; +- +-namespace DatabaseFields +-{ +-class Set; +-} +- +-class DIGIKAM_DATABASE_EXPORT ImageModel : public QAbstractListModel, public DragDropModelImplementation +-{ +- Q_OBJECT +- +-public: +- +- enum ImageModelRoles +- { +- /// An ImageModel* pointer to this model +- ImageModelPointerRole = Qt::UserRole, +- ImageModelInternalId = Qt::UserRole + 1, +- /// Returns a thumbnail pixmap. May be implemented by subclasses. +- /// Returns either a valid pixmap or a null QVariant. +- ThumbnailRole = Qt::UserRole + 2, +- /// Returns a QDateTime with the creation date +- CreationDateRole = Qt::UserRole + 3, +- /// Return (optional) extraData field +- ExtraDataRole = Qt::UserRole + 5, +- /// Returns the number of duplicate indexes for the same image id +- ExtraDataDuplicateCount = Qt::UserRole + 6, +- +- // Roles which are defined here but not implemented by ImageModel +- /// Returns position of item in Left Light Table preview. +- LTLeftPanelRole = Qt::UserRole + 50, +- /// Returns position of item in Right Light Table preview. +- LTRightPanelRole = Qt::UserRole + 51, +- +- // For use by subclasses +- SubclassRoles = Qt::UserRole + 100, +- // For use by filter models +- FilterModelRoles = Qt::UserRole + 500 +- }; +- +-public: +- +- explicit ImageModel(QObject* parent = 0); +- ~ImageModel(); +- +- /** If a cache is kept, lookup by file path is fast, +- * without a cache it is O(n). Default is false. +- */ +- void setKeepsFilePathCache(bool keepCache); +- bool keepsFilePathCache() const; +- +- /** Set a set of database fields to watch. +- * If either of these is changed, dataChanged() will be emitted. +- * Default is no flag (no signal will be emitted). +- */ +- void setWatchFlags(const DatabaseFields::Set& set); +- +- /** Returns the ImageInfo object, reference or image id from the underlying data +- * pointed to by the index. +- * If the index is not valid, imageInfo will return a null ImageInfo, imageId will +- * return 0, imageInfoRef must not be called with an invalid index. +- */ +- ImageInfo imageInfo(const QModelIndex& index) const; +- ImageInfo& imageInfoRef(const QModelIndex& index) const; +- qlonglong imageId(const QModelIndex& index) const; +- QList imageInfos(const QList& indexes) const; +- QList imageIds(const QList& indexes) const; +- +- /** Returns the ImageInfo object, reference or image id from the underlying data +- * of the given row (parent is the invalid QModelIndex, column is 0). +- * Note that imageInfoRef will crash if index is invalid. +- */ +- ImageInfo imageInfo(int row) const; +- ImageInfo& imageInfoRef(int row) const; +- qlonglong imageId(int row) const; +- +- /** Return the index for the given ImageInfo or id, if contained in this model. +- */ +- QModelIndex indexForImageInfo(const ImageInfo& info) const; +- QModelIndex indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const; +- QModelIndex indexForImageId(qlonglong id) const; +- QModelIndex indexForImageId(qlonglong id, const QVariant& extraValue) const; +- QList indexesForImageInfo(const ImageInfo& info) const; +- QList indexesForImageId(qlonglong id) const; +- +- int numberOfIndexesForImageInfo(const ImageInfo& info) const; +- int numberOfIndexesForImageId(qlonglong id) const; +- +- /** Returns the index or ImageInfo object from the underlying data +- * for the given file path. This is fast if keepsFilePathCache is enabled. +- * The file path is as returned by ImageInfo.filePath(). +- * In case of multiple occurrences of the same file, the simpler variants return +- * any one found first, use the QList methods to retrieve all occurrences. +- */ +- QModelIndex indexForPath(const QString& filePath) const; +- ImageInfo imageInfo(const QString& filePath) const; +- QList indexesForPath(const QString& filePath) const; +- QList imageInfos(const QString& filePath) const; +- +- /** Main entry point for subclasses adding image infos to the model. +- * If you list entries not unique per image id, you must add an extraValue +- * so that every entry is unique by imageId and extraValues. +- * Please note that these methods do not prevent addition of duplicate entries. +- */ +- void addImageInfo(const ImageInfo& info); +- void addImageInfos(const QList& infos); +- void addImageInfos(const QList& infos, const QList& extraValues); +- +- /** Clears image infos and resets model. +- */ +- void clearImageInfos(); +- +- /** Clears and adds the infos. +- */ +- void setImageInfos(const QList& infos); +- +- /** +- * Directly remove the given indexes or infos from the model. +- */ +- void removeIndex(const QModelIndex& indexes); +- void removeIndexes(const QList& indexes); +- void removeImageInfo(const ImageInfo& info); +- void removeImageInfos(const QList& infos); +- void removeImageInfos(const QList& infos, const QList& extraValues); +- +- /** +- * addImageInfo() is asynchronous if a prepocessor is set. +- * This method first adds the info, synchronously. +- * Only afterwards, the preprocessor will have the opportunity to process it. +- * This method also bypasses any incremental updates. +- * Please note that these methods do not prevent addition of duplicate entries. +- */ +- void addImageInfoSynchronously(const ImageInfo& info); +- void addImageInfosSynchronously(const QList& infos); +- void addImageInfosSynchronously(const QList& infos, const QList& extraValues); +- +- /** +- * Add the given entries. Method returns immediately, the +- * addition may happen later asynchronously. +- * These methods prevent the addition of duplicate entries. +- */ +- void ensureHasImageInfo(const ImageInfo& info); +- void ensureHasImageInfos(const QList& infos); +- void ensureHasImageInfos(const QList& infos, const QList& extraValues); +- +- /** +- * Ensure that all images grouped on the given leader are contained in the model. +- */ +- void ensureHasGroupedImages(const ImageInfo& groupLeader); +- +- QList imageInfos() const; +- QList imageIds() const; +- QList uniqueImageInfos() const; +- +- bool hasImage(qlonglong id) const; +- bool hasImage(const ImageInfo& info) const; +- bool hasImage(const ImageInfo& info, const QVariant& extraValue) const; +- bool hasImage(qlonglong id, const QVariant& extraValue) const; +- +- bool isEmpty() const; +- +- // Drag and Drop +- DECLARE_MODEL_DRAG_DROP_METHODS +- +- /** +- * Install an object as a preprocessor for ImageInfos added to this model. +- * For every QList of ImageInfos added to addImageInfo, the signal preprocess() +- * will be emitted. The preprocessor may process the items and shall then readd +- * them by calling reAddImageInfos(). It may take some time to process. +- * It shall discard any held infos when the modelReset() signal is sent. +- * It shall call readdFinished() when no reset occurred and all infos on the way have been readded. +- * This means that only after calling this method, you shall make three connections +- * (preprocess -> your slot, your signal -> reAddImageInfos, your signal -> reAddingFinished) +- * and make or already hold a connection modelReset() -> your slot. +- * There is only one preprocessor at a time, a previously set object will be disconnected. +- */ +- void setPreprocessor(QObject* processor); +- void unsetPreprocessor(QObject* processor); +- +- /** +- * Returns true if this model is currently refreshing. +- * For a preprocessor this means that, although the preprocessor may currently have +- * processed all it got, more batches are to be expected. +- */ +- bool isRefreshing() const; +- +- /** +- * Enable sending of imageInfosAboutToBeRemoved and imageInfosRemoved signals. +- * Default: false +- */ +- void setSendRemovalSignals(bool send); +- +- virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; +- virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; +- virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; +- virtual Qt::ItemFlags flags(const QModelIndex& index) const; +- virtual QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const; +- +- /** Retrieves the imageInfo object from the data() method of the given index. +- * The index may be from a QSortFilterProxyModel as long as an ImageModel is at the end. */ +- static ImageInfo retrieveImageInfo(const QModelIndex& index); +- static qlonglong retrieveImageId(const QModelIndex& index); +- +-Q_SIGNALS: +- +- /** Informs that ImageInfos will be added to the model. +- * This signal is sent before the model data is changed and views are informed. +- */ +- void imageInfosAboutToBeAdded(const QList& infos); +- +- /** Informs that ImageInfos have been added to the model. +- * This signal is sent after the model data is changed and views are informed. +- */ +- void imageInfosAdded(const QList& infos); +- +- /** Informs that ImageInfos will be removed from the model. +- * This signal is sent before the model data is changed and views are informed. +- * Note: You need to explicitly enable sending of this signal. It is not sent +- * in clearImageInfos(). +- */ +- void imageInfosAboutToBeRemoved(const QList& infos); +- +- /** Informs that ImageInfos have been removed from the model. +- * This signal is sent after the model data is changed and views are informed. * +- * Note: You need to explicitly enable sending of this signal. It is not sent +- * in clearImageInfos(). +- */ +- void imageInfosRemoved(const QList& infos); +- +- /** Connect to this signal only if you are the current preprocessor. +- */ +- void preprocess(const QList& infos, const QList&); +- void processAdded(const QList& infos, const QList&); +- +- /** If an ImageChangeset affected indexes of this model with changes as set in watchFlags(), +- * this signal contains the changeset and the affected indexes. +- */ +- void imageChange(const ImageChangeset&, const QItemSelection&); +- +- /** If an ImageTagChangeset affected indexes of this model, +- * this signal contains the changeset and the affected indexes. +- */ +- void imageTagChange(const ImageTagChangeset&, const QItemSelection&); +- +- /** Signals that the model is right now ready to start an incremental refresh. +- * This is guaranteed only for the scope of emitting this signal. +- */ +- void readyForIncrementalRefresh(); +- +- /** Signals that the model has finished currently with all scheduled +- * refreshing, full or incremental, and all preprocessing. +- * The model is in polished, clean situation right now. +- */ +- void allRefreshingFinished(); +- +-public Q_SLOTS: +- +- void reAddImageInfos(const QList& infos, const QList& extraValues); +- void reAddingFinished(); +- +-protected: +- +- /** Subclasses that add ImageInfos in batches shall call startRefresh() +- * when they start sending batches and finishRefresh() when they have finished. +- * No incremental refreshes will be started while listing. +- * A clearImageInfos() always stops listing, calling finishRefresh() is then not necessary. +- */ +- void startRefresh(); +- void finishRefresh(); +- +- /** As soon as the model is ready to start an incremental refresh, the signal +- * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline +- * if the model is ready right now. +- */ +- void requestIncrementalRefresh(); +- bool hasIncrementalRefreshPending() const; +- +- /** Starts an incremental refresh operation. You shall only call this method from a slot +- * connected to readyForIncrementalRefresh(). To initiate an incremental refresh, +- * call requestIncrementalRefresh(). +- */ +- void startIncrementalRefresh(); +- void finishIncrementalRefresh(); +- +- void emitDataChangedForAll(); +- void emitDataChangedForSelection(const QItemSelection& selection); +- +- // Called when the internal storage is cleared +- virtual void imageInfosCleared() {}; +- +- // Called before rowsAboutToBeRemoved +- virtual void imageInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {}; +- +-protected Q_SLOTS: +- +- virtual void slotImageChange(const ImageChangeset& changeset); +- virtual void slotImageTagChange(const ImageTagChangeset& changeset); +- +-private: +- +- void appendInfos(const QList& infos, const QList& extraValues); +- void appendInfosChecked(const QList& infos, const QList& extraValues); +- void publiciseInfos(const QList& infos, const QList& extraValues); +- void cleanSituationChecks(); +- void removeRowPairsWithCheck(const QList >& toRemove); +- void removeRowPairs(const QList >& toRemove); +- +-public: +- +- // Declared public because it's used in ImageModelIncrementalUpdater class +- class Private; +- +-private: +- +- Private* const d; +-}; +- +-} // namespace Digikam +- +-Q_DECLARE_METATYPE(Digikam::ImageModel*) +- +-#endif // IMAGEMODEL_H +diff --git a/libs/models/imagesortsettings.cpp b/libs/models/imagesortsettings.cpp +deleted file mode 100644 +index 39ee6e1..0000000 +--- a/libs/models/imagesortsettings.cpp ++++ /dev/null +@@ -1,400 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Filter values for use with ImageFilterModel +- * +- * Copyright (C) 2009 by Marcel Wiesweg +- * Copyright (C) 2014 by Mohamed Anwer +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagesortsettings.h" +- +-// Qt includes +- +-#include +-#include +- +-// Local includes +- +-#include "coredbfields.h" +-#include "imageinfo.h" +- +-namespace Digikam +-{ +- +-ImageSortSettings::ImageSortSettings() +-{ +- categorizationMode = NoCategories; +- categorizationSortOrder = DefaultOrder; +- categorizationCaseSensitivity = Qt::CaseSensitive; +- sortRole = SortByFileName; +- sortOrder = DefaultOrder; +- strTypeNatural = true; +- sortCaseSensitivity = Qt::CaseSensitive; +- currentCategorizationSortOrder = Qt::AscendingOrder; +- currentSortOrder = Qt::AscendingOrder; +-} +- +-bool ImageSortSettings::operator==(const ImageSortSettings& other) const +-{ +- return +- categorizationMode == other.categorizationMode && +- categorizationSortOrder == other.categorizationSortOrder && +- categorizationCaseSensitivity == other.categorizationCaseSensitivity && +- sortRole == other.sortRole && +- sortOrder == other.sortOrder && +- sortCaseSensitivity == other.sortCaseSensitivity; +-} +- +-void ImageSortSettings::setCategorizationMode(CategorizationMode mode) +-{ +- categorizationMode = mode; +- +- if (categorizationSortOrder == DefaultOrder) +- { +- currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); +- } +-} +- +-void ImageSortSettings::setCategorizationSortOrder(SortOrder order) +-{ +- categorizationSortOrder = order; +- +- if (categorizationSortOrder == DefaultOrder) +- { +- currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode); +- } +- else +- { +- currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder; +- } +-} +- +-void ImageSortSettings::setSortRole(SortRole role) +-{ +- sortRole = role; +- +- if (sortOrder == DefaultOrder) +- { +- currentSortOrder = defaultSortOrderForSortRole(sortRole); +- } +-} +- +-void ImageSortSettings::setSortOrder(SortOrder order) +-{ +- sortOrder = order; +- +- if (sortOrder == DefaultOrder) +- { +- currentSortOrder = defaultSortOrderForSortRole(sortRole); +- } +- else +- { +- currentSortOrder = (Qt::SortOrder)order; +- } +-} +- +-void ImageSortSettings::setStringTypeNatural(bool natural) +-{ +- strTypeNatural = natural; +-} +- +-Qt::SortOrder ImageSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode) +-{ +- switch (mode) +- { +- case NoCategories: +- case OneCategory: +- case CategoryByAlbum: +- case CategoryByFormat: +- default: +- return Qt::AscendingOrder; +- } +-} +- +-Qt::SortOrder ImageSortSettings::defaultSortOrderForSortRole(SortRole role) +-{ +- switch (role) +- { +- case SortByFileName: +- case SortByFilePath: +- return Qt::AscendingOrder; +- case SortByFileSize: +- return Qt::DescendingOrder; +- case SortByModificationDate: +- case SortByCreationDate: +- return Qt::AscendingOrder; +- case SortByRating: +- case SortByImageSize: +- return Qt::DescendingOrder; +- case SortByAspectRatio: +- return Qt::DescendingOrder; +- case SortBySimilarity: +- return Qt::DescendingOrder; +- default: +- return Qt::AscendingOrder; +- } +-} +- +-int ImageSortSettings::compareCategories(const ImageInfo& left, const ImageInfo& right) const +-{ +- switch (categorizationMode) +- { +- case NoCategories: +- case OneCategory: +- return 0; +- case CategoryByAlbum: +- { +- int leftAlbum = left.albumId(); +- int rightAlbum = right.albumId(); +- +- // return comparation result +- if (leftAlbum == rightAlbum) +- { +- return 0; +- } +- else if (lessThanByOrder(leftAlbum, rightAlbum, currentCategorizationSortOrder)) +- { +- return -1; +- } +- else +- { +- return 1; +- } +- } +- case CategoryByFormat: +- { +- return naturalCompare(left.format(), right.format(), +- currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural); +- } +- default: +- return 0; +- } +-} +- +-bool ImageSortSettings::lessThan(const ImageInfo& left, const ImageInfo& right) const +-{ +- int result = compare(left, right, sortRole); +- +- if (result != 0) +- { +- return result < 0; +- } +- +- // are they identical? +- if (left == right) +- { +- return false; +- } +- +- // If left and right equal for first sort order, use a hierarchy of all sort orders +- if ( (result = compare(left, right, SortByFileName)) != 0) +- { +- return result < 0; +- } +- +- if ( (result = compare(left, right, SortByCreationDate)) != 0) +- { +- return result < 0; +- } +- +- if ( (result = compare(left, right, SortByModificationDate)) != 0) +- { +- return result < 0; +- } +- +- if ( (result = compare(left, right, SortByFilePath)) != 0) +- { +- return result < 0; +- } +- +- if ( (result = compare(left, right, SortByFileSize)) != 0) +- { +- return result < 0; +- } +- +- if ( (result = compare(left, right, SortBySimilarity)) != 0) +- { +- return result < 0; +- } +- +- return false; +-} +- +-int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right) const +-{ +- return compare(left, right, sortRole); +-} +- +-int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right, SortRole role) const +-{ +- switch (role) +- { +- case SortByFileName: +- { +- bool versioning = (left.name().contains(QLatin1String("_v"), Qt::CaseInsensitive) || +- right.name().contains(QLatin1String("_v"), Qt::CaseInsensitive)); +- return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural, versioning); +- } +- case SortByFilePath: +- return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural); +- case SortByFileSize: +- return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder); +- case SortByModificationDate: +- return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder); +- case SortByCreationDate: +- return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder); +- case SortByRating: +- // I have the feeling that inverting the sort order for rating is the natural order +- return - compareByOrder(left.rating(), right.rating(), currentSortOrder); +- case SortByImageSize: +- { +- QSize leftSize = left.dimensions(); +- QSize rightSize = right.dimensions(); +- int leftPixels = leftSize.width() * leftSize.height(); +- int rightPixels = rightSize.width() * rightSize.height(); +- return compareByOrder(leftPixels, rightPixels, currentSortOrder); +- } +- case SortByAspectRatio: +- { +- QSize leftSize = left.dimensions(); +- QSize rightSize = right.dimensions(); +- int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000; +- int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000; +- return compareByOrder(leftAR, rightAR, currentSortOrder); +- } +- case SortBySimilarity: +- { +- qlonglong leftReferenceImageId = left.currentReferenceImage(); +- qlonglong rightReferenceImageId = right.currentReferenceImage(); +- // make sure that the original image has always the highest similarity. +- double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity(); +- double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity(); +- return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder); +- } +- default: +- return 1; +- } +-} +- +-bool ImageSortSettings::lessThan(const QVariant& left, const QVariant& right) const +-{ +- if (left.type() != right.type()) +- { +- return false; +- } +- +- switch (left.type()) +- { +- case QVariant::Int: +- return compareByOrder(left.toInt(), right.toInt(), currentSortOrder); +- case QVariant::UInt: +- return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder); +- case QVariant::LongLong: +- return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder); +- case QVariant::ULongLong: +- return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder); +- case QVariant::Double: +- return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder); +- case QVariant::Date: +- return compareByOrder(left.toDate(), right.toDate(), currentSortOrder); +- case QVariant::DateTime: +- return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder); +- case QVariant::Time: +- return compareByOrder(left.toTime(), right.toTime(), currentSortOrder); +- case QVariant::Rect: +- case QVariant::RectF: +- { +- QRectF rectLeft = left.toRectF(); +- QRectF rectRight = right.toRectF(); +- int result; +- +- if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0) +- { +- return result < 0; +- } +- +- if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0) +- { +- return result < 0; +- } +- +- QSizeF sizeLeft = rectLeft.size(), sizeRight = rectRight.size(); +- +- if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0) +- { +- return result < 0; +- } +- // FIXME: fall through?? If not, add "break" here +- } +- default: +- return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural); +- } +-} +- +-DatabaseFields::Set ImageSortSettings::watchFlags() const +-{ +- DatabaseFields::Set set; +- +- switch (sortRole) +- { +- case SortByFileName: +- set |= DatabaseFields::Name; +- break; +- case SortByFilePath: +- set |= DatabaseFields::Name; +- break; +- case SortByFileSize: +- set |= DatabaseFields::FileSize; +- break; +- case SortByModificationDate: +- set |= DatabaseFields::ModificationDate; +- break; +- case SortByCreationDate: +- set |= DatabaseFields::CreationDate; +- break; +- case SortByRating: +- set |= DatabaseFields::Rating; +- break; +- case SortByImageSize: +- set |= DatabaseFields::Width | DatabaseFields::Height; +- break; +- case SortByAspectRatio: +- set |= DatabaseFields::Width | DatabaseFields::Height; +- break; +- case SortBySimilarity: +- // TODO: Not sure what to do here.... +- set |= DatabaseFields::Name; +- break; +- } +- +- switch (categorizationMode) +- { +- case NoCategories: +- case OneCategory: +- case CategoryByAlbum: +- break; +- case CategoryByFormat: +- set |= DatabaseFields::Format; +- break; +- } +- +- return set; +-} +- +-} // namespace Digikam +diff --git a/libs/models/imagesortsettings.h b/libs/models/imagesortsettings.h +deleted file mode 100644 +index 2a5fd8c..0000000 +--- a/libs/models/imagesortsettings.h ++++ /dev/null +@@ -1,225 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-05-31 +- * Description : Sort settings for use with ImageFilterModel +- * +- * Copyright (C) 2009 by Marcel Wiesweg +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGESORTSETTINGS_H +-#define IMAGESORTSETTINGS_H +- +-// Qt includes +- +-#include +-#include +-#include +-#include +-#include +- +-// Local includes +- +-#include "digikam_export.h" +- +-namespace Digikam +-{ +- +-class ImageInfo; +- +-namespace DatabaseFields +-{ +- class Set; +-} +- +-class DIGIKAM_DATABASE_EXPORT ImageSortSettings +-{ +-public: +- +- ImageSortSettings(); +- +- bool operator==(const ImageSortSettings& other) const; +- +- /** Compares the categories of left and right. +- * Return -1 if left is less than right, 0 if both fall in the same category, +- * and 1 if left is greater than right. +- * Adheres to set categorization mode and current category sort order. +- */ +- int compareCategories(const ImageInfo& left, const ImageInfo& right) const; +- +- /** Returns true if left is less than right. +- * Adheres to current sort role and sort order. +- */ +- bool lessThan(const ImageInfo& left, const ImageInfo& right) const; +- +- /** Compares the ImageInfos left and right. +- * Return -1 if left is less than right, 1 if left is greater than right, +- * and 0 if left equals right comparing the current sort role's value. +- * Adheres to set sort role and sort order. +- */ +- int compare(const ImageInfo& left, const ImageInfo& right) const; +- +- /** Returns true if left QVariant is less than right. +- * Adheres to current sort role and sort order. +- * Use for extraValue, if necessary. +- */ +- bool lessThan(const QVariant& left, const QVariant& right) const; +- +- enum SortOrder +- { +- AscendingOrder = Qt::AscendingOrder, +- DescendingOrder = Qt::DescendingOrder, +- DefaultOrder /// sort order depends on the chosen sort role +- }; +- +- /// --- Categories --- +- +- enum CategorizationMode +- { +- NoCategories, /// categorization switched off +- OneCategory, /// all items in one global category +- CategoryByAlbum, +- CategoryByFormat +- }; +- +- CategorizationMode categorizationMode; +- SortOrder categorizationSortOrder; +- +- void setCategorizationMode(CategorizationMode mode); +- void setCategorizationSortOrder(SortOrder order); +- +- /// Only Ascending or Descending, never DefaultOrder +- Qt::SortOrder currentCategorizationSortOrder; +- Qt::CaseSensitivity categorizationCaseSensitivity; +- +- bool isCategorized() const { return categorizationMode >= CategoryByAlbum; } +- +- /// --- Image Sorting --- +- +- enum SortRole +- { +- // Note: For legacy reasons, the order of the first five entries must remain unchanged +- SortByFileName, +- SortByFilePath, +- SortByCreationDate, +- SortByFileSize, +- SortByRating, +- SortByModificationDate, +- SortByImageSize, // pixel number +- SortByAspectRatio, // width / height * 100000 +- SortBySimilarity +- }; +- +- SortRole sortRole; +- SortOrder sortOrder; +- bool strTypeNatural; +- +- void setSortRole(SortRole role); +- void setSortOrder(SortOrder order); +- void setStringTypeNatural(bool natural); +- +- Qt::SortOrder currentSortOrder; +- Qt::CaseSensitivity sortCaseSensitivity; +- +- int compare(const ImageInfo& left, const ImageInfo& right, SortRole sortRole) const; +- +- // --- --- +- +- static Qt::SortOrder defaultSortOrderForCategorizationMode(CategorizationMode mode); +- static Qt::SortOrder defaultSortOrderForSortRole(SortRole role); +- +- /// --- Change notification --- +- +- /** Returns database fields a change in which would affect the current sorting. +- */ +- DatabaseFields::Set watchFlags() const; +- +- /// --- Utilities --- +- +- /** Returns a < b if sortOrder is Ascending, or b < a if order is descending. +- */ +- template +- static inline bool lessThanByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) +- { +- if (sortOrder == Qt::AscendingOrder) +- { +- return a < b; +- } +- else +- { +- return b < a; +- } +- } +- +- /** Returns the usual compare result of -1, 0, or 1 for lessThan, equals and greaterThan. +- */ +- template +- static inline int compareValue(const T& a, const T& b) +- { +- if (a == b) +- { +- return 0; +- } +- +- if (a < b) +- { +- return -1; +- } +- else +- { +- return 1; +- } +- } +- +- /** Takes a typical result from a compare method (0 is equal, -1 is less than, 1 is greater than) +- * and applies the given sort order to it. +- */ +- static inline int compareByOrder(int compareResult, Qt::SortOrder sortOrder) +- { +- if (sortOrder == Qt::AscendingOrder) +- { +- return compareResult; +- } +- else +- { +- return - compareResult; +- } +- } +- +- template +- static inline int compareByOrder(const T& a, const T& b, Qt::SortOrder sortOrder) +- { +- return compareByOrder(compareValue(a, b), sortOrder); +- } +- +- /** Compares the two string by natural comparison and adheres to given sort order +- */ +- static inline int naturalCompare(const QString& a, const QString& b, Qt::SortOrder sortOrder, +- Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive, +- bool natural = true, bool versioning = false) +- { +- QCollator collator; +- collator.setNumericMode(natural); +- collator.setIgnorePunctuation(versioning); +- collator.setCaseSensitivity(caseSensitive); +- return (compareByOrder(collator.compare(a, b), sortOrder)); +- } +-}; +- +-} // namespace Digikam +- +-#endif // IMAGESORTSETTINGS_H +diff --git a/libs/models/imagethumbnailmodel.cpp b/libs/models/imagethumbnailmodel.cpp +deleted file mode 100644 +index b7f5661..0000000 +--- a/libs/models/imagethumbnailmodel.cpp ++++ /dev/null +@@ -1,323 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries with support for thumbnail loading +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011-2017 by Gilles Caulier +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imagethumbnailmodel.h" +- +-// Qt includes +- +-#include +- +-// Local includes +- +-#include "digikam_debug.h" +-#include "thumbnailloadthread.h" +-#include "digikam_export.h" +-#include "digikam_globals.h" +- +-namespace Digikam +-{ +- +-class ImageThumbnailModel::ImageThumbnailModelPriv +-{ +-public: +- +- ImageThumbnailModelPriv() : +- thread(0), +- preloadThread(0), +- thumbSize(0), +- lastGlobalThumbSize(0), +- preloadThumbSize(0), +- emitDataChanged(true) +- { +- staticListContainingThumbnailRole << ImageModel::ThumbnailRole; +- } +- +- ThumbnailLoadThread* thread; +- ThumbnailLoadThread* preloadThread; +- ThumbnailSize thumbSize; +- ThumbnailSize lastGlobalThumbSize; +- ThumbnailSize preloadThumbSize; +- QRect detailRect; +- QVector staticListContainingThumbnailRole; +- +- bool emitDataChanged; +- +- int preloadThumbnailSize() const +- { +- if (preloadThumbSize.size()) +- { +- return preloadThumbSize.size(); +- } +- +- return thumbSize.size(); +- } +-}; +- +-ImageThumbnailModel::ImageThumbnailModel(QObject* parent) +- : ImageModel(parent), d(new ImageThumbnailModelPriv) +-{ +- setKeepsFilePathCache(true); +-} +- +-ImageThumbnailModel::~ImageThumbnailModel() +-{ +- delete d->preloadThread; +- delete d; +-} +- +-void ImageThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread) +-{ +- d->thread = thread; +- +- connect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), +- this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); +-} +- +-ThumbnailLoadThread* ImageThumbnailModel::thumbnailLoadThread() const +-{ +- return d->thread; +-} +- +-ThumbnailSize ImageThumbnailModel::thumbnailSize() const +-{ +- return d->thumbSize; +-} +- +-void ImageThumbnailModel::setThumbnailSize(const ThumbnailSize& size) +-{ +- d->lastGlobalThumbSize = size; +- d->thumbSize = size; +-} +- +-void ImageThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size) +-{ +- d->preloadThumbSize = size; +-} +- +-void ImageThumbnailModel::setEmitDataChanged(bool emitSignal) +-{ +- d->emitDataChanged = emitSignal; +-} +- +-void ImageThumbnailModel::setPreloadThumbnails(bool preload) +-{ +- if (preload) +- { +- if (!d->preloadThread) +- { +- d->preloadThread = new ThumbnailLoadThread; +- d->preloadThread->setPixmapRequested(false); +- d->preloadThread->setPriority(QThread::LowestPriority); +- } +- +- connect(this, SIGNAL(allRefreshingFinished()), +- this, SLOT(preloadAllThumbnails())); +- } +- else +- { +- delete d->preloadThread; +- d->preloadThread = 0; +- disconnect(this, SIGNAL(allRefreshingFinished()), +- this, SLOT(preloadAllThumbnails())); +- } +-} +- +-void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare) +-{ +- prepareThumbnails(indexesToPrepare, d->thumbSize); +-} +- +-void ImageThumbnailModel::prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize) +-{ +- if (!d->thread) +- { +- return; +- } +- +- QList ids; +- foreach(const QModelIndex& index, indexesToPrepare) +- { +- ids << imageInfoRef(index).thumbnailIdentifier(); +- } +- d->thread->findGroup(ids, thumbSize.size()); +-} +- +-void ImageThumbnailModel::preloadThumbnails(const QList& infos) +-{ +- if (!d->preloadThread) +- { +- return; +- } +- +- QList ids; +- foreach(const ImageInfo& info, infos) +- { +- ids << info.thumbnailIdentifier(); +- } +- d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); +-} +- +-void ImageThumbnailModel::preloadThumbnails(const QList& indexesToPreload) +-{ +- if (!d->preloadThread) +- { +- return; +- } +- +- QList ids; +- foreach(const QModelIndex& index, indexesToPreload) +- { +- ids << imageInfoRef(index).thumbnailIdentifier(); +- } +- d->preloadThread->stopAllTasks(); +- d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize()); +-} +- +-void ImageThumbnailModel::preloadAllThumbnails() +-{ +- preloadThumbnails(imageInfos()); +-} +- +-void ImageThumbnailModel::imageInfosCleared() +-{ +- if (d->preloadThread) +- { +- d->preloadThread->stopAllTasks(); +- } +-} +- +-QVariant ImageThumbnailModel::data(const QModelIndex& index, int role) const +-{ +- if (role == ThumbnailRole && d->thread && index.isValid()) +- { +- QPixmap thumbnail; +- ImageInfo info = imageInfo(index); +- QString path = info.filePath(); +- +- if (info.isNull()) +- { +- return QVariant(QVariant::Pixmap); +- } +- +- if (!d->detailRect.isNull()) +- { +- if (d->thread->find(info.thumbnailIdentifier(), d->detailRect, thumbnail, d->thumbSize.size())) +- { +- return thumbnail; +- } +- } +- else +- { +- if (d->thread->find(info.thumbnailIdentifier(), thumbnail, d->thumbSize.size())) +- { +- return thumbnail; +- } +- } +- +- return QVariant(QVariant::Pixmap); +- } +- +- return ImageModel::data(index, role); +-} +- +-bool ImageThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role) +-{ +- if (role == ThumbnailRole) +- { +- switch (value.type()) +- { +- case QVariant::Invalid: +- d->thumbSize = d->lastGlobalThumbSize; +- d->detailRect = QRect(); +- break; +- +- case QVariant::Int: +- +- if (value.isNull()) +- { +- d->thumbSize = d->lastGlobalThumbSize; +- } +- else +- { +- d->thumbSize = value.toInt(); +- } +- break; +- +- case QVariant::Rect: +- +- if (value.isNull()) +- { +- d->detailRect = QRect(); +- } +- else +- { +- d->detailRect = value.toRect(); +- } +- break; +- +- default: +- break; +- } +- } +- +- return ImageModel::setData(index, value, role); +-} +- +-void ImageThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb) +-{ +- if (thumb.isNull()) +- { +- return; +- } +- +- // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all. +- QModelIndexList indexes; +- ThumbnailIdentifier thumbId = loadingDescription.thumbnailIdentifier(); +- if (thumbId.filePath.isEmpty()) +- { +- indexes = indexesForImageId(thumbId.id); +- } +- else +- { +- indexes = indexesForPath(thumbId.filePath); +- } +- foreach(const QModelIndex& index, indexes) +- { +- if (thumb.isNull()) +- { +- emit thumbnailFailed(index, loadingDescription.previewParameters.size); +- } +- else +- { +- emit thumbnailAvailable(index, loadingDescription.previewParameters.size); +- +- if (d->emitDataChanged) +- { +- emit dataChanged(index, index, d->staticListContainingThumbnailRole); +- } +- } +- } +-} +- +-} // namespace Digikam +diff --git a/libs/models/imagethumbnailmodel.h b/libs/models/imagethumbnailmodel.h +deleted file mode 100644 +index 366ca65..0000000 +--- a/libs/models/imagethumbnailmodel.h ++++ /dev/null +@@ -1,140 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2009-03-05 +- * Description : Qt item model for database entries with support for thumbnail loading +- * +- * Copyright (C) 2009-2011 by Marcel Wiesweg +- * Copyright (C) 2011 by Gilles Caulier +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGETHUMBNAILMODEL_H +-#define IMAGETHUMBNAILMODEL_H +- +-// Local includes +- +-#include "imagemodel.h" +-#include "thumbnailsize.h" +-#include "digikam_export.h" +- +-namespace Digikam +-{ +- +-class LoadingDescription; +-class ThumbnailLoadThread; +- +-class DIGIKAM_DATABASE_EXPORT ImageThumbnailModel : public ImageModel +-{ +- Q_OBJECT +- +-public: +- +- /** +- * An ImageModel that supports thumbnail loading. +- * You need to set a ThumbnailLoadThread to enable thumbnail loading. +- * Adjust the thumbnail size to your needs. +- * Note that setKeepsFilePathCache is enabled per default. +- */ +- explicit ImageThumbnailModel(QObject* parent); +- ~ImageThumbnailModel(); +- +- /** Enable thumbnail loading and set the thread that shall be used. +- * The thumbnail size of this thread will be adjusted. +- */ +- void setThumbnailLoadThread(ThumbnailLoadThread* thread); +- ThumbnailLoadThread* thumbnailLoadThread() const; +- +- /// Set the thumbnail size to use +- void setThumbnailSize(const ThumbnailSize& thumbSize); +- +- /// If you want to fix a size for preloading, do it here. +- void setPreloadThumbnailSize(const ThumbnailSize& thumbSize); +- +- void setExifRotate(bool rotate); +- +- /** +- * Enable emitting dataChanged() when a thumbnail becomes available. +- * The thumbnailAvailable() signal will be emitted in any case. +- * Default is true. +- */ +- void setEmitDataChanged(bool emitSignal); +- +- /** +- * Enable preloading of thumbnails: +- * If preloading is enabled, for every entry in the model a thumbnail generation is started. +- * Default: false. +- */ +- void setPreloadThumbnails(bool preload); +- +- ThumbnailSize thumbnailSize() const; +- +- /** +- * Handles the ThumbnailRole. +- * If the pixmap is available, returns it in the QVariant. +- * If it still needs to be loaded, returns a null QVariant and emits +- * thumbnailAvailable() as soon as it is available. +- */ +- virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; +- +- /** +- * You can override the current thumbnail size by giving an integer value for ThumbnailRole. +- * Set a null QVariant to use the thumbnail size set by setThumbnailSize() again. +- * The index given here is ignored for this purpose. +- */ +- virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole); +- +-public Q_SLOTS: +- +- /** Prepare the thumbnail loading for the given indexes +- */ +- void prepareThumbnails(const QList& indexesToPrepare); +- void prepareThumbnails(const QList& indexesToPrepare, const ThumbnailSize& thumbSize); +- +- /** +- * Preload thumbnail for the given infos resp. indexes. +- * Note: Use setPreloadThumbnails to automatically preload all entries in the model. +- * Note: This only ensures thumbnail generation. It is not guaranteed that pixmaps +- * are stored in the cache. For thumbnails that are expect to be drawn immediately, +- * include them in prepareThumbnails(). +- * Note: Stops preloading of previously added thumbnails. +- */ +- void preloadThumbnails(const QList&); +- void preloadThumbnails(const QList&); +- void preloadAllThumbnails(); +- +-Q_SIGNALS: +- +- void thumbnailAvailable(const QModelIndex& index, int requestedSize); +- void thumbnailFailed(const QModelIndex& index, int requestedSize); +- +-protected: +- +- virtual void imageInfosCleared(); +- +-protected Q_SLOTS: +- +- void slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb); +- +-private: +- +- class ImageThumbnailModelPriv; +- ImageThumbnailModelPriv* const d; +-}; +- +-} // namespace Digikam +- +-#endif /* IMAGETHUMBNAILMODEL_H */ +diff --git a/libs/models/imageversionsmodel.cpp b/libs/models/imageversionsmodel.cpp +deleted file mode 100644 +index e6ba582..0000000 +--- a/libs/models/imageversionsmodel.cpp ++++ /dev/null +@@ -1,183 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2010-07-13 +- * Description : Model for image versions +- * +- * Copyright (C) 2010 by Martin Klapetek +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#include "imageversionsmodel.h" +- +-// KDE includes +- +-#include +- +-// Local includes +- +-#include "digikam_debug.h" +-#include "workingwidget.h" +- +-namespace Digikam +-{ +- +-class ImageVersionsModel::Private +-{ +-public: +- +- Private() +- { +- data = 0; +- paintTree = false; +- } +- +- ///Complete paths with filenames and tree level +- QList >* data; +- ///This is for delegate to paint it as selected +- QString currentSelectedImage; +- ///If true, the delegate will paint items as a tree +- ///if false, it will be painted as a list +- bool paintTree; +-}; +- +-ImageVersionsModel::ImageVersionsModel(QObject* parent) +- : QAbstractListModel(parent), +- d(new Private) +-{ +- d->data = new QList >; +-} +- +-ImageVersionsModel::~ImageVersionsModel() +-{ +- //qDeleteAll(d->data); +- delete d; +-} +- +-Qt::ItemFlags ImageVersionsModel::flags(const QModelIndex& index) const +-{ +- if (!index.isValid()) +- { +- return 0; +- } +- +- return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +-} +- +-QVariant ImageVersionsModel::data(const QModelIndex& index, int role) const +-{ +- if (!index.isValid()) +- { +- return QVariant(); +- } +- +- if (role == Qt::DisplayRole && !d->data->isEmpty()) +- { +- return d->data->at(index.row()).first; +- } +- else if (role == Qt::UserRole && !d->data->isEmpty()) +- { +- return d->data->at(index.row()).second; +- } +- else if (role == Qt::DisplayRole && d->data->isEmpty()) +- { +- //TODO: make this text Italic +- return QVariant(QString(i18n("No image selected"))); +- } +- +- return QVariant(); +-} +- +-int ImageVersionsModel::rowCount(const QModelIndex& parent) const +-{ +- Q_UNUSED(parent) +- return d->data->count(); +-} +- +-void ImageVersionsModel::setupModelData(QList >& data) +-{ +- beginResetModel(); +- +- d->data->clear(); +- +- if (!data.isEmpty()) +- { +- d->data->append(data); +- } +- else +- { +- d->data->append(qMakePair(QString(i18n("This is the original image")), 0)); +- } +- +- endResetModel(); +-} +- +-void ImageVersionsModel::clearModelData() +-{ +- beginResetModel(); +- +- if (!d->data->isEmpty()) +- { +- d->data->clear(); +- } +- +- endResetModel(); +-} +- +-void ImageVersionsModel::slotAnimationStep() +-{ +- emit dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 1)); +-} +- +-QString ImageVersionsModel::currentSelectedImage() const +-{ +- return d->currentSelectedImage; +-} +- +-void ImageVersionsModel::setCurrentSelectedImage(const QString& path) +-{ +- d->currentSelectedImage = path; +-} +- +-QModelIndex ImageVersionsModel::currentSelectedImageIndex() const +-{ +- return index(listIndexOf(d->currentSelectedImage), 0); +-} +- +-bool ImageVersionsModel::paintTree() const +-{ +- return d->paintTree; +-} +- +-void ImageVersionsModel::setPaintTree(bool paint) +-{ +- d->paintTree = paint; +-} +- +-int ImageVersionsModel::listIndexOf(const QString& item) const +-{ +- for (int i = 0; i < d->data->size(); ++i) +- { +- if (d->data->at(i).first == item) +- { +- return i; +- } +- } +- +- return -1; +-} +- +-} // namespace Digikam +diff --git a/libs/models/imageversionsmodel.h b/libs/models/imageversionsmodel.h +deleted file mode 100644 +index ed08529..0000000 +--- a/libs/models/imageversionsmodel.h ++++ /dev/null +@@ -1,75 +0,0 @@ +-/* ============================================================ +- * +- * This file is a part of digiKam project +- * http://www.digikam.org +- * +- * Date : 2010-07-13 +- * Description : Model for image versions +- * +- * Copyright (C) 2010 by Martin Klapetek +- * +- * This program is free software; you can redistribute it +- * and/or modify it under the terms of the GNU General +- * Public License as published by the Free Software Foundation; +- * either version 2, or (at your option) +- * any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * ============================================================ */ +- +-#ifndef IMAGEVERSIONSMODEL_H +-#define IMAGEVERSIONSMODEL_H +- +-// Qt includes +- +-#include +-#include +- +-// Local includes +- +-#include "digikam_export.h" +- +-namespace Digikam +-{ +- +-class DIGIKAM_DATABASE_EXPORT ImageVersionsModel : public QAbstractListModel +-{ +- Q_OBJECT +- +-public: +- +- explicit ImageVersionsModel(QObject* parent = 0); +- ~ImageVersionsModel(); +- +- Qt::ItemFlags flags(const QModelIndex& index) const; +- QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; +- int rowCount(const QModelIndex& parent = QModelIndex()) const; +- +- void setupModelData(QList >& data); +- void clearModelData(); +- +- QString currentSelectedImage() const; +- void setCurrentSelectedImage(const QString& path); +- QModelIndex currentSelectedImageIndex() const; +- +- bool paintTree() const; +- int listIndexOf(const QString& item) const; +- +-public Q_SLOTS: +- +- void slotAnimationStep(); +- void setPaintTree(bool paint); +- +-private: +- +- class Private; +- Private* const d; +-}; +- +-} // namespace Digikam +- +-#endif // IMAGEVERSIONSMODEL_H +-- +cgit v0.11.2 +