rev 20819 - in kde-extras/digikam/trunk/debian: . patches

Steven Michael Robbins smr at moszumanska.debian.org
Mon Aug 14 01:49:59 UTC 2017


Author: smr
Date: 2017-08-14 01:49:59 +0000 (Mon, 14 Aug 2017)
New Revision: 20819

Added:
   kde-extras/digikam/trunk/debian/patches/cmake-v9-fix.patch
Modified:
   kde-extras/digikam/trunk/debian/changelog
   kde-extras/digikam/trunk/debian/patches/series
Log:
Initial packaging of Digikam 5.6.0

Modified: kde-extras/digikam/trunk/debian/changelog
===================================================================
--- kde-extras/digikam/trunk/debian/changelog	2017-08-13 04:12:42 UTC (rev 20818)
+++ kde-extras/digikam/trunk/debian/changelog	2017-08-14 01:49:59 UTC (rev 20819)
@@ -1,9 +1,12 @@
-digikam (4:5.3.0-2) unstable; urgency=medium
+digikam (4:5.6.0-1) unstable; urgency=medium
 
   * kipi-plugins: Change konqueror from Recommends to Suggested.  Closes:
     #792163
+    
+  * patches/cmake-v9-fix.patch: New.  Upstream patch required to build
+    using CMake 3.9.
 
- -- Steve M. Robbins <smr at debian.org>  Sat, 19 Nov 2016 23:27:08 -0600
+ -- Steve M. Robbins <smr at debian.org>  Sun, 13 Aug 2017 20:48:08 -0500
 
 digikam (4:5.3.0-1) unstable; urgency=medium
 

Added: kde-extras/digikam/trunk/debian/patches/cmake-v9-fix.patch
===================================================================
--- kde-extras/digikam/trunk/debian/patches/cmake-v9-fix.patch	                        (rev 0)
+++ kde-extras/digikam/trunk/debian/patches/cmake-v9-fix.patch	2017-08-14 01:49:59 UTC (rev 20819)
@@ -0,0 +1,13340 @@
+From 7e00441c257e7e9e5dc5ab983fc06046fb72b0c5 Mon Sep 17 00:00:00 2001
+From: Gilles Caulier <caulier.gilles at 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
+ 
+ Note from Steve Robbins: this upstream patch also fixes compilation errors
+ noted when using CMake 3.9; see https://bugs.kde.org/show_bug.cgi?id=382703
+
+---
+ 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/core/libs/database/CMakeLists.txt
++++ b/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/libs/models/CMakeLists.txt
++++ b/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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/core/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
+

Modified: kde-extras/digikam/trunk/debian/patches/series
===================================================================
--- kde-extras/digikam/trunk/debian/patches/series	2017-08-13 04:12:42 UTC (rev 20818)
+++ kde-extras/digikam/trunk/debian/patches/series	2017-08-14 01:49:59 UTC (rev 20819)
@@ -1 +1,2 @@
 cmake-disable-jasper.patch
+cmake-v9-fix.patch




More information about the pkg-kde-commits mailing list