summaryrefslogtreecommitdiffstats
path: root/kde/patch/digikam/digikam_databasemodel.patch
diff options
context:
space:
mode:
author Eric Hameleers <alien@slackware.com>2017-07-22 17:50:03 +0200
committer Eric Hameleers <alien@slackware.com>2017-07-22 17:50:03 +0200
commit191fb140f45bfa1d92fa139142fde62c0f76630f (patch)
tree5651a0d272b478eca5c144b82db84970232ea425 /kde/patch/digikam/digikam_databasemodel.patch
parent6ad50b02c979a2cd97ad972d8e87f23aee7358d4 (diff)
downloadktown-191fb140f45bfa1d92fa139142fde62c0f76630f.tar.gz
ktown-191fb140f45bfa1d92fa139142fde62c0f76630f.tar.xz
Fix digikam compilation against latest slackware-current
Huge patch was taken from git master. It will end up in a regular digikam release at some point. The developer thinks it only fixes compilation on MacOS and told us that our issue was with an incomplete Qt5 package. He is wrong.
Diffstat (limited to '')
-rw-r--r--kde/patch/digikam/digikam_databasemodel.patch13337
1 files changed, 13337 insertions, 0 deletions
diff --git a/kde/patch/digikam/digikam_databasemodel.patch b/kde/patch/digikam/digikam_databasemodel.patch
new file mode 100644
index 0000000..8b2fff7
--- /dev/null
+++ b/kde/patch/digikam/digikam_databasemodel.patch
@@ -0,0 +1,13337 @@
+From 7e00441c257e7e9e5dc5ab983fc06046fb72b0c5 Mon Sep 17 00:00:00 2001
+From: Gilles Caulier <caulier.gilles@gmail.com>
+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($<TARGET_PROPERTY:Qt5::DBus,INTERFACE_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 $<TARGET_OBJECTS:digikamdatabase_src> $<TARGET_OBJECTS:digikamdatabasemodels_src>)
++add_library(digikamdatabase $<TARGET_OBJECTS:digikamdatabase_src>)
+
+ 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * 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<ImageModel*>(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<QModelIndex> ImageSortFilterModel::mapListToSource(const QList<QModelIndex>& indexes) const
++{
++ QList<QModelIndex> sourceIndexes;
++ foreach(const QModelIndex& index, indexes)
++ {
++ sourceIndexes << mapToSourceImageModel(index);
++ }
++ return sourceIndexes;
++}
++
++QList<QModelIndex> ImageSortFilterModel::mapListFromSource(const QList<QModelIndex>& sourceIndexes) const
++{
++ QList<QModelIndex> 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<ImageInfo> ImageSortFilterModel::imageInfos(const QList<QModelIndex>& indexes) const
++{
++ QList<ImageInfo> infos;
++ ImageModel* const model = sourceImageModel();
++
++ foreach(const QModelIndex& index, indexes)
++ {
++ infos << model->imageInfo(mapToSourceImageModel(index));
++ }
++
++ return infos;
++}
++
++QList<qlonglong> ImageSortFilterModel::imageIds(const QList<QModelIndex>& indexes) const
++{
++ QList<qlonglong> 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<ImageInfo> ImageSortFilterModel::imageInfosSorted() const
++{
++ QList<ImageInfo> infos;
++ const int size = rowCount();
++ ImageModel* const model = sourceImageModel();
++
++ for (int i=0; i<size; ++i)
++ {
++ infos << model->imageInfo(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<ImageInfo>,QList<QVariant>)),
++ d, SLOT(preprocessInfos(QList<ImageInfo>,QList<QVariant>)));
++
++ connect(d->imageModel, SIGNAL(processAdded(QList<ImageInfo>,QList<QVariant>)),
++ d, SLOT(processAddedInfos(QList<ImageInfo>,QList<QVariant>)));
++
++ connect(d, SIGNAL(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)),
++ d->imageModel, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
++
++ 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<ImageFilterModel*>(this));
++ }
++
++ return DCategorizedSortFilterProxyModel::data(index, role);
++}
++
++ImageFilterModel* ImageFilterModel::imageFilterModel() const
++{
++ return const_cast<ImageFilterModel*>(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<QDateTime>& days)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setDayFilter(days);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
++ ImageFilterSettings::MatchingCondition matchingCond,
++ bool showUnTagged, const QList<int>& clTagIds, const QList<int>& 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<QUrl> urlList, const QString& id)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setUrlWhitelist(urlList, id);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setIdWhitelist(const QList<qlonglong>& 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<qlonglong>& 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<qlonglong, bool>::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<ImageInfo> 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<ImageInfo> 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<ImageFilterModelPrepareHook*> 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<size; ++i)
++ {
++ *p = 'a' + (number & 0xF);
++ number >>= 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * 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<ImageInfo>& 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<QModelIndex> mapListToSource(const QList<QModelIndex>& indexes) const;
++ QList<QModelIndex> mapListFromSource(const QList<QModelIndex>& sourceIndexes) const;
++
++ ImageInfo imageInfo(const QModelIndex& index) const;
++ qlonglong imageId(const QModelIndex& index) const;
++ QList<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
++ QList<qlonglong> imageIds(const QList<QModelIndex>& 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<ImageInfo> 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<QDateTime>& days);
++ void setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
++ ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged,
++ const QList<int>& clTagIds, const QList<int>& 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<QUrl> urlList, const QString& id);
++ void setIdWhitelist(const QList<qlonglong>& idList, const QString& id);
++
++ void setVersionManagerSettings(const VersionManagerSettings& settings);
++ void setExceptionList(const QList<qlonglong>& 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<ImageInfo>& infos);
++ void imageInfosAboutToBeRemoved(const QList<ImageInfo>& 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * 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>("ImageFilterModelTodoPackage");
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ infosToProcess(infos, extraValues, true);
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos)
++{
++ infosToProcess(infos, QList<QVariant>(), false);
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>::const_iterator it = infos.constBegin(), end;
++ QList<QVariant>::const_iterator xit = extraValues.constBegin(), xend;
++ int index = 0;
++ QVector<ImageInfo> infoVector;
++ QVector<QVariant> 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<qlonglong, bool>::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 <marcel dot wiesweg at gmx dot de>
++ *
++ * 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 <QHash>
++#include <QMutex>
++#include <QMutexLocker>
++#include <QSet>
++#include <QThread>
++#include <QTimer>
++#include <QWaitCondition>
++
++// 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<ImageInfo>& infos, const QVector<QVariant>& extraValues, int version, bool isForReAdd)
++ : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd)
++ {
++ }
++
++ QVector<ImageInfo> infos;
++ QVector<QVariant> extraValues;
++ unsigned int version;
++ bool isForReAdd;
++ QHash<qlonglong, bool> 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<ImageInfo>& infos);
++ void infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& 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<qlonglong, bool> filterResults;
++ bool hasOneMatch;
++ bool hasOneMatchForText;
++
++ QList<ImageFilterModelPrepareHook*> prepareHooks;
++
++/*
++ QHash<int, QSet<qlonglong> > categoryCountHashInt;
++ QHash<QString, QSet<qlonglong> > categoryCountHashString;
++
++public:
++
++ void cacheCategoryCount(int id, qlonglong imageid) const
++ { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashInt[id].insert(imageid); }
++ void cacheCategoryCount(const QString& id, qlonglong imageid) const
++ { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashString[id].insert(imageid); }
++*/
++
++public Q_SLOTS:
++
++ void preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos, const QList<QVariant>& 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * 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 <marcel dot wiesweg at gmx dot de>
++ *
++ * 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 <QThread>
++
++// 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * 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 <cmath>
++
++// Qt includes
++
++#include <QDateTime>
++
++// 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<QDateTime>& days)
++{
++ m_dayFilter.clear();
++
++ for (QList<QDateTime>::const_iterator it = days.constBegin(); it != days.constEnd(); ++it)
++ {
++ m_dayFilter.insert(*it, true);
++ }
++}
++
++void ImageFilterSettings::setTagFilter(const QList<int>& includedTags,
++ const QList<int>& excludedTags,
++ MatchingCondition matchingCondition,
++ bool showUnTagged,
++ const QList<int>& clTagIds,
++ const QList<int>& 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<int, QString>& hash)
++{
++ m_tagNameHash = hash;
++}
++
++void ImageFilterSettings::setAlbumNames(const QHash<int, QString>& hash)
++{
++ m_albumNameHash = hash;
++}
++
++void ImageFilterSettings::setUrlWhitelist(const QList<QUrl>& urlList, const QString& id)
++{
++ if (urlList.isEmpty())
++ {
++ m_urlWhitelists.remove(id);
++ }
++ else
++ {
++ m_urlWhitelists.insert(id, urlList);
++ }
++}
++
++void ImageFilterSettings::setIdWhitelist(const QList<qlonglong>& idList, const QString& id)
++{
++ if (idList.isEmpty())
++ {
++ m_idWhitelists.remove(id);
++ }
++ else
++ {
++ m_idWhitelists.insert(id, idList);
++ }
++}
++
++template <class ContainerA, class ContainerB>
++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 <class ContainerA, typename Value, class ContainerB>
++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<int> tagIds = info.tagIds();
++ QList<int>::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<int> 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<int> 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<QString, QList<QUrl>>::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<QString, QList<qlonglong> >::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<QString, QList<qlonglong> >::const_iterator it = m_exceptionLists.constBegin();
++ it != m_exceptionLists.constEnd(); ++it)
++ {
++ if (it->contains(id))
++ {
++ return true;
++ }
++ }
++
++ bool match = true;
++ QList<int> tagIds = info.tagIds();
++
++ if (!tagIds.contains(m_includeTagFilter))
++ {
++ for (QList<int>::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<int> 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<qlonglong>& 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * 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 <QHash>
++#include <QList>
++#include <QMap>
++#include <QString>
++#include <QSet>
++#include <QUrl>
++
++// 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<int>& includedTags,
++ const QList<int>& excludedTags,
++ MatchingCondition matchingCond,
++ bool showUnTagged,
++ const QList<int>& clTagIds,
++ const QList<int>& 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<QDateTime>& days);
++
++public:
++
++ /// --- Text filter ---
++ void setTextFilter(const SearchTextFilterSettings& settings);
++ void setTagNames(const QHash<int, QString>& tagNameHash);
++ void setAlbumNames(const QHash<int, QString>& 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<QUrl>& urlList, const QString& id);
++
++public:
++
++ /// --- ID whitelist filter
++ void setIdWhitelist(const QList<qlonglong>& 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<int> m_includeTagFilter;
++ QList<int> m_excludeTagFilter;
++ MatchingCondition m_matchingCond;
++ QList<int> m_colorLabelTagFilter;
++ QList<int> m_pickLabelTagFilter;
++
++ /// --- Rating filter ---
++ int m_ratingFilter;
++ RatingCondition m_ratingCond;
++ bool m_isUnratedExcluded;
++
++ /// --- Date filter ---
++ QMap<QDateTime, bool> 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<int, QString> m_tagNameHash;
++ QHash<int, QString> m_albumNameHash;
++
++ /// --- Mime filter ---
++ MimeFilter::TypeMimeFilter m_mimeTypeFilter;
++
++ /// --- Geolocation filter
++ GeolocationCondition m_geolocationCondition;
++
++ /// --- URL whitelist filter
++ QHash<QString,QList<QUrl>> m_urlWhitelists;
++
++ /// --- ID whitelist filter
++ QHash<QString,QList<qlonglong> > 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<qlonglong>& 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<int> m_excludeTagFilter;
++ int m_includeTagFilter;
++ int m_exceptionTagFilter;
++ QHash<QString,QList<qlonglong> > 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<qlonglong> 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 <marcel dot wiesweg at gmx dot de>
++ *
++ * 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 <marcel dot wiesweg at gmx dot de>
++ *
++ * 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<ImageInfo>& 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 <marcel dot wiesweg at gmx dot de>
++ *
++ * 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 <QHash>
++#include <QItemSelection>
++
++// 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<QVariant> extraValues;
++ QHash<qlonglong, int> idHash;
++
++ bool keepFilePathCache;
++ QHash<QString, qlonglong> filePathHash;
++
++ bool sendRemovalSignals;
++
++ QObject* preprocessor;
++ bool refreshing;
++ bool reAdding;
++ bool incrementalRefreshRequested;
++
++ DatabaseFields::Set watchFlags;
++
++ class ImageModelIncrementalUpdater* incrementalUpdater;
++
++ ImageInfoList pendingInfos;
++ QList<QVariant> 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<int, int> IntPair; // to make foreach macro happy
++typedef QList<IntPair> IntPairList;
++
++class ImageModelIncrementalUpdater
++{
++public:
++
++ explicit ImageModelIncrementalUpdater(ImageModel::Private* d);
++
++ void appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved);
++ QList<IntPair> oldIndexes();
++
++ static QList<IntPair> toContiguousPairs(const QList<int>& ids);
++
++public:
++
++ QHash<qlonglong, int> oldIds;
++ QList<QVariant> oldExtraValues;
++ QList<ImageInfo> newInfos;
++ QList<QVariant> newExtraValues;
++ QList<IntPairList> 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<ImageInfo> ImageModel::imageInfos(const QList<QModelIndex>& indexes) const
++{
++ QList<ImageInfo> infos;
++
++ foreach(const QModelIndex& index, indexes)
++ {
++ infos << imageInfo(index);
++ }
++
++ return infos;
++}
++
++QList<qlonglong> ImageModel::imageIds(const QList<QModelIndex>& indexes) const
++{
++ QList<qlonglong> 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<QModelIndex> 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<qlonglong, int>::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<QModelIndex> ImageModel::indexesForImageId(qlonglong id) const
++{
++ QList<QModelIndex> indexes;
++ QHash<qlonglong, int>::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<qlonglong,int>::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<ImageModel*>();
++ 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<ImageModel*>();
++ 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; i<size; ++i)
++ {
++ if (d->infos.at(i).filePath() == filePath)
++ {
++ return createIndex(i, 0);
++ }
++ }
++ }
++
++ return QModelIndex();
++}
++
++QList<QModelIndex> ImageModel::indexesForPath(const QString& filePath) const
++{
++ if (d->keepFilePathCache)
++ {
++ return indexesForImageId(d->filePathHash.value(filePath));
++ }
++ else
++ {
++ QList<QModelIndex> indexes;
++ const int size = d->infos.size();
++
++ for (int i=0; i<size; ++i)
++ {
++ if (d->infos.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<ImageInfo> ImageModel::imageInfos(const QString& filePath) const
++{
++ QList<ImageInfo> 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<ImageInfo>() << info, QList<QVariant>());
++}
++
++void ImageModel::addImageInfos(const QList<ImageInfo>& infos)
++{
++ addImageInfos(infos, QList<QVariant>());
++}
++
++void ImageModel::addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>() << info, QList<QVariant>());
++}
++
++void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos)
++{
++ addImageInfos(infos, QList<QVariant>());
++}
++
++void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (infos.isEmpty())
++ {
++ return;
++ }
++
++ publiciseInfos(infos, extraValues);
++ emit processAdded(infos, extraValues);
++}
++
++void ImageModel::ensureHasImageInfo(const ImageInfo& info)
++{
++ ensureHasImageInfos(QList<ImageInfo>() << info, QList<QVariant>());
++}
++
++void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos)
++{
++ ensureHasImageInfos(infos, QList<QVariant>());
++}
++
++void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos)
++{
++ clearImageInfos();
++ addImageInfos(infos);
++}
++
++QList<ImageInfo> ImageModel::imageInfos() const
++{
++ return d->infos;
++}
++
++QList<qlonglong> 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<qlonglong, int>::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<ImageInfo> ImageModel::uniqueImageInfos() const
++{
++ if (d->extraValues.isEmpty())
++ {
++ return d->infos;
++ }
++
++ QList<ImageInfo> uniqueInfos;
++ const int size = d->infos.size();
++
++ for (int i=0; i<size; ++i)
++ {
++ const ImageInfo& info = d->infos.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<ImageInfo>,QList<QVariant>)), 0, 0);
++ disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
++ disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished()));
++ }
++}
++
++void ImageModel::appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (infos.isEmpty())
++ {
++ return;
++ }
++
++ if (d->preprocessor)
++ {
++ d->reAdding = true;
++ emit preprocess(infos, extraValues);
++ }
++ else
++ {
++ publiciseInfos(infos, extraValues);
++ }
++}
++
++void ImageModel::appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ // This method does deduplication. It is private because in context of readding or refreshing it is of no use.
++
++ if (extraValues.isEmpty())
++ {
++ QList<ImageInfo> checkedInfos;
++
++ foreach (const ImageInfo& info, infos)
++ {
++ if (!hasImage(info))
++ {
++ checkedInfos << info;
++ }
++ }
++
++ appendInfos(checkedInfos, QList<QVariant>());
++ }
++ else
++ {
++ QList<ImageInfo> checkedInfos;
++ QList<QVariant> checkedExtraValues;
++ const int size = infos.size();
++
++ for (int i=0; i<size; i++)
++ {
++ if (!hasImage(infos[i], extraValues[i]))
++ {
++ checkedInfos << infos[i];
++ checkedExtraValues << extraValues[i];
++ }
++ }
++
++ appendInfos(checkedInfos, checkedExtraValues);
++ }
++}
++
++void ImageModel::reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos, const QList<QVariant>& 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<QPair<int, int> > 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<QModelIndex>() << index);
++}
++
++void ImageModel::removeIndexes(const QList<QModelIndex>& indexes)
++{
++ QList<int> 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<ImageInfo>() << info);
++}
++
++void ImageModel::removeImageInfos(const QList<ImageInfo>& infos)
++{
++ QList<int> 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<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (extraValues.isEmpty())
++ {
++ removeImageInfos(infos);
++ return;
++ }
++
++ QList<int> listIndexes;
++
++ for (int i=0; i<infos.size(); ++i)
++ {
++ QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i));
++
++ if (index.isValid())
++ {
++ listIndexes << index.row();
++ }
++ }
++
++ removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
++}
++
++void ImageModel::setSendRemovalSignals(bool send)
++{
++ d->sendRemovalSignals = send;
++}
++
++template <class List, typename T>
++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<QPair<int, int> >& toRemove)
++{
++ if (d->incrementalUpdater)
++ {
++ d->incrementalUpdater->aboutToBeRemovedInModel(toRemove);
++ }
++
++ removeRowPairs(toRemove);
++}
++
++void ImageModel::removeRowPairs(const QList<QPair<int, int> >& 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<int, int> 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<ImageInfo> 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<qlonglong, int>::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<QString, qlonglong>::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<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (extraValues.isEmpty())
++ {
++ foreach(const ImageInfo& info, infos)
++ {
++ QHash<qlonglong,int>::iterator it = oldIds.find(info.id());
++
++ if (it != oldIds.end())
++ {
++ oldIds.erase(it);
++ }
++ else
++ {
++ newInfos << info;
++ }
++ }
++ }
++ else
++ {
++ for (int i=0; i<infos.size(); ++i)
++ {
++ const ImageInfo& info = infos.at(i);
++ bool found = false;
++ QHash<qlonglong,int>::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<QPair<int, int> > 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<qlonglong, int>::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<QPair<int, int> > ImageModelIncrementalUpdater::toContiguousPairs(const QList<int>& unsorted)
++{
++ // Take the given indices and return them as contiguous pairs [begin, end]
++
++ QList<QPair<int, int> > pairs;
++
++ if (unsorted.isEmpty())
++ {
++ return pairs;
++ }
++
++ QList<int> indices(unsorted);
++ qSort(indices);
++
++ QPair<int, int> pair(indices.first(), indices.first());
++
++ for (int i=1; i<indices.size(); ++i)
++ {
++ const int &index = indices.at(i);
++
++ if (index == pair.second + 1)
++ {
++ pair.second = index;
++ continue;
++ }
++
++ pairs << pair; // insert last pair
++ pair.first = index;
++ pair.second = index;
++ }
++
++ pairs << pair;
++
++ return pairs;
++}
++
++// ------------ QAbstractItemModel implementation -------------
++
++QVariant ImageModel::data(const QModelIndex& index, int role) const
++{
++ if (!d->isValid(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<ImageModel*>(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 <marcel dot wiesweg at gmx dot de>
++ *
++ * 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 <QAbstractListModel>
++
++// 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<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
++ QList<qlonglong> imageIds(const QList<QModelIndex>& 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<QModelIndex> indexesForImageInfo(const ImageInfo& info) const;
++ QList<QModelIndex> 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<QModelIndex> indexesForPath(const QString& filePath) const;
++ QList<ImageInfo> 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<ImageInfo>& infos);
++ void addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++
++ /** Clears image infos and resets model.
++ */
++ void clearImageInfos();
++
++ /** Clears and adds the infos.
++ */
++ void setImageInfos(const QList<ImageInfo>& infos);
++
++ /**
++ * Directly remove the given indexes or infos from the model.
++ */
++ void removeIndex(const QModelIndex& indexes);
++ void removeIndexes(const QList<QModelIndex>& indexes);
++ void removeImageInfo(const ImageInfo& info);
++ void removeImageInfos(const QList<ImageInfo>& infos);
++ void removeImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos);
++ void addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos);
++ void ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++
++ /**
++ * Ensure that all images grouped on the given leader are contained in the model.
++ */
++ void ensureHasGroupedImages(const ImageInfo& groupLeader);
++
++ QList<ImageInfo> imageInfos() const;
++ QList<qlonglong> imageIds() const;
++ QList<ImageInfo> 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<ImageInfo>& 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<ImageInfo>& 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<ImageInfo>& 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<ImageInfo>& infos);
++
++ /** Connect to this signal only if you are the current preprocessor.
++ */
++ void preprocess(const QList<ImageInfo>& infos, const QList<QVariant>&);
++ void processAdded(const QList<ImageInfo>& infos, const QList<QVariant>&);
++
++ /** 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<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void publiciseInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void cleanSituationChecks();
++ void removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove);
++ void removeRowPairs(const QList<QPair<int, int> >& 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * 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 <QDateTime>
++#include <QRectF>
++
++// 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 <marcel dot wiesweg at gmx dot de>
++ *
++ * 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 <QHash>
++#include <QList>
++#include <QMap>
++#include <QString>
++#include <QCollator>
++
++// 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 <typename T>
++ 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 <typename T>
++ 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 <typename T>
++ 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ *
++ * 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 <QHash>
++
++// 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<int> 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<QModelIndex>& indexesToPrepare)
++{
++ prepareThumbnails(indexesToPrepare, d->thumbSize);
++}
++
++void ImageThumbnailModel::prepareThumbnails(const QList<QModelIndex>& indexesToPrepare, const ThumbnailSize& thumbSize)
++{
++ if (!d->thread)
++ {
++ return;
++ }
++
++ QList<ThumbnailIdentifier> ids;
++ foreach(const QModelIndex& index, indexesToPrepare)
++ {
++ ids << imageInfoRef(index).thumbnailIdentifier();
++ }
++ d->thread->findGroup(ids, thumbSize.size());
++}
++
++void ImageThumbnailModel::preloadThumbnails(const QList<ImageInfo>& infos)
++{
++ if (!d->preloadThread)
++ {
++ return;
++ }
++
++ QList<ThumbnailIdentifier> ids;
++ foreach(const ImageInfo& info, infos)
++ {
++ ids << info.thumbnailIdentifier();
++ }
++ d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize());
++}
++
++void ImageThumbnailModel::preloadThumbnails(const QList<QModelIndex>& indexesToPreload)
++{
++ if (!d->preloadThread)
++ {
++ return;
++ }
++
++ QList<ThumbnailIdentifier> 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 <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ *
++ * 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<QModelIndex>& indexesToPrepare);
++ void prepareThumbnails(const QList<QModelIndex>& 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<ImageInfo>&);
++ void preloadThumbnails(const QList<QModelIndex>&);
++ 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 <martin dot klapetek at gmail dot com>
++ *
++ * 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 <klocalizedstring.h>
++
++// 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<QPair<QString, int> >* 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<QPair<QString, int> >;
++}
++
++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<QPair<QString, int> >& 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 <martin dot klapetek at gmail dot com>
++ *
++ * 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 <QModelIndex>
++#include <QPixmap>
++
++// 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<QPair<QString, int> >& 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * 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<ImageModel*>(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<QModelIndex> ImageSortFilterModel::mapListToSource(const QList<QModelIndex>& indexes) const
+-{
+- QList<QModelIndex> sourceIndexes;
+- foreach(const QModelIndex& index, indexes)
+- {
+- sourceIndexes << mapToSourceImageModel(index);
+- }
+- return sourceIndexes;
+-}
+-
+-QList<QModelIndex> ImageSortFilterModel::mapListFromSource(const QList<QModelIndex>& sourceIndexes) const
+-{
+- QList<QModelIndex> 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<ImageInfo> ImageSortFilterModel::imageInfos(const QList<QModelIndex>& indexes) const
+-{
+- QList<ImageInfo> infos;
+- ImageModel* const model = sourceImageModel();
+-
+- foreach(const QModelIndex& index, indexes)
+- {
+- infos << model->imageInfo(mapToSourceImageModel(index));
+- }
+-
+- return infos;
+-}
+-
+-QList<qlonglong> ImageSortFilterModel::imageIds(const QList<QModelIndex>& indexes) const
+-{
+- QList<qlonglong> 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<ImageInfo> ImageSortFilterModel::imageInfosSorted() const
+-{
+- QList<ImageInfo> infos;
+- const int size = rowCount();
+- ImageModel* const model = sourceImageModel();
+-
+- for (int i=0; i<size; ++i)
+- {
+- infos << model->imageInfo(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<ImageInfo>,QList<QVariant>)),
+- d, SLOT(preprocessInfos(QList<ImageInfo>,QList<QVariant>)));
+-
+- connect(d->imageModel, SIGNAL(processAdded(QList<ImageInfo>,QList<QVariant>)),
+- d, SLOT(processAddedInfos(QList<ImageInfo>,QList<QVariant>)));
+-
+- connect(d, SIGNAL(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)),
+- d->imageModel, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
+-
+- 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<ImageFilterModel*>(this));
+- }
+-
+- return DCategorizedSortFilterProxyModel::data(index, role);
+-}
+-
+-ImageFilterModel* ImageFilterModel::imageFilterModel() const
+-{
+- return const_cast<ImageFilterModel*>(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<QDateTime>& days)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setDayFilter(days);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
+- ImageFilterSettings::MatchingCondition matchingCond,
+- bool showUnTagged, const QList<int>& clTagIds, const QList<int>& 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<QUrl> urlList, const QString& id)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setUrlWhitelist(urlList, id);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setIdWhitelist(const QList<qlonglong>& 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<qlonglong>& 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<qlonglong, bool>::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<ImageInfo> 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<ImageInfo> 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<ImageFilterModelPrepareHook*> 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<size; ++i)
+- {
+- *p = 'a' + (number & 0xF);
+- number >>= 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * 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<ImageInfo>& 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<QModelIndex> mapListToSource(const QList<QModelIndex>& indexes) const;
+- QList<QModelIndex> mapListFromSource(const QList<QModelIndex>& sourceIndexes) const;
+-
+- ImageInfo imageInfo(const QModelIndex& index) const;
+- qlonglong imageId(const QModelIndex& index) const;
+- QList<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
+- QList<qlonglong> imageIds(const QList<QModelIndex>& 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<ImageInfo> 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<QDateTime>& days);
+- void setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
+- ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged,
+- const QList<int>& clTagIds, const QList<int>& 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<QUrl> urlList, const QString& id);
+- void setIdWhitelist(const QList<qlonglong>& idList, const QString& id);
+-
+- void setVersionManagerSettings(const VersionManagerSettings& settings);
+- void setExceptionList(const QList<qlonglong>& 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<ImageInfo>& infos);
+- void imageInfosAboutToBeRemoved(const QList<ImageInfo>& 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * 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>("ImageFilterModelTodoPackage");
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- infosToProcess(infos, extraValues, true);
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos)
+-{
+- infosToProcess(infos, QList<QVariant>(), false);
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>::const_iterator it = infos.constBegin(), end;
+- QList<QVariant>::const_iterator xit = extraValues.constBegin(), xend;
+- int index = 0;
+- QVector<ImageInfo> infoVector;
+- QVector<QVariant> 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<qlonglong, bool>::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 <marcel dot wiesweg at gmx dot de>
+- *
+- * 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 <QHash>
+-#include <QMutex>
+-#include <QMutexLocker>
+-#include <QSet>
+-#include <QThread>
+-#include <QTimer>
+-#include <QWaitCondition>
+-
+-// 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<ImageInfo>& infos, const QVector<QVariant>& extraValues, int version, bool isForReAdd)
+- : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd)
+- {
+- }
+-
+- QVector<ImageInfo> infos;
+- QVector<QVariant> extraValues;
+- unsigned int version;
+- bool isForReAdd;
+- QHash<qlonglong, bool> 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<ImageInfo>& infos);
+- void infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& 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<qlonglong, bool> filterResults;
+- bool hasOneMatch;
+- bool hasOneMatchForText;
+-
+- QList<ImageFilterModelPrepareHook*> prepareHooks;
+-
+-/*
+- QHash<int, QSet<qlonglong> > categoryCountHashInt;
+- QHash<QString, QSet<qlonglong> > categoryCountHashString;
+-
+-public:
+-
+- void cacheCategoryCount(int id, qlonglong imageid) const
+- { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashInt[id].insert(imageid); }
+- void cacheCategoryCount(const QString& id, qlonglong imageid) const
+- { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashString[id].insert(imageid); }
+-*/
+-
+-public Q_SLOTS:
+-
+- void preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos, const QList<QVariant>& 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * 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 <marcel dot wiesweg at gmx dot de>
+- *
+- * 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 <QThread>
+-
+-// 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * 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 <cmath>
+-
+-// Qt includes
+-
+-#include <QDateTime>
+-
+-// 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<QDateTime>& days)
+-{
+- m_dayFilter.clear();
+-
+- for (QList<QDateTime>::const_iterator it = days.constBegin(); it != days.constEnd(); ++it)
+- {
+- m_dayFilter.insert(*it, true);
+- }
+-}
+-
+-void ImageFilterSettings::setTagFilter(const QList<int>& includedTags,
+- const QList<int>& excludedTags,
+- MatchingCondition matchingCondition,
+- bool showUnTagged,
+- const QList<int>& clTagIds,
+- const QList<int>& 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<int, QString>& hash)
+-{
+- m_tagNameHash = hash;
+-}
+-
+-void ImageFilterSettings::setAlbumNames(const QHash<int, QString>& hash)
+-{
+- m_albumNameHash = hash;
+-}
+-
+-void ImageFilterSettings::setUrlWhitelist(const QList<QUrl>& urlList, const QString& id)
+-{
+- if (urlList.isEmpty())
+- {
+- m_urlWhitelists.remove(id);
+- }
+- else
+- {
+- m_urlWhitelists.insert(id, urlList);
+- }
+-}
+-
+-void ImageFilterSettings::setIdWhitelist(const QList<qlonglong>& idList, const QString& id)
+-{
+- if (idList.isEmpty())
+- {
+- m_idWhitelists.remove(id);
+- }
+- else
+- {
+- m_idWhitelists.insert(id, idList);
+- }
+-}
+-
+-template <class ContainerA, class ContainerB>
+-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 <class ContainerA, typename Value, class ContainerB>
+-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<int> tagIds = info.tagIds();
+- QList<int>::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<int> 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<int> 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<QString, QList<QUrl>>::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<QString, QList<qlonglong> >::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<QString, QList<qlonglong> >::const_iterator it = m_exceptionLists.constBegin();
+- it != m_exceptionLists.constEnd(); ++it)
+- {
+- if (it->contains(id))
+- {
+- return true;
+- }
+- }
+-
+- bool match = true;
+- QList<int> tagIds = info.tagIds();
+-
+- if (!tagIds.contains(m_includeTagFilter))
+- {
+- for (QList<int>::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<int> 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<qlonglong>& 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * 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 <QHash>
+-#include <QList>
+-#include <QMap>
+-#include <QString>
+-#include <QSet>
+-#include <QUrl>
+-
+-// 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<int>& includedTags,
+- const QList<int>& excludedTags,
+- MatchingCondition matchingCond,
+- bool showUnTagged,
+- const QList<int>& clTagIds,
+- const QList<int>& 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<QDateTime>& days);
+-
+-public:
+-
+- /// --- Text filter ---
+- void setTextFilter(const SearchTextFilterSettings& settings);
+- void setTagNames(const QHash<int, QString>& tagNameHash);
+- void setAlbumNames(const QHash<int, QString>& 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<QUrl>& urlList, const QString& id);
+-
+-public:
+-
+- /// --- ID whitelist filter
+- void setIdWhitelist(const QList<qlonglong>& 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<int> m_includeTagFilter;
+- QList<int> m_excludeTagFilter;
+- MatchingCondition m_matchingCond;
+- QList<int> m_colorLabelTagFilter;
+- QList<int> m_pickLabelTagFilter;
+-
+- /// --- Rating filter ---
+- int m_ratingFilter;
+- RatingCondition m_ratingCond;
+- bool m_isUnratedExcluded;
+-
+- /// --- Date filter ---
+- QMap<QDateTime, bool> 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<int, QString> m_tagNameHash;
+- QHash<int, QString> m_albumNameHash;
+-
+- /// --- Mime filter ---
+- MimeFilter::TypeMimeFilter m_mimeTypeFilter;
+-
+- /// --- Geolocation filter
+- GeolocationCondition m_geolocationCondition;
+-
+- /// --- URL whitelist filter
+- QHash<QString,QList<QUrl>> m_urlWhitelists;
+-
+- /// --- ID whitelist filter
+- QHash<QString,QList<qlonglong> > 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<qlonglong>& 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<int> m_excludeTagFilter;
+- int m_includeTagFilter;
+- int m_exceptionTagFilter;
+- QHash<QString,QList<qlonglong> > 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<qlonglong> 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 <marcel dot wiesweg at gmx dot de>
+- *
+- * 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 <marcel dot wiesweg at gmx dot de>
+- *
+- * 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<ImageInfo>& 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 <marcel dot wiesweg at gmx dot de>
+- *
+- * 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 <QHash>
+-#include <QItemSelection>
+-
+-// 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<QVariant> extraValues;
+- QHash<qlonglong, int> idHash;
+-
+- bool keepFilePathCache;
+- QHash<QString, qlonglong> filePathHash;
+-
+- bool sendRemovalSignals;
+-
+- QObject* preprocessor;
+- bool refreshing;
+- bool reAdding;
+- bool incrementalRefreshRequested;
+-
+- DatabaseFields::Set watchFlags;
+-
+- class ImageModelIncrementalUpdater* incrementalUpdater;
+-
+- ImageInfoList pendingInfos;
+- QList<QVariant> 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<int, int> IntPair; // to make foreach macro happy
+-typedef QList<IntPair> IntPairList;
+-
+-class ImageModelIncrementalUpdater
+-{
+-public:
+-
+- explicit ImageModelIncrementalUpdater(ImageModel::Private* d);
+-
+- void appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved);
+- QList<IntPair> oldIndexes();
+-
+- static QList<IntPair> toContiguousPairs(const QList<int>& ids);
+-
+-public:
+-
+- QHash<qlonglong, int> oldIds;
+- QList<QVariant> oldExtraValues;
+- QList<ImageInfo> newInfos;
+- QList<QVariant> newExtraValues;
+- QList<IntPairList> 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<ImageInfo> ImageModel::imageInfos(const QList<QModelIndex>& indexes) const
+-{
+- QList<ImageInfo> infos;
+-
+- foreach(const QModelIndex& index, indexes)
+- {
+- infos << imageInfo(index);
+- }
+-
+- return infos;
+-}
+-
+-QList<qlonglong> ImageModel::imageIds(const QList<QModelIndex>& indexes) const
+-{
+- QList<qlonglong> 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<QModelIndex> 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<qlonglong, int>::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<QModelIndex> ImageModel::indexesForImageId(qlonglong id) const
+-{
+- QList<QModelIndex> indexes;
+- QHash<qlonglong, int>::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<qlonglong,int>::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<ImageModel*>();
+- 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<ImageModel*>();
+- 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; i<size; ++i)
+- {
+- if (d->infos.at(i).filePath() == filePath)
+- {
+- return createIndex(i, 0);
+- }
+- }
+- }
+-
+- return QModelIndex();
+-}
+-
+-QList<QModelIndex> ImageModel::indexesForPath(const QString& filePath) const
+-{
+- if (d->keepFilePathCache)
+- {
+- return indexesForImageId(d->filePathHash.value(filePath));
+- }
+- else
+- {
+- QList<QModelIndex> indexes;
+- const int size = d->infos.size();
+-
+- for (int i=0; i<size; ++i)
+- {
+- if (d->infos.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<ImageInfo> ImageModel::imageInfos(const QString& filePath) const
+-{
+- QList<ImageInfo> 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<ImageInfo>() << info, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfos(const QList<ImageInfo>& infos)
+-{
+- addImageInfos(infos, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>() << info, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos)
+-{
+- addImageInfos(infos, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (infos.isEmpty())
+- {
+- return;
+- }
+-
+- publiciseInfos(infos, extraValues);
+- emit processAdded(infos, extraValues);
+-}
+-
+-void ImageModel::ensureHasImageInfo(const ImageInfo& info)
+-{
+- ensureHasImageInfos(QList<ImageInfo>() << info, QList<QVariant>());
+-}
+-
+-void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos)
+-{
+- ensureHasImageInfos(infos, QList<QVariant>());
+-}
+-
+-void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos)
+-{
+- clearImageInfos();
+- addImageInfos(infos);
+-}
+-
+-QList<ImageInfo> ImageModel::imageInfos() const
+-{
+- return d->infos;
+-}
+-
+-QList<qlonglong> 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<qlonglong, int>::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<ImageInfo> ImageModel::uniqueImageInfos() const
+-{
+- if (d->extraValues.isEmpty())
+- {
+- return d->infos;
+- }
+-
+- QList<ImageInfo> uniqueInfos;
+- const int size = d->infos.size();
+-
+- for (int i=0; i<size; ++i)
+- {
+- const ImageInfo& info = d->infos.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<ImageInfo>,QList<QVariant>)), 0, 0);
+- disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
+- disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished()));
+- }
+-}
+-
+-void ImageModel::appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (infos.isEmpty())
+- {
+- return;
+- }
+-
+- if (d->preprocessor)
+- {
+- d->reAdding = true;
+- emit preprocess(infos, extraValues);
+- }
+- else
+- {
+- publiciseInfos(infos, extraValues);
+- }
+-}
+-
+-void ImageModel::appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- // This method does deduplication. It is private because in context of readding or refreshing it is of no use.
+-
+- if (extraValues.isEmpty())
+- {
+- QList<ImageInfo> checkedInfos;
+-
+- foreach (const ImageInfo& info, infos)
+- {
+- if (!hasImage(info))
+- {
+- checkedInfos << info;
+- }
+- }
+-
+- appendInfos(checkedInfos, QList<QVariant>());
+- }
+- else
+- {
+- QList<ImageInfo> checkedInfos;
+- QList<QVariant> checkedExtraValues;
+- const int size = infos.size();
+-
+- for (int i=0; i<size; i++)
+- {
+- if (!hasImage(infos[i], extraValues[i]))
+- {
+- checkedInfos << infos[i];
+- checkedExtraValues << extraValues[i];
+- }
+- }
+-
+- appendInfos(checkedInfos, checkedExtraValues);
+- }
+-}
+-
+-void ImageModel::reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos, const QList<QVariant>& 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<QPair<int, int> > 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<QModelIndex>() << index);
+-}
+-
+-void ImageModel::removeIndexes(const QList<QModelIndex>& indexes)
+-{
+- QList<int> 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<ImageInfo>() << info);
+-}
+-
+-void ImageModel::removeImageInfos(const QList<ImageInfo>& infos)
+-{
+- QList<int> 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<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (extraValues.isEmpty())
+- {
+- removeImageInfos(infos);
+- return;
+- }
+-
+- QList<int> listIndexes;
+-
+- for (int i=0; i<infos.size(); ++i)
+- {
+- QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i));
+-
+- if (index.isValid())
+- {
+- listIndexes << index.row();
+- }
+- }
+-
+- removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
+-}
+-
+-void ImageModel::setSendRemovalSignals(bool send)
+-{
+- d->sendRemovalSignals = send;
+-}
+-
+-template <class List, typename T>
+-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<QPair<int, int> >& toRemove)
+-{
+- if (d->incrementalUpdater)
+- {
+- d->incrementalUpdater->aboutToBeRemovedInModel(toRemove);
+- }
+-
+- removeRowPairs(toRemove);
+-}
+-
+-void ImageModel::removeRowPairs(const QList<QPair<int, int> >& 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<int, int> 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<ImageInfo> 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<qlonglong, int>::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<QString, qlonglong>::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<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (extraValues.isEmpty())
+- {
+- foreach(const ImageInfo& info, infos)
+- {
+- QHash<qlonglong,int>::iterator it = oldIds.find(info.id());
+-
+- if (it != oldIds.end())
+- {
+- oldIds.erase(it);
+- }
+- else
+- {
+- newInfos << info;
+- }
+- }
+- }
+- else
+- {
+- for (int i=0; i<infos.size(); ++i)
+- {
+- const ImageInfo& info = infos.at(i);
+- bool found = false;
+- QHash<qlonglong,int>::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<QPair<int, int> > 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<qlonglong, int>::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<QPair<int, int> > ImageModelIncrementalUpdater::toContiguousPairs(const QList<int>& unsorted)
+-{
+- // Take the given indices and return them as contiguous pairs [begin, end]
+-
+- QList<QPair<int, int> > pairs;
+-
+- if (unsorted.isEmpty())
+- {
+- return pairs;
+- }
+-
+- QList<int> indices(unsorted);
+- qSort(indices);
+-
+- QPair<int, int> pair(indices.first(), indices.first());
+-
+- for (int i=1; i<indices.size(); ++i)
+- {
+- const int &index = indices.at(i);
+-
+- if (index == pair.second + 1)
+- {
+- pair.second = index;
+- continue;
+- }
+-
+- pairs << pair; // insert last pair
+- pair.first = index;
+- pair.second = index;
+- }
+-
+- pairs << pair;
+-
+- return pairs;
+-}
+-
+-// ------------ QAbstractItemModel implementation -------------
+-
+-QVariant ImageModel::data(const QModelIndex& index, int role) const
+-{
+- if (!d->isValid(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<ImageModel*>(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 <marcel dot wiesweg at gmx dot de>
+- *
+- * 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 <QAbstractListModel>
+-
+-// 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<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
+- QList<qlonglong> imageIds(const QList<QModelIndex>& 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<QModelIndex> indexesForImageInfo(const ImageInfo& info) const;
+- QList<QModelIndex> 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<QModelIndex> indexesForPath(const QString& filePath) const;
+- QList<ImageInfo> 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<ImageInfo>& infos);
+- void addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+-
+- /** Clears image infos and resets model.
+- */
+- void clearImageInfos();
+-
+- /** Clears and adds the infos.
+- */
+- void setImageInfos(const QList<ImageInfo>& infos);
+-
+- /**
+- * Directly remove the given indexes or infos from the model.
+- */
+- void removeIndex(const QModelIndex& indexes);
+- void removeIndexes(const QList<QModelIndex>& indexes);
+- void removeImageInfo(const ImageInfo& info);
+- void removeImageInfos(const QList<ImageInfo>& infos);
+- void removeImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos);
+- void addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos);
+- void ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+-
+- /**
+- * Ensure that all images grouped on the given leader are contained in the model.
+- */
+- void ensureHasGroupedImages(const ImageInfo& groupLeader);
+-
+- QList<ImageInfo> imageInfos() const;
+- QList<qlonglong> imageIds() const;
+- QList<ImageInfo> 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<ImageInfo>& 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<ImageInfo>& 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<ImageInfo>& 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<ImageInfo>& infos);
+-
+- /** Connect to this signal only if you are the current preprocessor.
+- */
+- void preprocess(const QList<ImageInfo>& infos, const QList<QVariant>&);
+- void processAdded(const QList<ImageInfo>& infos, const QList<QVariant>&);
+-
+- /** 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<ImageInfo>& infos, const QList<QVariant>& 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<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void publiciseInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void cleanSituationChecks();
+- void removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove);
+- void removeRowPairs(const QList<QPair<int, int> >& 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * 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 <QDateTime>
+-#include <QRectF>
+-
+-// 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 <marcel dot wiesweg at gmx dot de>
+- *
+- * 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 <QHash>
+-#include <QList>
+-#include <QMap>
+-#include <QString>
+-#include <QCollator>
+-
+-// 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 <typename T>
+- 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 <typename T>
+- 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 <typename T>
+- 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- *
+- * 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 <QHash>
+-
+-// 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<int> 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<QModelIndex>& indexesToPrepare)
+-{
+- prepareThumbnails(indexesToPrepare, d->thumbSize);
+-}
+-
+-void ImageThumbnailModel::prepareThumbnails(const QList<QModelIndex>& indexesToPrepare, const ThumbnailSize& thumbSize)
+-{
+- if (!d->thread)
+- {
+- return;
+- }
+-
+- QList<ThumbnailIdentifier> ids;
+- foreach(const QModelIndex& index, indexesToPrepare)
+- {
+- ids << imageInfoRef(index).thumbnailIdentifier();
+- }
+- d->thread->findGroup(ids, thumbSize.size());
+-}
+-
+-void ImageThumbnailModel::preloadThumbnails(const QList<ImageInfo>& infos)
+-{
+- if (!d->preloadThread)
+- {
+- return;
+- }
+-
+- QList<ThumbnailIdentifier> ids;
+- foreach(const ImageInfo& info, infos)
+- {
+- ids << info.thumbnailIdentifier();
+- }
+- d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize());
+-}
+-
+-void ImageThumbnailModel::preloadThumbnails(const QList<QModelIndex>& indexesToPreload)
+-{
+- if (!d->preloadThread)
+- {
+- return;
+- }
+-
+- QList<ThumbnailIdentifier> 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 <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- *
+- * 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<QModelIndex>& indexesToPrepare);
+- void prepareThumbnails(const QList<QModelIndex>& 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<ImageInfo>&);
+- void preloadThumbnails(const QList<QModelIndex>&);
+- 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 <martin dot klapetek at gmail dot com>
+- *
+- * 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 <klocalizedstring.h>
+-
+-// 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<QPair<QString, int> >* 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<QPair<QString, int> >;
+-}
+-
+-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<QPair<QString, int> >& 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 <martin dot klapetek at gmail dot com>
+- *
+- * 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 <QModelIndex>
+-#include <QPixmap>
+-
+-// 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<QPair<QString, int> >& 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
+