summaryrefslogtreecommitdiffstats
path: root/kde/patch
diff options
context:
space:
mode:
author Eric Hameleers <alien@slackware.com>2018-06-13 14:50:49 +0200
committer Eric Hameleers <alien@slackware.com>2018-06-13 14:50:49 +0200
commitc779e019ac53019baa07eb843aba59bc55ffec20 (patch)
treef4dd959f71cea6ac6a8741a1a8f9933a389f5bc7 /kde/patch
parente0d005c66dbed44be15070995d9d9e9200c989fa (diff)
downloadktown-c779e019ac53019baa07eb843aba59bc55ffec20.tar.gz
ktown-c779e019ac53019baa07eb843aba59bc55ffec20.tar.xz
Updated 'testing' in preparation for June '18 release of Plasma 5.13
Diffstat (limited to 'kde/patch')
-rw-r--r--kde/patch/akonadi.patch12
-rw-r--r--kde/patch/akonadi/akonadi_mariadb_qtsql.patch91
-rw-r--r--kde/patch/akonadi/akonadi_rename-header.patch77
-rw-r--r--kde/patch/akonadi/akonadi_revert-abs-path.patch70
-rw-r--r--kde/patch/ark.patch3
-rw-r--r--kde/patch/ark/ark_include_memory.patch15
-rw-r--r--kde/patch/cantor.patch4
-rw-r--r--kde/patch/cantor/cantor_julia.patch193
-rw-r--r--kde/patch/cantor/cantor_python.patch31
-rw-r--r--kde/patch/digikam.patch9
-rw-r--r--kde/patch/digikam/digikam_clang_fix.patch38
-rw-r--r--kde/patch/digikam/digikam_databasemodel.patch13337
-rw-r--r--kde/patch/dolphin.patch3
-rw-r--r--kde/patch/dolphin/dolphin_revert_noroot.patch44
-rw-r--r--kde/patch/gwenview.patch4
-rw-r--r--kde/patch/gwenview/gwenview-17.04.1_dataloss.patch131
-rw-r--r--kde/patch/kalzium.patch3
-rw-r--r--kde/patch/kalzium/kalzium_kf_5.31.patch156
-rw-r--r--kde/patch/kate.patch4
-rw-r--r--kde/patch/kate/kate_runasroot.patch48
-rw-r--r--kde/patch/kcalcore.patch4
-rw-r--r--kde/patch/kcalcore/kcalcore_libical3.patch109
-rw-r--r--kde/patch/kde-gtk-config.patch4
-rw-r--r--kde/patch/kde-gtk-config/kde-gtk-config_loadcurrentsettings.patch622
-rw-r--r--kde/patch/kde-runtime.patch3
-rw-r--r--kde/patch/kde-runtime/kde-runtime_gpgme.patch133
-rw-r--r--kde/patch/kdenlive.patch4
-rw-r--r--kde/patch/kdenlive/kdenlive_gcc7.patch32
-rw-r--r--kde/patch/kdepimlibs4.patch3
-rw-r--r--kde/patch/kdepimlibs4/kdepimlibs.libical3.diff184
-rw-r--r--kde/patch/kdesdk-kioslaves.patch2
-rw-r--r--kde/patch/kholidays.patch4
-rw-r--r--kde/patch/kholidays/kholidays_depfreeze_revert.patch61
-rw-r--r--kde/patch/kio.patch5
-rw-r--r--kde/patch/kio/kio_fix_url_setpath.patch65
-rw-r--r--kde/patch/konsole.patch5
-rw-r--r--kde/patch/konsole/konsole.term.is.konsole.patch24
-rw-r--r--kde/patch/kopete.patch9
-rw-r--r--kde/patch/kopete/kopete_kdebug376348.patch127
-rw-r--r--kde/patch/kopete/kopete_kdebug393372.patch30
-rw-r--r--kde/patch/kpat.patch5
-rw-r--r--kde/patch/kpat/kpat_no_freecell_solver_dep.patch1475
-rw-r--r--kde/patch/krita.patch3
-rw-r--r--kde/patch/krita/krita_qt59.patch26
-rw-r--r--kde/patch/ksudoku.patch4
-rw-r--r--kde/patch/ksudoku/ksudoku_qwindowtitle.patch15
-rw-r--r--kde/patch/ktexteditor.patch3
-rw-r--r--kde/patch/ktexteditor/ktexteditor_fix_indentation.patch32
-rw-r--r--kde/patch/kwin.patch11
-rw-r--r--kde/patch/kwin/kwin_cmake310.patch52
-rw-r--r--kde/patch/kwin/kwin_qt59_rootwindow_events.patch63
-rw-r--r--kde/patch/kwin/kwin_replace_logind_with_ck2.patch85
-rw-r--r--kde/patch/libkface.patch3
-rw-r--r--kde/patch/libkface/libkface_opencv3.patch61
-rw-r--r--kde/patch/libkleo.patch4
-rw-r--r--kde/patch/libkleo/libkleo_gcc7.patch27
-rw-r--r--kde/patch/oxygen-gtk2.patch3
-rw-r--r--kde/patch/oxygen-gtk2/oxygen-gtk2_KDEBUG_341181.patch115
-rw-r--r--kde/patch/perlqt.patch3
-rw-r--r--kde/patch/perlqt/perlqt.gcc6.diff11
-rw-r--r--kde/patch/plasma-workspace.patch11
-rw-r--r--kde/patch/plasma-workspace/plasma-workspace.systray_cpubug.patch152
-rw-r--r--kde/patch/plasma-workspace/plasma-workspace_kdebug389815.patch32
-rw-r--r--kde/patch/powerdevil.patch4
-rw-r--r--kde/patch/powerdevil/powerdevil-5.12.4_firstrun.patch42
-rw-r--r--kde/patch/pykde4.patch5
-rw-r--r--kde/patch/pykde4/0001-use-LIB_PYTHON-realpath.patch31
-rw-r--r--kde/patch/pykde4/0002-Add-some-missing-link-libraries.patch60
-rw-r--r--kde/patch/pykde4/0003-Fix-build-with-sip-4.19.patch599
-rw-r--r--kde/patch/sddm-qt5.patch15
-rw-r--r--kde/patch/sddm-qt5/sddm_avatars.patch33
-rw-r--r--kde/patch/sddm-qt5/sddm_consolekit.diff22
-rw-r--r--kde/patch/sddm-qt5/sddm_userxsession.diff13
73 files changed, 18708 insertions, 15 deletions
diff --git a/kde/patch/akonadi.patch b/kde/patch/akonadi.patch
new file mode 100644
index 0000000..ddab2a8
--- /dev/null
+++ b/kde/patch/akonadi.patch
@@ -0,0 +1,12 @@
+# Remove hardcoded absolute path to stdlib header.
+# This is Windows-centric and breaks on any linux GCC upgrade.
+# Thanks to Gentoo where I found the following two patches at
+# https://packages.gentoo.org/packages/kde-apps/akonadi
+# No longer needed since 17.04.0.
+#cat $CWD/patch/akonadi/akonadi_revert-abs-path.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+#cat $CWD/patch/akonadi/akonadi_rename-header.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Temporary fix for non-working Akonadi in combination with mariadb 10.2.8
+# (actually this is a bug in qtsql, not in akonadi or mariadb):
+#cat $CWD/patch/akonadi/akonadi_mariadb_qtsql.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/akonadi/akonadi_mariadb_qtsql.patch b/kde/patch/akonadi/akonadi_mariadb_qtsql.patch
new file mode 100644
index 0000000..7ec6d7c
--- /dev/null
+++ b/kde/patch/akonadi/akonadi_mariadb_qtsql.patch
@@ -0,0 +1,91 @@
+Patch taken from https://cgit.kde.org/akonadi.git/patch/?id=b145f47f000978b9d39edc1882849ec7f6b3ef79
+
+Upstream bug reports:
+https://bugs.kde.org/show_bug.cgi?id=383991
+https://bugreports.qt.io/browse/QTBUG-63108
+
+From b145f47f000978b9d39edc1882849ec7f6b3ef79 Mon Sep 17 00:00:00 2001
+From: Heinz Wiesinger <pprkut@liwjatan.at>
+Date: Sun, 17 Sep 2017 10:56:44 +0200
+Subject: Only remove init connections to the database on server shutdown.
+
+Summary:
+With MariaDB 10.2 libmysqlclient was replaced with libmariadb that
+changed how establishing database connections behaves. The MySQL
+QSQL driver calls mysql_server_end() on QSqlDatabase::removeDatabase()
+if the overall connection count dropped to 0 (which it does when
+the init connection is removed).
+A future QSqlDatabase:addDatabase() would call mysql_server_init()
+again, but this no longer works with libmariadb as that one only
+allows calling mysql_server_init() once. Future calls are simply
+ignored.
+
+In order to prevent this from happening we have to keep the
+init connection open until the server shuts down, so the connection
+count only drops to 0 at shutdown and mysql_server_end() isn't
+called before.
+
+This is a workaround for QTBUG-63108
+
+CCBUG: 383991
+
+Reviewers: dvratil, mlaurent
+
+Reviewed By: dvratil
+
+Subscribers: #kde_pim
+
+Tags: #kde_pim
+
+Differential Revision: https://phabricator.kde.org/D7858
+---
+ src/server/akonadi.cpp | 3 ++-
+ src/server/storage/dbconfigmysql.cpp | 4 +++-
+ 2 files changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/src/server/akonadi.cpp b/src/server/akonadi.cpp
+index 4364e63..bcb7e88 100644
+--- a/src/server/akonadi.cpp
++++ b/src/server/akonadi.cpp
+@@ -423,13 +423,14 @@ bool AkonadiServer::createDatabase()
+ success = false;
+ }
+ }
+- QSqlDatabase::removeDatabase(initCon);
+ return success;
+ }
+
+ void AkonadiServer::stopDatabaseProcess()
+ {
+ if (!DbConfig::configuredDatabase()->useInternalServer()) {
++ // closing initConnection this late to work around QTBUG-63108
++ QSqlDatabase::removeDatabase(QStringLiteral("initConnection"));
+ return;
+ }
+
+diff --git a/src/server/storage/dbconfigmysql.cpp b/src/server/storage/dbconfigmysql.cpp
+index 2bd231d..d565706 100644
+--- a/src/server/storage/dbconfigmysql.cpp
++++ b/src/server/storage/dbconfigmysql.cpp
+@@ -492,7 +492,6 @@ bool DbConfigMysql::startInternalServer()
+ }
+ }
+
+- QSqlDatabase::removeDatabase(initCon);
+ return success;
+ }
+
+@@ -520,6 +519,9 @@ void DbConfigMysql::stopInternalServer()
+ return;
+ }
+
++ // closing initConnection this late to work around QTBUG-63108
++ QSqlDatabase::removeDatabase(QStringLiteral("initConnection"));
++
+ disconnect(mDatabaseProcess, static_cast<void(QProcess::*)(int,QProcess::ExitStatus)>(&QProcess::finished),
+ this, &DbConfigMysql::processFinished);
+
+--
+cgit v0.11.2
+
+
diff --git a/kde/patch/akonadi/akonadi_rename-header.patch b/kde/patch/akonadi/akonadi_rename-header.patch
new file mode 100644
index 0000000..73347f0
--- /dev/null
+++ b/kde/patch/akonadi/akonadi_rename-header.patch
@@ -0,0 +1,77 @@
+commit 248671e8200ff0883877b6d0e56700ef99ff3b51
+Author: Andreas Sturmlechner <andreas.sturmlechner@gmail.com>
+Date: Sat Jan 7 14:38:17 2017 +0100
+
+ Rename exception.h to exceptionbase.h
+
+ REVIEW: 129788
+
+diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
+index 6ac851e..fa996b9 100644
+--- a/src/core/CMakeLists.txt
++++ b/src/core/CMakeLists.txt
+@@ -95,7 +95,7 @@ ecm_generate_headers(AkonadiCore_base_HEADERS
+ EntityDeletedAttribute
+ EntityDisplayAttribute
+ EntityHiddenAttribute
+- Exception
++ ExceptionBase
+ GidExtractorInterface
+ IndexPolicyAttribute
+ Item
+diff --git a/src/core/exception.cpp b/src/core/exception.cpp
+index f229c1a..14f7330 100644
+--- a/src/core/exception.cpp
++++ b/src/core/exception.cpp
+@@ -17,7 +17,7 @@
+ 02110-1301, USA.
+ */
+
+-#include "exception.h"
++#include "exceptionbase.h"
+
+ #include <QString>
+
+diff --git a/src/core/exception.h b/src/core/exceptionbase.h
+similarity index 100%
+rename from src/core/exception.h
+rename to src/core/exceptionbase.h
+diff --git a/src/core/item.h b/src/core/item.h
+index de71cad..5ec62c8 100644
+--- a/src/core/item.h
++++ b/src/core/item.h
+@@ -23,7 +23,7 @@
+
+ #include "akonadicore_export.h"
+ #include "attribute.h"
+-#include "exception.h"
++#include "exceptionbase.h"
+ #include "tag.h"
+ #include "collection.h"
+ #include "relation.h"
+diff --git a/src/core/itempayloadinternals_p.h b/src/core/itempayloadinternals_p.h
+index 0a4de3c..1626f10 100644
+--- a/src/core/itempayloadinternals_p.h
++++ b/src/core/itempayloadinternals_p.h
+@@ -32,7 +32,7 @@
+
+ #include <boost/shared_ptr.hpp>
+
+-#include "exception.h"
++#include "exceptionbase.h"
+
+ //@cond PRIVATE Doxygen 1.7.1 hangs processing this file. so skip it.
+ //for more info, see https://bugzilla.gnome.org/show_bug.cgi?id=531637
+diff --git a/src/core/protocolhelper.cpp b/src/core/protocolhelper.cpp
+index f740e9d..c218f0c 100644
+--- a/src/core/protocolhelper.cpp
++++ b/src/core/protocolhelper.cpp
+@@ -23,7 +23,7 @@
+ #include "collectionstatistics.h"
+ #include "item_p.h"
+ #include "collection_p.h"
+-#include "exception.h"
++#include "exceptionbase.h"
+ #include "itemserializer_p.h"
+ #include "itemserializerplugin.h"
+ #include "servermanager.h"
diff --git a/kde/patch/akonadi/akonadi_revert-abs-path.patch b/kde/patch/akonadi/akonadi_revert-abs-path.patch
new file mode 100644
index 0000000..3b48253
--- /dev/null
+++ b/kde/patch/akonadi/akonadi_revert-abs-path.patch
@@ -0,0 +1,70 @@
+commit d98e29a07f4acc3bf01f06f25b3eef5522397e2e
+Author: Andreas Sturmlechner <andreas.sturmlechner@gmail.com>
+Date: Thu Jan 5 22:41:02 2017 +0100
+
+ Revert "Workaround an include loop on case-insensitive systems"
+
+ Do not hardcode absolute patchs to GCC headers.
+
+ This reverts commit 59b9d6b79425c9ec1e5df059a2593580048c4adf.
+
+ REVIEW: 129788
+
+diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
+index 72589cd..6ac851e 100644
+--- a/src/core/CMakeLists.txt
++++ b/src/core/CMakeLists.txt
+@@ -257,21 +257,6 @@ ecm_generate_headers(AkonadiCore_jobs_HEADERS
+ RELATIVE jobs
+ )
+
+-# This is a workaround for conflict between our "Exception" fancy header and
+-# C++ stdlib's "exception" header which occurs in case-insensitive systems.
+-# For that reason we generate std_exception.h file, which contains an absolute
+-# path to the stdlib's exception header file, which resolves the ambiguity
+-# when including <exception> from within Akonadi.
+-include(FindStdlibInclude)
+-findStdlibInclude("exception" std_exception_file)
+-if (NOT "${std_exception_file}" STREQUAL "")
+- configure_file(${CMAKE_CURRENT_SOURCE_DIR}/std_exception.h.in
+- ${CMAKE_CURRENT_BINARY_DIR}/std_exception.h
+- )
+-else()
+- message(FATAL_ERROR "stdlib <exception> include absolute path not found")
+-endif()
+-
+ set(akonadicore_dbus_xml ${Akonadi_SOURCE_DIR}/src/interfaces/org.freedesktop.Akonadi.NotificationManager.xml)
+ qt5_add_dbus_interface(akonadicore_dbus_SRCS ${akonadicore_dbus_xml} notificationmanagerinterface)
+
+@@ -338,7 +323,6 @@ install(TARGETS
+
+ install(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/akonadicore_export.h
+- ${CMAKE_CURRENT_BINARY_DIR}/std_exception.h
+ ${AkonadiCore_base_HEADERS}
+ ${AkonadiCore_models_HEADERS}
+ ${AkonadiCore_jobs_HEADERS}
+diff --git a/src/core/exception.h b/src/core/exception.h
+index d07ca71..2a376df 100644
+--- a/src/core/exception.h
++++ b/src/core/exception.h
+@@ -20,16 +20,11 @@
+ #ifndef AKONADI_EXCEPTION_H
+ #define AKONADI_EXCEPTION_H
+
+-// The std_exception.h file is generated at build-time and #includes C++ stdlib
+-// header "exception" by aboslute path. This is to workaround an include loop on
+-// case-insensitive systems, where #include <exception> includes our "Exception"
+-// fancy header instead of stdlib's exception, causing an endless loop of
+-// includes between "Exception" and "exception.h".
+-#include "std_exception.h"
+-
+ #include "akonadicore_export.h"
++#include <QObject>
++#include <QByteArray>
++#include <exception>
+
+-class QByteArray;
+ class QString;
+
+ namespace Akonadi
diff --git a/kde/patch/ark.patch b/kde/patch/ark.patch
index 5018ca4..35ac2d3 100644
--- a/kde/patch/ark.patch
+++ b/kde/patch/ark.patch
@@ -2,3 +2,6 @@
# KDEBUG #357057 is fixed in 15.12.1; still needs unrar.
#cat $CWD/patch/ark/ark_kdebug357057.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+# Fix a compilation issue in 18.04.0:
+cat $CWD/patch/ark/ark_include_memory.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/ark/ark_include_memory.patch b/kde/patch/ark/ark_include_memory.patch
new file mode 100644
index 0000000..235436e
--- /dev/null
+++ b/kde/patch/ark/ark_include_memory.patch
@@ -0,0 +1,15 @@
+#To overcome compilation issues like:
+# error: 'unique_ptr' is not a member of 'std'
+# error: expected primary-expression before '[' token
+
+--- ark-18.04.0/plugins/libzipplugin/libzipplugin.cpp.orig 2018-04-09 22:42:03.000000000 +0200
++++ ark-18.04.0/plugins/libzipplugin/libzipplugin.cpp 2018-04-15 12:41:56.490025275 +0200
+@@ -39,6 +39,8 @@
+ #include <qplatformdefs.h>
+ #include <QThread>
+
++#include <memory>
++
+ #include <utime.h>
+ #include <zlib.h>
+
diff --git a/kde/patch/cantor.patch b/kde/patch/cantor.patch
new file mode 100644
index 0000000..54f2b47
--- /dev/null
+++ b/kde/patch/cantor.patch
@@ -0,0 +1,4 @@
+# Fix build against KF 5.31.0 (repaired in 16.12.3):
+#cat $CWD/patch/cantor/cantor_python.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+#cat $CWD/patch/cantor/cantor_julia.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/cantor/cantor_julia.patch b/kde/patch/cantor/cantor_julia.patch
new file mode 100644
index 0000000..5c381ca
--- /dev/null
+++ b/kde/patch/cantor/cantor_julia.patch
@@ -0,0 +1,193 @@
+Patch taken from:
+https://gitweb.gentoo.org/repo/gentoo.git/plain/kde-apps/cantor/files/cantor-16.12.2-julia-kf-5.31.patch
+
+From 45322d9f58f50df3d4d5755d4199e579f6fd8646 Mon Sep 17 00:00:00 2001
+From: Andreas Sturmlechner <andreas.sturmlechner@gmail.com>
+Date: Sat, 11 Feb 2017 22:46:35 +0100
+Subject: [PATCH] [julia] Fix build with -fno-operator-names
+
+REVIEW: 129942
+---
+ src/backends/julia/juliaexpression.cpp | 6 +++---
+ src/backends/julia/juliaextensions.cpp | 4 ++--
+ src/backends/julia/juliahighlighter.cpp | 4 ++--
+ src/backends/julia/juliakeywords.cpp | 10 +++++-----
+ src/backends/julia/juliaserver/juliaserver.cpp | 4 ++--
+ src/backends/julia/juliaserver/main.cpp | 4 ++--
+ src/backends/julia/juliasession.cpp | 4 ++--
+ 7 files changed, 18 insertions(+), 18 deletions(-)
+
+diff --git a/src/backends/julia/juliaexpression.cpp b/src/backends/julia/juliaexpression.cpp
+index 27cdd85..618200d 100644
+--- a/src/backends/julia/juliaexpression.cpp
++++ b/src/backends/julia/juliaexpression.cpp
+@@ -40,7 +40,7 @@ void JuliaExpression::evaluate()
+
+ // Plots integration
+ m_plot_filename.clear();
+- if (juliaSession->integratePlots() and checkPlotShowingCommands()) {
++ if (juliaSession->integratePlots() && checkPlotShowingCommands()) {
+ // Simply add plot saving command to the end of execution
+ QStringList inlinePlotFormats;
+ inlinePlotFormats << QLatin1String("svg");
+@@ -73,8 +73,8 @@ void JuliaExpression::finalize()
+ setResult(new Cantor::TextResult(juliaSession->getOutput()));
+ setStatus(Cantor::Expression::Error);
+ } else {
+- if (not m_plot_filename.isEmpty()
+- and QFileInfo(m_plot_filename).exists()) {
++ if (!m_plot_filename.isEmpty()
++ && QFileInfo(m_plot_filename).exists()) {
+ // If we have plot in result, show it
+ setResult(
+ new Cantor::ImageResult(QUrl::fromLocalFile(m_plot_filename)));
+diff --git a/src/backends/julia/juliaextensions.cpp b/src/backends/julia/juliaextensions.cpp
+index 4585c6f..ad5e3a9 100644
+--- a/src/backends/julia/juliaextensions.cpp
++++ b/src/backends/julia/juliaextensions.cpp
+@@ -138,7 +138,7 @@ QString JuliaPlotExtension::plotFunction2d(
+ {
+ auto new_left = left;
+ auto new_right = right;
+- if (new_left.isEmpty() and new_right.isEmpty()) {
++ if (new_left.isEmpty() && new_right.isEmpty()) {
+ new_left = QLatin1String("-1");
+ new_right = QLatin1String("1");
+ } else if (new_left.isEmpty()) {
+@@ -165,7 +165,7 @@ QString JuliaPlotExtension::plotFunction3d(
+ {
+
+ auto update_interval = [](Interval &interval) {
+- if (interval.first.isEmpty() and interval.second.isEmpty()) {
++ if (interval.first.isEmpty() && interval.second.isEmpty()) {
+ interval.first = QLatin1String("-1");
+ interval.second = QLatin1String("1");
+ } else if (interval.first.isEmpty()) {
+diff --git a/src/backends/julia/juliahighlighter.cpp b/src/backends/julia/juliahighlighter.cpp
+index 4795361..f7d3622 100644
+--- a/src/backends/julia/juliahighlighter.cpp
++++ b/src/backends/julia/juliahighlighter.cpp
+@@ -98,7 +98,7 @@ void JuliaHighlighter::highlightBlock(const QString &text)
+ while (pos < text.length()) {
+ // Trying to close current environments
+ bool triggered = false;
+- for (int i = 0; i < flags.size() and not triggered; i++) {
++ for (int i = 0; i < flags.size() && !triggered; i++) {
+ int flag = flags[i];
+ QRegExp &regexp = regexps_ends[i];
+ QTextCharFormat &format = formats[i];
+@@ -144,7 +144,7 @@ void JuliaHighlighter::highlightBlock(const QString &text)
+ singleLineCommentStart.indexIn(text, pos);
+
+ if (singleLineCommentStartPos != -1
+- and singleLineCommentStartPos < minPos) {
++ && singleLineCommentStartPos < minPos) {
+ // single line comment starts earlier
+ setFormat(pos, text.length() - pos, commentFormat());
+ break;
+diff --git a/src/backends/julia/juliakeywords.cpp b/src/backends/julia/juliakeywords.cpp
+index f0a5846..8a0efec 100644
+--- a/src/backends/julia/juliakeywords.cpp
++++ b/src/backends/julia/juliakeywords.cpp
+@@ -62,11 +62,11 @@ void JuliaKeywords::loadFromFile()
+ const QStringRef name = xml.name();
+
+ if (name == QLatin1String("keywords")
+- or name == QLatin1String("variables")
+- or name == QLatin1String("plot_showing_commands")) {
++ || name == QLatin1String("variables")
++ || name == QLatin1String("plot_showing_commands")) {
+ while (xml.readNextStartElement()) {
+ Q_ASSERT(
+- xml.isStartElement() and xml.name() == QLatin1String("word")
++ xml.isStartElement() && xml.name() == QLatin1String("word")
+ );
+
+ const QString text = xml.readElementText();
+@@ -91,7 +91,7 @@ void JuliaKeywords::loadFromFile()
+
+ void JuliaKeywords::addVariable(const QString &variable)
+ {
+- if (not m_variables.contains(variable)) {
++ if (!m_variables.contains(variable)) {
+ m_variables << variable;
+ }
+ }
+@@ -104,7 +104,7 @@ void JuliaKeywords::clearVariables()
+
+ void JuliaKeywords::addFunction(const QString &function)
+ {
+- if (not m_functions.contains(function)) {
++ if (!m_functions.contains(function)) {
+ m_functions << function;
+ }
+ }
+diff --git a/src/backends/julia/juliaserver/juliaserver.cpp b/src/backends/julia/juliaserver/juliaserver.cpp
+index c9beb4c..91585cf 100644
+--- a/src/backends/julia/juliaserver/juliaserver.cpp
++++ b/src/backends/julia/juliaserver/juliaserver.cpp
+@@ -47,7 +47,7 @@ void JuliaServer::runJuliaCommand(const QString &command)
+ {
+ // Redirect stdout, stderr to temprorary files
+ QTemporaryFile output, error;
+- if (not output.open() or not error.open()) {
++ if (!output.open() || !error.open()) {
+ qFatal("Unable to create temprorary files for stdout/stderr");
+ return;
+ }
+@@ -90,7 +90,7 @@ void JuliaServer::runJuliaCommand(const QString &command)
+ bool is_nothing = jl_unbox_bool(
+ static_cast<jl_value_t *>(jl_call2(equality, nothing, val))
+ );
+- if (not is_nothing) {
++ if (!is_nothing) {
+ jl_static_show(JL_STDOUT, val);
+ }
+ m_was_exception = false;
+diff --git a/src/backends/julia/juliaserver/main.cpp b/src/backends/julia/juliaserver/main.cpp
+index ad7e4d9..11687ec 100644
+--- a/src/backends/julia/juliaserver/main.cpp
++++ b/src/backends/julia/juliaserver/main.cpp
+@@ -30,7 +30,7 @@ int main(int argc, char *argv[])
+ {
+ QCoreApplication app(argc, argv);
+
+- if (not QDBusConnection::sessionBus().isConnected()) {
++ if (!QDBusConnection::sessionBus().isConnected()) {
+ qWarning() << "Can't connect to the D-Bus session bus.\n"
+ "To start it, run: eval `dbus-launch --auto-syntax`";
+ return 1;
+@@ -39,7 +39,7 @@ int main(int argc, char *argv[])
+ const QString &serviceName =
+ QString::fromLatin1("org.kde.Cantor.Julia-%1").arg(app.applicationPid());
+
+- if (not QDBusConnection::sessionBus().registerService(serviceName)) {
++ if (!QDBusConnection::sessionBus().registerService(serviceName)) {
+ qWarning() << QDBusConnection::sessionBus().lastError().message();
+ return 2;
+ }
+diff --git a/src/backends/julia/juliasession.cpp b/src/backends/julia/juliasession.cpp
+index 425e6cb..9183e11 100644
+--- a/src/backends/julia/juliasession.cpp
++++ b/src/backends/julia/juliasession.cpp
+@@ -86,7 +86,7 @@ void JuliaSession::login()
+ QDBusConnection::sessionBus()
+ );
+
+- if (not m_interface->isValid()) {
++ if (!m_interface->isValid()) {
+ qWarning() << QDBusConnection::sessionBus().lastError().message();
+ return;
+ }
+@@ -213,7 +213,7 @@ bool JuliaSession::getWasException()
+ {
+ const QDBusReply<bool> &reply =
+ m_interface->call(QLatin1String("getWasException"));
+- return reply.isValid() and reply.value();
++ return reply.isValid() && reply.value();
+ }
+
+ void JuliaSession::listVariables()
+--
+2.10.2
+
diff --git a/kde/patch/cantor/cantor_python.patch b/kde/patch/cantor/cantor_python.patch
new file mode 100644
index 0000000..aa2b398
--- /dev/null
+++ b/kde/patch/cantor/cantor_python.patch
@@ -0,0 +1,31 @@
+Patch taken from:
+https://gitweb.gentoo.org/repo/gentoo.git/plain/kde-apps/cantor/files/cantor-16.12.2-python-kf-5.31.patch
+
+commit 4b8ef6bed62daced90c7826985650c2a813d2996
+Author: Jonathan Riddell <jr@jriddell.org>
+Date: Wed Feb 8 14:56:48 2017 +0000
+
+ remove modern C++ use to fix compile with current KDE policy
+
+diff --git a/src/backends/python/pythonhighlighter.cpp b/src/backends/python/pythonhighlighter.cpp
+index 4064524..87b10dd 100644
+--- a/src/backends/python/pythonhighlighter.cpp
++++ b/src/backends/python/pythonhighlighter.cpp
+@@ -87,7 +87,7 @@ void PythonHighlighter::highlightBlock(const QString &text)
+ while (pos < text.length()) {
+ // Trying to close current environments
+ bool triggered = false;
+- for (int i = 0; i < flags.size() and not triggered; i++) {
++ for (int i = 0; i < flags.size() && !triggered; i++) {
+ int flag = flags[i];
+ QRegExp &regexp = regexps[i];
+ QTextCharFormat &format = formats[i];
+@@ -126,7 +126,7 @@ void PythonHighlighter::highlightBlock(const QString &text)
+ singleLineCommentStart.indexIn(text, pos);
+
+ if (singleLineCommentStartPos != -1
+- and singleLineCommentStartPos < minPos) {
++ && singleLineCommentStartPos < minPos) {
+ setFormat(pos, text.length() - pos, commentFormat());
+ break;
+ } else if (minRegexp) {
diff --git a/kde/patch/digikam.patch b/kde/patch/digikam.patch
new file mode 100644
index 0000000..ec056c9
--- /dev/null
+++ b/kde/patch/digikam.patch
@@ -0,0 +1,9 @@
+# Fix compilation with clang.
+# Fixed in digikam 5.5.0.
+#cat $CWD/patch/digikam/digikam_clang_fix.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Fix linking issue, backport from git master:
+#cd core
+# cat $CWD/patch/digikam/digikam_databasemodel.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+#cd -
+
diff --git a/kde/patch/digikam/digikam_clang_fix.patch b/kde/patch/digikam/digikam_clang_fix.patch
new file mode 100644
index 0000000..a4d77b0
--- /dev/null
+++ b/kde/patch/digikam/digikam_clang_fix.patch
@@ -0,0 +1,38 @@
+Taken from:
+http://pkgs.fedoraproject.org/cgit/rpms/digikam.git/
+And added and extra '/core/' path componenent.
+
+From 86cd0d1d89c8b4d13f06dc8a353bdd99f13c4758 Mon Sep 17 00:00:00 2001
+From: Gilles Caulier <caulier.gilles@gmail.com>
+Date: Wed, 18 Jan 2017 10:13:20 +0100
+Subject: [PATCH 2/2] Fix compilation with clang
+
+---
+ libs/dmetadata/metaengine_p.cpp | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/core/libs/dmetadata/metaengine_p.cpp b/core/libs/dmetadata/metaengine_p.cpp
+index 2c83b58..2b44e06 100644
+--- a/core/libs/dmetadata/metaengine_p.cpp
++++ b/core/libs/dmetadata/metaengine_p.cpp
+@@ -49,7 +49,7 @@ extern "C"
+ #include "digikam_debug.h"
+
+ // Pragma directives to reduce warnings from Exiv2.
+-#if not defined(__APPLE__) && defined(__GNUC__)
++#if !defined(__APPLE__) && defined(__GNUC__)
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ #endif
+@@ -723,7 +723,7 @@ void MetaEngine::Private::loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar)
+ } // namespace Digikam
+
+ // Restore warnings
+-#if not defined(__APPLE__) && defined(__GNUC__)
++#if !defined(__APPLE__) && defined(__GNUC__)
+ #pragma GCC diagnostic pop
+ #endif
+
+--
+2.9.3
+
diff --git a/kde/patch/digikam/digikam_databasemodel.patch b/kde/patch/digikam/digikam_databasemodel.patch
new file mode 100644
index 0000000..8b2fff7
--- /dev/null
+++ b/kde/patch/digikam/digikam_databasemodel.patch
@@ -0,0 +1,13337 @@
+From 7e00441c257e7e9e5dc5ab983fc06046fb72b0c5 Mon Sep 17 00:00:00 2001
+From: Gilles Caulier <caulier.gilles@gmail.com>
+Date: Sat, 22 Jul 2017 15:46:08 +0200
+Subject: fix broken linking stage under MacOS with macports. move database
+ models into libdigikamdatabase. Let's others model in place to be included
+ into libdigikamcore
+
+---
+ libs/database/CMakeLists.txt | 16 +-
+ libs/database/models/imagefiltermodel.cpp | 1116 ++++++++++++++++++
+ libs/database/models/imagefiltermodel.h | 299 +++++
+ libs/database/models/imagefiltermodelpriv.cpp | 258 ++++
+ libs/database/models/imagefiltermodelpriv.h | 159 +++
+ libs/database/models/imagefiltermodelthreads.cpp | 40 +
+ libs/database/models/imagefiltermodelthreads.h | 100 ++
+ libs/database/models/imagefiltersettings.cpp | 952 +++++++++++++++
+ libs/database/models/imagefiltersettings.h | 349 ++++++
+ libs/database/models/imagelistmodel.cpp | 70 ++
+ libs/database/models/imagelistmodel.h | 63 +
+ libs/database/models/imagemodel.cpp | 1368 ++++++++++++++++++++++
+ libs/database/models/imagemodel.h | 364 ++++++
+ libs/database/models/imagesortsettings.cpp | 400 +++++++
+ libs/database/models/imagesortsettings.h | 225 ++++
+ libs/database/models/imagethumbnailmodel.cpp | 323 +++++
+ libs/database/models/imagethumbnailmodel.h | 140 +++
+ libs/database/models/imageversionsmodel.cpp | 183 +++
+ libs/database/models/imageversionsmodel.h | 75 ++
+ libs/models/CMakeLists.txt | 15 +-
+ libs/models/imagefiltermodel.cpp | 1116 ------------------
+ libs/models/imagefiltermodel.h | 299 -----
+ libs/models/imagefiltermodelpriv.cpp | 258 ----
+ libs/models/imagefiltermodelpriv.h | 159 ---
+ libs/models/imagefiltermodelthreads.cpp | 40 -
+ libs/models/imagefiltermodelthreads.h | 100 --
+ libs/models/imagefiltersettings.cpp | 952 ---------------
+ libs/models/imagefiltersettings.h | 349 ------
+ libs/models/imagelistmodel.cpp | 70 --
+ libs/models/imagelistmodel.h | 63 -
+ libs/models/imagemodel.cpp | 1368 ----------------------
+ libs/models/imagemodel.h | 364 ------
+ libs/models/imagesortsettings.cpp | 400 -------
+ libs/models/imagesortsettings.h | 225 ----
+ libs/models/imagethumbnailmodel.cpp | 323 -----
+ libs/models/imagethumbnailmodel.h | 140 ---
+ libs/models/imageversionsmodel.cpp | 183 ---
+ libs/models/imageversionsmodel.h | 75 --
+ 38 files changed, 6499 insertions(+), 6500 deletions(-)
+ create mode 100644 libs/database/models/imagefiltermodel.cpp
+ create mode 100644 libs/database/models/imagefiltermodel.h
+ create mode 100644 libs/database/models/imagefiltermodelpriv.cpp
+ create mode 100644 libs/database/models/imagefiltermodelpriv.h
+ create mode 100644 libs/database/models/imagefiltermodelthreads.cpp
+ create mode 100644 libs/database/models/imagefiltermodelthreads.h
+ create mode 100644 libs/database/models/imagefiltersettings.cpp
+ create mode 100644 libs/database/models/imagefiltersettings.h
+ create mode 100644 libs/database/models/imagelistmodel.cpp
+ create mode 100644 libs/database/models/imagelistmodel.h
+ create mode 100644 libs/database/models/imagemodel.cpp
+ create mode 100644 libs/database/models/imagemodel.h
+ create mode 100644 libs/database/models/imagesortsettings.cpp
+ create mode 100644 libs/database/models/imagesortsettings.h
+ create mode 100644 libs/database/models/imagethumbnailmodel.cpp
+ create mode 100644 libs/database/models/imagethumbnailmodel.h
+ create mode 100644 libs/database/models/imageversionsmodel.cpp
+ create mode 100644 libs/database/models/imageversionsmodel.h
+ delete mode 100644 libs/models/imagefiltermodel.cpp
+ delete mode 100644 libs/models/imagefiltermodel.h
+ delete mode 100644 libs/models/imagefiltermodelpriv.cpp
+ delete mode 100644 libs/models/imagefiltermodelpriv.h
+ delete mode 100644 libs/models/imagefiltermodelthreads.cpp
+ delete mode 100644 libs/models/imagefiltermodelthreads.h
+ delete mode 100644 libs/models/imagefiltersettings.cpp
+ delete mode 100644 libs/models/imagefiltersettings.h
+ delete mode 100644 libs/models/imagelistmodel.cpp
+ delete mode 100644 libs/models/imagelistmodel.h
+ delete mode 100644 libs/models/imagemodel.cpp
+ delete mode 100644 libs/models/imagemodel.h
+ delete mode 100644 libs/models/imagesortsettings.cpp
+ delete mode 100644 libs/models/imagesortsettings.h
+ delete mode 100644 libs/models/imagethumbnailmodel.cpp
+ delete mode 100644 libs/models/imagethumbnailmodel.h
+ delete mode 100644 libs/models/imageversionsmodel.cpp
+ delete mode 100644 libs/models/imageversionsmodel.h
+
+diff --git a/libs/database/CMakeLists.txt b/libs/database/CMakeLists.txt
+index 7d05536..a431a36 100644
+--- a/libs/database/CMakeLists.txt
++++ b/libs/database/CMakeLists.txt
+@@ -13,6 +13,18 @@ endif (POLICY CMP0063)
+ # Boost uses operator names (and, not, ...)
+ string(REPLACE "-fno-operator-names" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+
++set(libdatabasemodels_SRCS
++ models/imagemodel.cpp
++ models/imagefiltermodel.cpp
++ models/imagefiltermodelpriv.cpp
++ models/imagefiltermodelthreads.cpp
++ models/imagefiltersettings.cpp
++ models/imagelistmodel.cpp
++ models/imagesortsettings.cpp
++ models/imagethumbnailmodel.cpp
++ models/imageversionsmodel.cpp
++)
++
+ set(libdatabasecore_SRCS
+ server/databaseserverstarter.cpp
+ server/databaseservererror.cpp
+@@ -152,10 +164,10 @@ if(ENABLE_DBUS)
+ include_directories($<TARGET_PROPERTY:Qt5::DBus,INTERFACE_INCLUDE_DIRECTORIES>)
+ endif()
+
+-add_library(digikamdatabase_src OBJECT ${digikamdatabase_LIB_SRCS})
++add_library(digikamdatabase_src OBJECT ${digikamdatabase_LIB_SRCS} ${libdatabasemodels_SRCS})
+ add_library(digikamdatabasemain_src OBJECT ${libdatabaseutils_SRCS} ${libimgqsort_SRCS})
+ add_library(digikamdatabasecore_src OBJECT ${libdatabasecore_SRCS})
+-add_library(digikamdatabase SHARED $<TARGET_OBJECTS:digikamdatabase_src> $<TARGET_OBJECTS:digikamdatabasemodels_src>)
++add_library(digikamdatabase $<TARGET_OBJECTS:digikamdatabase_src>)
+
+ generate_export_header(digikamdatabase
+ BASE_NAME digikam_database
+diff --git a/libs/database/models/imagefiltermodel.cpp b/libs/database/models/imagefiltermodel.cpp
+new file mode 100644
+index 0000000..3d57e05
+--- /dev/null
++++ b/libs/database/models/imagefiltermodel.cpp
+@@ -0,0 +1,1116 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagefiltermodel.h"
++#include "imagefiltermodelpriv.h"
++#include "imagefiltermodelthreads.h"
++
++// Local includes
++
++#include "digikam_debug.h"
++#include "coredbaccess.h"
++#include "coredbchangesets.h"
++#include "coredbwatch.h"
++#include "imageinfolist.h"
++#include "imagemodel.h"
++
++namespace Digikam
++{
++
++ImageSortFilterModel::ImageSortFilterModel(QObject* parent)
++ : DCategorizedSortFilterProxyModel(parent), m_chainedModel(0)
++{
++}
++
++void ImageSortFilterModel::setSourceImageModel(ImageModel* source)
++{
++ if (m_chainedModel)
++ {
++ m_chainedModel->setSourceImageModel(source);
++ }
++ else
++ {
++ setDirectSourceImageModel(source);
++ }
++}
++
++void ImageSortFilterModel::setSourceFilterModel(ImageSortFilterModel* source)
++{
++ if (source)
++ {
++ ImageModel* const model = sourceImageModel();
++
++ if (model)
++ {
++ source->setSourceImageModel(model);
++ }
++ }
++
++ m_chainedModel = source;
++ setSourceModel(source);
++}
++
++void ImageSortFilterModel::setDirectSourceImageModel(ImageModel* model)
++{
++ setSourceModel(model);
++}
++
++void ImageSortFilterModel::setSourceModel(QAbstractItemModel* model)
++{
++ // made it protected, only setSourceImageModel is public
++ DCategorizedSortFilterProxyModel::setSourceModel(model);
++}
++
++ImageModel* ImageSortFilterModel::sourceImageModel() const
++{
++ if (m_chainedModel)
++ {
++ return m_chainedModel->sourceImageModel();
++ }
++
++ return static_cast<ImageModel*>(sourceModel());
++}
++
++ImageSortFilterModel* ImageSortFilterModel::sourceFilterModel() const
++{
++ return m_chainedModel;
++}
++
++ImageFilterModel* ImageSortFilterModel::imageFilterModel() const
++{
++ // reimplemented in ImageFilterModel
++ if (m_chainedModel)
++ {
++ return m_chainedModel->imageFilterModel();
++ }
++
++ return 0;
++}
++
++QModelIndex ImageSortFilterModel::mapToSourceImageModel(const QModelIndex& index) const
++{
++ if (m_chainedModel)
++ {
++ return m_chainedModel->mapToSourceImageModel(mapToSource(index));
++ }
++
++ return mapToSource(index);
++}
++
++QModelIndex ImageSortFilterModel::mapFromSourceImageModel(const QModelIndex& albummodel_index) const
++{
++ if (m_chainedModel)
++ {
++ return mapFromSource(m_chainedModel->mapFromSourceImageModel(albummodel_index));
++ }
++
++ return mapFromSource(albummodel_index);
++}
++
++
++QModelIndex ImageSortFilterModel::mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const
++{
++ if (m_chainedModel)
++ {
++ return m_chainedModel->mapToSourceImageModel(sourceModel_index);
++ }
++ return sourceModel_index;
++}
++
++// -------------- Convenience mappers -------------------------------------------------------------------
++
++QList<QModelIndex> ImageSortFilterModel::mapListToSource(const QList<QModelIndex>& indexes) const
++{
++ QList<QModelIndex> sourceIndexes;
++ foreach(const QModelIndex& index, indexes)
++ {
++ sourceIndexes << mapToSourceImageModel(index);
++ }
++ return sourceIndexes;
++}
++
++QList<QModelIndex> ImageSortFilterModel::mapListFromSource(const QList<QModelIndex>& sourceIndexes) const
++{
++ QList<QModelIndex> indexes;
++ foreach(const QModelIndex& index, sourceIndexes)
++ {
++ indexes << mapFromSourceImageModel(index);
++ }
++ return indexes;
++}
++
++ImageInfo ImageSortFilterModel::imageInfo(const QModelIndex& index) const
++{
++ return sourceImageModel()->imageInfo(mapToSourceImageModel(index));
++}
++
++qlonglong ImageSortFilterModel::imageId(const QModelIndex& index) const
++{
++ return sourceImageModel()->imageId(mapToSourceImageModel(index));
++}
++
++QList<ImageInfo> ImageSortFilterModel::imageInfos(const QList<QModelIndex>& indexes) const
++{
++ QList<ImageInfo> infos;
++ ImageModel* const model = sourceImageModel();
++
++ foreach(const QModelIndex& index, indexes)
++ {
++ infos << model->imageInfo(mapToSourceImageModel(index));
++ }
++
++ return infos;
++}
++
++QList<qlonglong> ImageSortFilterModel::imageIds(const QList<QModelIndex>& indexes) const
++{
++ QList<qlonglong> ids;
++ ImageModel* const model = sourceImageModel();
++
++ foreach(const QModelIndex& index, indexes)
++ {
++ ids << model->imageId(mapToSourceImageModel(index));
++ }
++
++ return ids;
++}
++
++QModelIndex ImageSortFilterModel::indexForPath(const QString& filePath) const
++{
++ return mapFromSourceImageModel(sourceImageModel()->indexForPath(filePath));
++}
++
++QModelIndex ImageSortFilterModel::indexForImageInfo(const ImageInfo& info) const
++{
++ return mapFromSourceImageModel(sourceImageModel()->indexForImageInfo(info));
++}
++
++QModelIndex ImageSortFilterModel::indexForImageId(qlonglong id) const
++{
++ return mapFromSourceImageModel(sourceImageModel()->indexForImageId(id));
++}
++
++QList<ImageInfo> ImageSortFilterModel::imageInfosSorted() const
++{
++ QList<ImageInfo> infos;
++ const int size = rowCount();
++ ImageModel* const model = sourceImageModel();
++
++ for (int i=0; i<size; ++i)
++ {
++ infos << model->imageInfo(mapToSourceImageModel(index(i, 0)));
++ }
++
++ return infos;
++}
++
++// --------------------------------------------------------------------------------------------
++
++ImageFilterModel::ImageFilterModel(QObject* parent)
++ : ImageSortFilterModel(parent),
++ d_ptr(new ImageFilterModelPrivate)
++{
++ d_ptr->init(this);
++}
++
++ImageFilterModel::ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent)
++ : ImageSortFilterModel(parent),
++ d_ptr(&dd)
++{
++ d_ptr->init(this);
++}
++
++ImageFilterModel::~ImageFilterModel()
++{
++ Q_D(ImageFilterModel);
++ delete d;
++}
++
++void ImageFilterModel::setDirectSourceImageModel(ImageModel* sourceModel)
++{
++ Q_D(ImageFilterModel);
++
++ if (d->imageModel)
++ {
++ d->imageModel->unsetPreprocessor(d);
++ disconnect(d->imageModel, SIGNAL(modelReset()),
++ this, SLOT(slotModelReset()));
++ slotModelReset();
++ }
++
++ d->imageModel = sourceModel;
++
++ if (d->imageModel)
++ {
++ d->imageModel->setPreprocessor(d);
++
++ connect(d->imageModel, SIGNAL(preprocess(QList<ImageInfo>,QList<QVariant>)),
++ d, SLOT(preprocessInfos(QList<ImageInfo>,QList<QVariant>)));
++
++ connect(d->imageModel, SIGNAL(processAdded(QList<ImageInfo>,QList<QVariant>)),
++ d, SLOT(processAddedInfos(QList<ImageInfo>,QList<QVariant>)));
++
++ connect(d, SIGNAL(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)),
++ d->imageModel, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
++
++ connect(d, SIGNAL(reAddingFinished()),
++ d->imageModel, SLOT(reAddingFinished()));
++
++ connect(d->imageModel, SIGNAL(modelReset()),
++ this, SLOT(slotModelReset()));
++
++ connect(d->imageModel, SIGNAL(imageChange(ImageChangeset,QItemSelection)),
++ this, SLOT(slotImageChange(ImageChangeset)));
++
++ connect(d->imageModel, SIGNAL(imageTagChange(ImageTagChangeset,QItemSelection)),
++ this, SLOT(slotImageTagChange(ImageTagChangeset)));
++ }
++
++ setSourceModel(d->imageModel);
++}
++
++QVariant ImageFilterModel::data(const QModelIndex& index, int role) const
++{
++ Q_D(const ImageFilterModel);
++
++ if (!index.isValid())
++ {
++ return QVariant();
++ }
++
++ switch (role)
++ {
++ // Attention: This breaks should there ever be another filter model between this and the ImageModel
++
++ case DCategorizedSortFilterProxyModel::CategoryDisplayRole:
++ return categoryIdentifier(d->imageModel->imageInfoRef(mapToSource(index)));
++ case CategorizationModeRole:
++ return d->sorter.categorizationMode;
++ case SortOrderRole:
++ return d->sorter.sortRole;
++ //case CategoryCountRole:
++ // return categoryCount(d->imageModel->imageInfoRef(mapToSource(index)));
++ case CategoryAlbumIdRole:
++ return d->imageModel->imageInfoRef(mapToSource(index)).albumId();
++ case CategoryFormatRole:
++ return d->imageModel->imageInfoRef(mapToSource(index)).format();
++ case GroupIsOpenRole:
++ return d->groupFilter.isAllOpen() ||
++ d->groupFilter.isOpen(d->imageModel->imageInfoRef(mapToSource(index)).id());
++ case ImageFilterModelPointerRole:
++ return QVariant::fromValue(const_cast<ImageFilterModel*>(this));
++ }
++
++ return DCategorizedSortFilterProxyModel::data(index, role);
++}
++
++ImageFilterModel* ImageFilterModel::imageFilterModel() const
++{
++ return const_cast<ImageFilterModel*>(this);
++}
++
++DatabaseFields::Set ImageFilterModel::suggestedWatchFlags() const
++{
++ DatabaseFields::Set watchFlags;
++ watchFlags |= DatabaseFields::Name | DatabaseFields::FileSize | DatabaseFields::ModificationDate;
++ watchFlags |= DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::Orientation |
++ DatabaseFields::Width | DatabaseFields::Height;
++ watchFlags |= DatabaseFields::Comment;
++ watchFlags |= DatabaseFields::ImageRelations;
++ return watchFlags;
++}
++
++// -------------- Filter settings --------------
++
++void ImageFilterModel::setDayFilter(const QList<QDateTime>& days)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setDayFilter(days);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
++ ImageFilterSettings::MatchingCondition matchingCond,
++ bool showUnTagged, const QList<int>& clTagIds, const QList<int>& plTagIds)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setTagFilter(includedTags, excludedTags, matchingCond, showUnTagged, clTagIds, plTagIds);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setRatingFilter(rating, ratingCond, isUnratedExcluded);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setUrlWhitelist(const QList<QUrl> urlList, const QString& id)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setUrlWhitelist(urlList, id);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setIdWhitelist(const QList<qlonglong>& idList, const QString& id)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setIdWhitelist(idList, id);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setMimeTypeFilter(int mimeTypeFilter)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setMimeTypeFilter(mimeTypeFilter);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setGeolocationFilter(condition);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setTextFilter(const SearchTextFilterSettings& settings)
++{
++ Q_D(ImageFilterModel);
++ d->filter.setTextFilter(settings);
++ setImageFilterSettings(d->filter);
++}
++
++void ImageFilterModel::setImageFilterSettings(const ImageFilterSettings& settings)
++{
++ Q_D(ImageFilterModel);
++
++ {
++ QMutexLocker lock(&d->mutex);
++ d->version++;
++ d->filter = settings;
++ d->filterCopy = settings;
++ d->versionFilterCopy = d->versionFilter;
++ d->groupFilterCopy = d->groupFilter;
++
++ d->needPrepareComments = settings.isFilteringByText();
++ d->needPrepareTags = settings.isFilteringByTags();
++ d->needPrepareGroups = true;
++ d->needPrepare = d->needPrepareComments || d->needPrepareTags || d->needPrepareGroups;
++
++ d->hasOneMatch = false;
++ d->hasOneMatchForText = false;
++ }
++
++ d->filterResults.clear();
++
++ //d->categoryCountHashInt.clear();
++ //d->categoryCountHashString.clear();
++ if (d->imageModel)
++ {
++ d->infosToProcess(d->imageModel->imageInfos());
++ }
++
++ emit filterSettingsChanged(settings);
++}
++
++void ImageFilterModel::setVersionManagerSettings(const VersionManagerSettings& settings)
++{
++ Q_D(ImageFilterModel);
++ d->versionFilter.setVersionManagerSettings(settings);
++ setVersionImageFilterSettings(d->versionFilter);
++}
++
++void ImageFilterModel::setExceptionList(const QList<qlonglong>& idList, const QString& id)
++{
++ Q_D(ImageFilterModel);
++ d->versionFilter.setExceptionList(idList, id);
++ setVersionImageFilterSettings(d->versionFilter);
++}
++
++void ImageFilterModel::setVersionImageFilterSettings(const VersionImageFilterSettings& settings)
++{
++ Q_D(ImageFilterModel);
++ d->versionFilter = settings;
++ slotUpdateFilter();
++}
++
++bool ImageFilterModel::isGroupOpen(qlonglong group) const
++{
++ Q_D(const ImageFilterModel);
++ return d->groupFilter.isOpen(group);
++}
++
++bool ImageFilterModel::isAllGroupsOpen() const
++{
++ Q_D(const ImageFilterModel);
++ return d->groupFilter.isAllOpen();
++}
++
++void ImageFilterModel::setGroupOpen(qlonglong group, bool open)
++{
++ Q_D(ImageFilterModel);
++ d->groupFilter.setOpen(group, open);
++ setGroupImageFilterSettings(d->groupFilter);
++}
++
++void ImageFilterModel::toggleGroupOpen(qlonglong group)
++{
++ setGroupOpen(group, !isGroupOpen(group));
++}
++
++void ImageFilterModel::setAllGroupsOpen(bool open)
++{
++ Q_D(ImageFilterModel);
++ d->groupFilter.setAllOpen(open);
++ setGroupImageFilterSettings(d->groupFilter);
++}
++
++void ImageFilterModel::setGroupImageFilterSettings(const GroupImageFilterSettings& settings)
++{
++ Q_D(ImageFilterModel);
++ d->groupFilter = settings;
++ slotUpdateFilter();
++}
++
++void ImageFilterModel::slotUpdateFilter()
++{
++ Q_D(ImageFilterModel);
++ setImageFilterSettings(d->filter);
++}
++
++ImageFilterSettings ImageFilterModel::imageFilterSettings() const
++{
++ Q_D(const ImageFilterModel);
++ return d->filter;
++}
++
++ImageSortSettings ImageFilterModel::imageSortSettings() const
++{
++ Q_D(const ImageFilterModel);
++ return d->sorter;
++}
++
++VersionImageFilterSettings ImageFilterModel::versionImageFilterSettings() const
++{
++ Q_D(const ImageFilterModel);
++ return d->versionFilter;
++}
++
++GroupImageFilterSettings ImageFilterModel::groupImageFilterSettings() const
++{
++ Q_D(const ImageFilterModel);
++ return d->groupFilter;
++}
++
++void ImageFilterModel::slotModelReset()
++{
++ Q_D(ImageFilterModel);
++ {
++ QMutexLocker lock(&d->mutex);
++ // discard all packages on the way that are marked as send out for re-add
++ d->lastDiscardVersion = d->version;
++ d->sentOutForReAdd = 0;
++ // discard all packages on the way
++ d->version++;
++ d->sentOut = 0;
++
++ d->hasOneMatch = false;
++ d->hasOneMatchForText = false;
++ }
++ d->filterResults.clear();
++}
++
++bool ImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
++{
++ Q_D(const ImageFilterModel);
++
++ if (source_parent.isValid())
++ {
++ return false;
++ }
++
++ qlonglong id = d->imageModel->imageId(source_row);
++ QHash<qlonglong, bool>::const_iterator it = d->filterResults.constFind(id);
++
++ if (it != d->filterResults.constEnd())
++ {
++ return it.value();
++ }
++
++ // usually done in thread and cache, unless source model changed
++ ImageInfo info = d->imageModel->imageInfo(source_row);
++ bool match = d->filter.matches(info);
++ match = match ? d->versionFilter.matches(info) : false;
++
++ return match ? d->groupFilter.matches(info) : false;
++}
++
++void ImageFilterModel::setSendImageInfoSignals(bool sendSignals)
++{
++ if (sendSignals)
++ {
++ connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
++ this, SLOT(slotRowsInserted(QModelIndex,int,int)));
++
++ connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
++ this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
++ }
++ else
++ {
++ disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
++ this, SLOT(slotRowsInserted(QModelIndex,int,int)));
++
++ disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
++ this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
++ }
++}
++
++void ImageFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end)
++{
++ QList<ImageInfo> infos;
++
++ for (int i=start; i<=end; ++i)
++ {
++ infos << imageInfo(index(i, 0));
++ }
++
++ emit imageInfosAdded(infos);
++}
++
++void ImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end)
++{
++ QList<ImageInfo> infos;
++
++ for (int i=start; i<=end; ++i)
++ {
++ infos << imageInfo(index(i, 0));
++ }
++
++ emit imageInfosAboutToBeRemoved(infos);
++}
++
++// -------------- Threaded preparation & filtering --------------
++
++void ImageFilterModel::addPrepareHook(ImageFilterModelPrepareHook* hook)
++{
++ Q_D(ImageFilterModel);
++ QMutexLocker lock(&d->mutex);
++ d->prepareHooks << hook;
++}
++
++void ImageFilterModel::removePrepareHook(ImageFilterModelPrepareHook* hook)
++{
++ Q_D(ImageFilterModel);
++ QMutexLocker lock(&d->mutex);
++ d->prepareHooks.removeAll(hook);
++}
++
++void ImageFilterModelPreparer::process(ImageFilterModelTodoPackage package)
++{
++ if (!checkVersion(package))
++ {
++ emit discarded(package);
++ return;
++ }
++
++ // get thread-local copy
++ bool needPrepareTags, needPrepareComments, needPrepareGroups;
++ QList<ImageFilterModelPrepareHook*> prepareHooks;
++ {
++ QMutexLocker lock(&d->mutex);
++ needPrepareTags = d->needPrepareTags;
++ needPrepareComments = d->needPrepareComments;
++ needPrepareGroups = d->needPrepareGroups;
++ prepareHooks = d->prepareHooks;
++ }
++
++ //TODO: Make efficient!!
++ if (needPrepareComments)
++ {
++ foreach(const ImageInfo& info, package.infos)
++ {
++ info.comment();
++ }
++ }
++
++ if (!checkVersion(package))
++ {
++ emit discarded(package);
++ return;
++ }
++
++ // The downside of QVector: At some point, we may need a QList for an API.
++ // Nonetheless, QList and ImageInfo is fast. We could as well
++ // reimplement ImageInfoList to ImageInfoVector (internally with templates?)
++ ImageInfoList infoList;
++
++ if (needPrepareTags || needPrepareGroups)
++ {
++ infoList = package.infos.toList();
++ }
++
++ if (needPrepareTags)
++ {
++ infoList.loadTagIds();
++ }
++
++ if (needPrepareGroups)
++ {
++ infoList.loadGroupImageIds();
++ }
++
++ foreach(ImageFilterModelPrepareHook* hook, prepareHooks)
++ {
++ hook->prepare(package.infos);
++ }
++
++ emit processed(package);
++}
++
++void ImageFilterModelFilterer::process(ImageFilterModelTodoPackage package)
++{
++ if (!checkVersion(package))
++ {
++ emit discarded(package);
++ return;
++ }
++
++ // get thread-local copy
++ ImageFilterSettings localFilter;
++ VersionImageFilterSettings localVersionFilter;
++ GroupImageFilterSettings localGroupFilter;
++ bool hasOneMatch;
++ bool hasOneMatchForText;
++ {
++ QMutexLocker lock(&d->mutex);
++ localFilter = d->filterCopy;
++ localVersionFilter = d->versionFilterCopy;
++ localGroupFilter = d->groupFilterCopy;
++ hasOneMatch = d->hasOneMatch;
++ hasOneMatchForText = d->hasOneMatchForText;
++ }
++
++ // Actual filtering. The variants to spare checking hasOneMatch over and over again.
++ if (hasOneMatch && hasOneMatchForText)
++ {
++ foreach(const ImageInfo& info, package.infos)
++ {
++ package.filterResults[info.id()] = localFilter.matches(info) &&
++ localVersionFilter.matches(info) &&
++ localGroupFilter.matches(info);
++ }
++ }
++ else if (hasOneMatch)
++ {
++ bool matchForText;
++
++ foreach(const ImageInfo& info, package.infos)
++ {
++ package.filterResults[info.id()] = localFilter.matches(info, &matchForText) &&
++ localVersionFilter.matches(info) &&
++ localGroupFilter.matches(info);
++
++ if (matchForText)
++ {
++ hasOneMatchForText = true;
++ }
++ }
++ }
++ else
++ {
++ bool result, matchForText;
++
++ foreach(const ImageInfo& info, package.infos)
++ {
++ result = localFilter.matches(info, &matchForText) &&
++ localVersionFilter.matches(info) &&
++ localGroupFilter.matches(info);
++ package.filterResults[info.id()] = result;
++
++ if (result)
++ {
++ hasOneMatch = true;
++ }
++
++ if (matchForText)
++ {
++ hasOneMatchForText = true;
++ }
++ }
++ }
++
++ if (checkVersion(package))
++ {
++ QMutexLocker lock(&d->mutex);
++ d->hasOneMatch = hasOneMatch;
++ d->hasOneMatchForText = hasOneMatchForText;
++ }
++
++ emit processed(package);
++}
++
++// -------------- Sorting and Categorization -------------------------------------------------------
++
++void ImageFilterModel::setImageSortSettings(const ImageSortSettings& sorter)
++{
++ Q_D(ImageFilterModel);
++ d->sorter = sorter;
++ setCategorizedModel(d->sorter.categorizationMode != ImageSortSettings::NoCategories);
++ invalidate();
++}
++
++void ImageFilterModel::setCategorizationMode(ImageSortSettings::CategorizationMode mode)
++{
++ Q_D(ImageFilterModel);
++ d->sorter.setCategorizationMode(mode);
++ setImageSortSettings(d->sorter);
++}
++
++void ImageFilterModel::setCategorizationSortOrder(ImageSortSettings::SortOrder order)
++{
++ Q_D(ImageFilterModel);
++ d->sorter.setCategorizationSortOrder(order);
++ setImageSortSettings(d->sorter);
++}
++
++void ImageFilterModel::setSortRole(ImageSortSettings::SortRole role)
++{
++ Q_D(ImageFilterModel);
++ d->sorter.setSortRole(role);
++ setImageSortSettings(d->sorter);
++}
++
++void ImageFilterModel::setSortOrder(ImageSortSettings::SortOrder order)
++{
++ Q_D(ImageFilterModel);
++ d->sorter.setSortOrder(order);
++ setImageSortSettings(d->sorter);
++}
++
++void ImageFilterModel::setStringTypeNatural(bool natural)
++{
++ Q_D(ImageFilterModel);
++ d->sorter.setStringTypeNatural(natural);
++ setImageSortSettings(d->sorter);
++}
++
++int ImageFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const
++{
++ // source indexes
++ Q_D(const ImageFilterModel);
++
++ if (!d->sorter.isCategorized())
++ {
++ return 0;
++ }
++
++ if (!left.isValid() || !right.isValid())
++ {
++ return -1;
++ }
++
++ const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left);
++ const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right);
++
++ // Check grouping
++ qlonglong leftGroupImageId = leftInfo.groupImageId();
++ qlonglong rightGroupImageId = rightInfo.groupImageId();
++
++ return compareInfosCategories(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId),
++ rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId));
++}
++
++bool ImageFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const
++{
++ // source indexes
++ Q_D(const ImageFilterModel);
++
++ if (!left.isValid() || !right.isValid())
++ {
++ return true;
++ }
++
++ if (left == right)
++ {
++ return false;
++ }
++
++ const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left);
++ const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right);
++
++ if (leftInfo == rightInfo)
++ {
++ return d->sorter.lessThan(left.data(ImageModel::ExtraDataRole), right.data(ImageModel::ExtraDataRole));
++ }
++
++ // Check grouping
++ qlonglong leftGroupImageId = leftInfo.groupImageId();
++ qlonglong rightGroupImageId = rightInfo.groupImageId();
++
++ // Either no grouping (-1), or same group image, or same image
++ if (leftGroupImageId == rightGroupImageId)
++ {
++ return infosLessThan(leftInfo, rightInfo);
++ }
++
++ // We have grouping to handle
++
++ // Is one grouped on the other? Sort behind leader.
++ if (leftGroupImageId == rightInfo.id())
++ {
++ return false;
++ }
++ if (rightGroupImageId == leftInfo.id())
++ {
++ return true;
++ }
++
++ // Use the group leader for sorting
++ return infosLessThan(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId),
++ rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId));
++}
++
++int ImageFilterModel::compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const
++{
++ // Note: reimplemented in ImageAlbumFilterModel
++ Q_D(const ImageFilterModel);
++ return d->sorter.compareCategories(left, right);
++}
++
++// Feel free to optimize. QString::number is 3x slower.
++static inline QString fastNumberToString(int id)
++{
++ const int size = sizeof(int) * 2;
++ char c[size+1];
++ c[size] = '\0';
++ char* p = c;
++ int number = id;
++
++ for (int i=0; i<size; ++i)
++ {
++ *p = 'a' + (number & 0xF);
++ number >>= 4;
++ ++p;
++ }
++
++ return QString::fromLatin1(c);
++}
++
++QString ImageFilterModel::categoryIdentifier(const ImageInfo& i) const
++{
++ Q_D(const ImageFilterModel);
++
++ if (!d->sorter.isCategorized())
++ {
++ return QString();
++ }
++
++ qlonglong groupedImageId = i.groupImageId();
++ ImageInfo info = groupedImageId == -1 ? i : ImageInfo(groupedImageId);
++
++ switch (d->sorter.categorizationMode)
++ {
++ case ImageSortSettings::NoCategories:
++ return QString();
++ case ImageSortSettings::OneCategory:
++ return QString();
++ case ImageSortSettings::CategoryByAlbum:
++ return fastNumberToString(info.albumId());
++ case ImageSortSettings::CategoryByFormat:
++ return info.format();
++ default:
++ return QString();
++ }
++}
++
++bool ImageFilterModel::infosLessThan(const ImageInfo& left, const ImageInfo& right) const
++{
++ Q_D(const ImageFilterModel);
++ return d->sorter.lessThan(left, right);
++}
++
++// -------------- Watching changes -----------------------------------------------------------------
++
++void ImageFilterModel::slotImageTagChange(const ImageTagChangeset& changeset)
++{
++ Q_D(ImageFilterModel);
++
++ if (!d->imageModel || d->imageModel->isEmpty())
++ {
++ return;
++ }
++
++ // already scheduled to re-filter?
++ if (d->updateFilterTimer->isActive())
++ {
++ return;
++ }
++
++ // do we filter at all?
++ if (!d->versionFilter.isFilteringByTags() &&
++ !d->filter.isFilteringByTags() &&
++ !d->filter.isFilteringByText())
++ {
++ return;
++ }
++
++ // is one of our images affected?
++ foreach(const qlonglong& id, changeset.ids())
++ {
++ // if one matching image id is found, trigger a refresh
++ if (d->imageModel->hasImage(id))
++ {
++ d->updateFilterTimer->start();
++ return;
++ }
++ }
++}
++
++void ImageFilterModel::slotImageChange(const ImageChangeset& changeset)
++{
++ Q_D(ImageFilterModel);
++
++ if (!d->imageModel || d->imageModel->isEmpty())
++ {
++ return;
++ }
++
++ // already scheduled to re-filter?
++ if (d->updateFilterTimer->isActive())
++ {
++ return;
++ }
++
++ // is one of the values affected that we filter or sort by?
++ DatabaseFields::Set set = changeset.changes();
++ bool sortAffected = (set & d->sorter.watchFlags());
++ bool filterAffected = (set & d->filter.watchFlags()) || (set & d->groupFilter.watchFlags());
++
++ if (!sortAffected && !filterAffected)
++ {
++ return;
++ }
++
++ // is one of our images affected?
++ bool imageAffected = false;
++
++ foreach(const qlonglong& id, changeset.ids())
++ {
++ // if one matching image id is found, trigger a refresh
++ if (d->imageModel->hasImage(id))
++ {
++ imageAffected = true;
++ break;
++ }
++ }
++
++ if (!imageAffected)
++ {
++ return;
++ }
++
++ if (filterAffected)
++ {
++ d->updateFilterTimer->start();
++ }
++ else
++ {
++ invalidate(); // just resort, reuse filter results
++ }
++}
++
++// -------------------------------------------------------------------------------------------------------
++
++NoDuplicatesImageFilterModel::NoDuplicatesImageFilterModel(QObject* parent)
++ : ImageSortFilterModel(parent)
++{
++}
++
++bool NoDuplicatesImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
++{
++ QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
++
++ if (index.data(ImageModel::ExtraDataDuplicateCount).toInt() <= 1)
++ {
++ return true;
++ }
++
++ QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent);
++
++ if (!previousIndex.isValid())
++ {
++ return true;
++ }
++
++ if (sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(index)) == sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(previousIndex)))
++ {
++ return false;
++ }
++ return true;
++}
++
++/*
++void NoDuplicatesImageFilterModel::setSourceModel(QAbstractItemModel* model)
++{
++ if (sourceModel())
++ {
++ }
++
++ ImageSortFilterModel::setSourceModel(model);
++
++ if (sourceModel())
++ {
++ connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
++ this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
++ }
++}
++
++void NoDuplicatesImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end)
++{
++ bool needInvalidate = false;
++
++ for (int i = begin; i<=end; ++i)
++ {
++ QModelIndex index = sourceModel()->index(i, 0, parent);
++
++ // filtered out by us?
++ if (!mapFromSource(index).isValid())
++ {
++ continue;
++ }
++
++ QModelIndex sourceIndex = mapFromDirectSourceToSourceImageModel(index);
++ qlonglong id = sourceImageModel()->imageId(sourceIndex);
++
++ if (sourceImageModel()->numberOfIndexesForImageId(id) > 1)
++ {
++ needInvalidate = true;
++ }
++ }
++}*/
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagefiltermodel.h b/libs/database/models/imagefiltermodel.h
+new file mode 100644
+index 0000000..d131b3e
+--- /dev/null
++++ b/libs/database/models/imagefiltermodel.h
+@@ -0,0 +1,299 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGEFILTERMODEL_H
++#define IMAGEFILTERMODEL_H
++
++// Local includes
++
++#include "dcategorizedsortfilterproxymodel.h"
++#include "textfilter.h"
++#include "imagefiltersettings.h"
++#include "imagemodel.h"
++#include "imagesortsettings.h"
++#include "digikam_export.h"
++
++namespace Digikam
++{
++
++class ImageChangeset;
++class ImageFilterModel;
++class ImageTagChangeset;
++
++class DIGIKAM_DATABASE_EXPORT ImageFilterModelPrepareHook
++{
++public:
++
++ virtual ~ImageFilterModelPrepareHook() {};
++ virtual void prepare(const QVector<ImageInfo>& infos) = 0;
++};
++
++// -----------------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT ImageSortFilterModel : public DCategorizedSortFilterProxyModel
++{
++ Q_OBJECT
++
++public:
++
++ explicit ImageSortFilterModel(QObject* parent = 0);
++
++ void setSourceImageModel(ImageModel* model);
++ ImageModel* sourceImageModel() const;
++
++ void setSourceFilterModel(ImageSortFilterModel* model);
++ ImageSortFilterModel* sourceFilterModel() const;
++
++ QModelIndex mapToSourceImageModel(const QModelIndex& index) const;
++ QModelIndex mapFromSourceImageModel(const QModelIndex& imagemodel_index) const;
++ QModelIndex mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const;
++
++ /// Convenience methods mapped to ImageModel.
++ /// Mentioned indexes returned come from the source image model.
++ QList<QModelIndex> mapListToSource(const QList<QModelIndex>& indexes) const;
++ QList<QModelIndex> mapListFromSource(const QList<QModelIndex>& sourceIndexes) const;
++
++ ImageInfo imageInfo(const QModelIndex& index) const;
++ qlonglong imageId(const QModelIndex& index) const;
++ QList<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
++ QList<qlonglong> imageIds(const QList<QModelIndex>& indexes) const;
++
++ QModelIndex indexForPath(const QString& filePath) const;
++ QModelIndex indexForImageInfo(const ImageInfo& info) const;
++ QModelIndex indexForImageId(qlonglong id) const;
++
++ /** Returns a list of all image infos, sorted according to this model.
++ * If you do not need a sorted list, use ImageModel's imageInfos() method.
++ */
++ QList<ImageInfo> imageInfosSorted() const;
++
++ /// Returns this, any chained ImageFilterModel, or 0.
++ virtual ImageFilterModel* imageFilterModel() const;
++
++protected:
++
++ /// Reimplement if needed. Called only when model shall be set as (direct) sourceModel.
++ virtual void setDirectSourceImageModel(ImageModel* model);
++
++ // made protected
++ virtual void setSourceModel(QAbstractItemModel* model);
++
++protected:
++
++ ImageSortFilterModel* m_chainedModel;
++};
++
++// -----------------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT ImageFilterModel : public ImageSortFilterModel
++{
++ Q_OBJECT
++
++public:
++
++ enum ImageFilterModelRoles
++ {
++ /// Returns the current categorization mode
++ CategorizationModeRole = ImageModel::FilterModelRoles + 1,
++ /// Returns the current sort order
++ SortOrderRole = ImageModel::FilterModelRoles + 2,
++ // / Returns the number of items in the index' category
++ //CategoryCountRole = ImageModel::FilterModelRoles + 3,
++ /// Returns the id of the PAlbum of the index which is used for category
++ CategoryAlbumIdRole = ImageModel::FilterModelRoles + 3,
++ /// Returns the format of the index which is used for category
++ CategoryFormatRole = ImageModel::FilterModelRoles + 4,
++ /// Returns true if the given image is a group leader, and the group is opened
++ GroupIsOpenRole = ImageModel::FilterModelRoles + 5,
++ ImageFilterModelPointerRole = ImageModel::FilterModelRoles + 50
++ };
++
++public:
++
++ explicit ImageFilterModel(QObject* parent = 0);
++ ~ImageFilterModel();
++
++ /** Add a hook to get added images for preparation tasks before they are added in the model */
++ void addPrepareHook(ImageFilterModelPrepareHook* hook);
++ void removePrepareHook(ImageFilterModelPrepareHook* hook);
++
++ /** Returns a set of DatabaseFields suggested to set as watch flags on the source ImageModel.
++ * The contained flags will be those that this model can sort or filter by. */
++ DatabaseFields::Set suggestedWatchFlags() const;
++
++ ImageFilterSettings imageFilterSettings() const;
++ VersionImageFilterSettings versionImageFilterSettings() const;
++ GroupImageFilterSettings groupImageFilterSettings() const;
++ ImageSortSettings imageSortSettings() const;
++
++ // group is identified by the id of its group leader
++ bool isGroupOpen(qlonglong group) const;
++ bool isAllGroupsOpen() const;
++
++ /// Enables sending imageInfosAdded and imageInfosAboutToBeRemoved
++ void setSendImageInfoSignals(bool sendSignals);
++
++ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
++ virtual ImageFilterModel* imageFilterModel() const;
++
++public Q_SLOTS:
++
++ /** Changes the current version image filter settings and refilters. */
++ void setVersionImageFilterSettings(const VersionImageFilterSettings& settings);
++
++ /** Changes the current version image filter settings and refilters. */
++ void setGroupImageFilterSettings(const GroupImageFilterSettings& settings);
++
++ /** Adjust the current ImageFilterSettings.
++ * Equivalent to retrieving the current filter settings, adjusting the parameter
++ * and calling setImageFilterSettings.
++ * Provided for convenience.
++ * It is encouraged to use setImageFilterSettings if you change more than one
++ * parameter at a time.
++ */
++ void setDayFilter(const QList<QDateTime>& days);
++ void setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
++ ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged,
++ const QList<int>& clTagIds, const QList<int>& plTagIds);
++ void setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded);
++ void setMimeTypeFilter(int mimeTypeFilter);
++ void setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition);
++ void setTextFilter(const SearchTextFilterSettings& settings);
++
++ void setCategorizationMode(ImageSortSettings::CategorizationMode mode);
++ void setCategorizationSortOrder(ImageSortSettings::SortOrder order);
++ void setSortRole(ImageSortSettings::SortRole role);
++ void setSortOrder(ImageSortSettings::SortOrder order);
++ void setStringTypeNatural(bool natural);
++ void setUrlWhitelist(const QList<QUrl> urlList, const QString& id);
++ void setIdWhitelist(const QList<qlonglong>& idList, const QString& id);
++
++ void setVersionManagerSettings(const VersionManagerSettings& settings);
++ void setExceptionList(const QList<qlonglong>& idlist, const QString& id);
++
++ void setGroupOpen(qlonglong group, bool open);
++ void toggleGroupOpen(qlonglong group);
++ void setAllGroupsOpen(bool open);
++
++ /** Changes the current image filter settings and refilters. */
++ virtual void setImageFilterSettings(const ImageFilterSettings& settings);
++
++ /** Changes the current image sort settings and resorts. */
++ virtual void setImageSortSettings(const ImageSortSettings& settings);
++
++Q_SIGNALS:
++
++ /// Signals that the set filter matches at least one index
++ void filterMatches(bool matches);
++
++ /** Signals that the set text filter matches at least one entry.
++ If no text filter is set, this signal is emitted
++ with 'false' when filterMatches() is emitted.
++ */
++ void filterMatchesForText(bool matchesByText);
++
++ /** Emitted when the filter settings have been changed
++ (the model may not yet have been updated)
++ */
++ void filterSettingsChanged(const ImageFilterSettings& settings);
++
++ /** These signals need to be explicitly enabled with setSendImageInfoSignals()
++ */
++ void imageInfosAdded(const QList<ImageInfo>& infos);
++ void imageInfosAboutToBeRemoved(const QList<ImageInfo>& infos);
++
++public:
++
++ // Declared as public because of use in sub-classes.
++ class ImageFilterModelPrivate;
++
++protected:
++
++ ImageFilterModelPrivate* const d_ptr;
++
++protected:
++
++ ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent);
++
++ virtual void setDirectSourceImageModel(ImageModel* model);
++
++ virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
++
++ virtual int compareCategories(const QModelIndex& left, const QModelIndex& right) const;
++ virtual bool subSortLessThan(const QModelIndex& left, const QModelIndex& right) const;
++ //virtual int categoryCount(const ImageInfo& info) const;
++
++ /** Reimplement to customize category sorting,
++ * Return negative if category of left < category right,
++ * Return 0 if left and right are in the same category, else return positive.
++ */
++ virtual int compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const;
++
++ /** Reimplement to customize sorting. Do not take categories into account here.
++ */
++ virtual bool infosLessThan(const ImageInfo& left, const ImageInfo& right) const;
++
++ /** Returns a unique identifier for the category if info. The string need not be for user display.
++ */
++ virtual QString categoryIdentifier(const ImageInfo& info) const;
++
++protected Q_SLOTS:
++
++ void slotModelReset();
++ void slotUpdateFilter();
++
++ void slotImageTagChange(const ImageTagChangeset& changeset);
++ void slotImageChange(const ImageChangeset& changeset);
++
++ void slotRowsInserted(const QModelIndex& parent, int start, int end);
++ void slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end);
++
++private:
++
++ Q_DECLARE_PRIVATE(ImageFilterModel)
++};
++
++// -----------------------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT NoDuplicatesImageFilterModel : public ImageSortFilterModel
++{
++ Q_OBJECT
++
++public:
++
++ explicit NoDuplicatesImageFilterModel(QObject* parent = 0);
++
++protected:
++
++ virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
++};
++
++} // namespace Digikam
++
++Q_DECLARE_METATYPE(Digikam::ImageFilterModel*)
++
++#endif // IMAGEMODEL_H
+diff --git a/libs/database/models/imagefiltermodelpriv.cpp b/libs/database/models/imagefiltermodelpriv.cpp
+new file mode 100644
+index 0000000..07d9e79
+--- /dev/null
++++ b/libs/database/models/imagefiltermodelpriv.cpp
+@@ -0,0 +1,258 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagefiltermodelpriv.h"
++
++// Local includes
++
++#include "digikam_debug.h"
++#include "imagefiltermodelthreads.h"
++
++namespace Digikam
++{
++
++ImageFilterModel::ImageFilterModelPrivate::ImageFilterModelPrivate()
++{
++ imageModel = 0;
++ version = 0;
++ lastDiscardVersion = 0;
++ sentOut = 0;
++ sentOutForReAdd = 0;
++ updateFilterTimer = 0;
++ needPrepare = false;
++ needPrepareComments = false;
++ needPrepareTags = false;
++ needPrepareGroups = false;
++ preparer = 0;
++ filterer = 0;
++ hasOneMatch = false;
++ hasOneMatchForText = false;
++
++ setupWorkers();
++}
++
++ImageFilterModel::ImageFilterModelPrivate::~ImageFilterModelPrivate()
++{
++ // facilitate thread stopping
++ ++version;
++ preparer->deactivate();
++ filterer->deactivate();
++ delete preparer;
++ delete filterer;
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::init(ImageFilterModel* _q)
++{
++ q = _q;
++
++ updateFilterTimer = new QTimer(this);
++ updateFilterTimer->setSingleShot(true);
++ updateFilterTimer->setInterval(250);
++
++ connect(updateFilterTimer, SIGNAL(timeout()),
++ q, SLOT(slotUpdateFilter()));
++
++ // inter-thread redirection
++ qRegisterMetaType<ImageFilterModelTodoPackage>("ImageFilterModelTodoPackage");
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ infosToProcess(infos, extraValues, true);
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ // These have already been added, we just process them afterwards
++ infosToProcess(infos, extraValues, false);
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::setupWorkers()
++{
++ preparer = new ImageFilterModelPreparer(this);
++ filterer = new ImageFilterModelFilterer(this);
++
++ // A package in constructed in infosToProcess.
++ // Normal flow is infosToProcess -> preparer::process -> filterer::process -> packageFinished.
++ // If no preparation is needed, the first step is skipped.
++ // If filter version changes, both will discard old package and send them to packageDiscarded.
++
++ connect(this, SIGNAL(packageToPrepare(ImageFilterModelTodoPackage)),
++ preparer, SLOT(process(ImageFilterModelTodoPackage)));
++
++ connect(this, SIGNAL(packageToFilter(ImageFilterModelTodoPackage)),
++ filterer, SLOT(process(ImageFilterModelTodoPackage)));
++
++ connect(preparer, SIGNAL(processed(ImageFilterModelTodoPackage)),
++ filterer, SLOT(process(ImageFilterModelTodoPackage)));
++
++ connect(filterer, SIGNAL(processed(ImageFilterModelTodoPackage)),
++ this, SLOT(packageFinished(ImageFilterModelTodoPackage)));
++
++ connect(preparer, SIGNAL(discarded(ImageFilterModelTodoPackage)),
++ this, SLOT(packageDiscarded(ImageFilterModelTodoPackage)));
++
++ connect(filterer, SIGNAL(discarded(ImageFilterModelTodoPackage)),
++ this, SLOT(packageDiscarded(ImageFilterModelTodoPackage)));
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList<ImageInfo>& infos)
++{
++ infosToProcess(infos, QList<QVariant>(), false);
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues, bool forReAdd)
++{
++ if (infos.isEmpty())
++ {
++ return;
++ }
++
++ filterer->schedule();
++
++ if (needPrepare)
++ {
++ preparer->schedule();
++ }
++
++ Q_ASSERT(extraValues.isEmpty() || infos.size() == extraValues.size());
++
++ // prepare and filter in chunks
++ const int size = infos.size();
++ const int maxChunkSize = needPrepare ? PrepareChunkSize : FilterChunkSize;
++ const bool hasExtraValues = !extraValues.isEmpty();
++ QList<ImageInfo>::const_iterator it = infos.constBegin(), end;
++ QList<QVariant>::const_iterator xit = extraValues.constBegin(), xend;
++ int index = 0;
++ QVector<ImageInfo> infoVector;
++ QVector<QVariant> extraValueVector;
++
++ while (it != infos.constEnd())
++ {
++ const int chunkSize = qMin(maxChunkSize, size - index);
++ infoVector.resize(chunkSize);
++ end = it + chunkSize;
++ qCopy(it, end, infoVector.begin());
++
++ if (hasExtraValues)
++ {
++ extraValueVector.resize(chunkSize);
++ xend = xit + chunkSize;
++ qCopy(xit, xend, extraValueVector.begin());
++ xit = xend;
++ }
++
++ it = end;
++ index += chunkSize;
++
++ ++sentOut;
++
++ if (forReAdd)
++ {
++ ++sentOutForReAdd;
++ }
++
++ if (needPrepare)
++ {
++ emit packageToPrepare(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd));
++ }
++ else
++ {
++ emit packageToFilter(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd));
++ }
++ }
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::packageFinished(const ImageFilterModelTodoPackage& package)
++{
++ // check if it got discarded on the journey
++ if (package.version != version)
++ {
++ packageDiscarded(package);
++ return;
++ }
++
++ // incorporate result
++ QHash<qlonglong, bool>::const_iterator it = package.filterResults.constBegin();
++
++ for (; it != package.filterResults.constEnd(); ++it)
++ {
++ filterResults.insert(it.key(), it.value());
++ }
++
++ // re-add if necessary
++ if (package.isForReAdd)
++ {
++ emit reAddImageInfos(package.infos.toList(), package.extraValues.toList());
++
++ if (sentOutForReAdd == 1) // last package
++ {
++ emit reAddingFinished();
++ }
++ }
++
++ // decrement counters
++ --sentOut;
++
++ if (package.isForReAdd)
++ {
++ --sentOutForReAdd;
++ }
++
++ // If all packages have returned, filtered and readded, and no more are expected,
++ // and there is need to tell the filter result to the view, do that
++ if (sentOut == 0 && sentOutForReAdd == 0 && !imageModel->isRefreshing())
++ {
++ q->invalidate(); // use invalidate, not invalidateFilter only. Sorting may have changed as well.
++ emit (q->filterMatches(hasOneMatch));
++ emit (q->filterMatchesForText(hasOneMatchForText));
++ filterer->deactivate();
++ preparer->deactivate();
++ }
++}
++
++void ImageFilterModel::ImageFilterModelPrivate::packageDiscarded(const ImageFilterModelTodoPackage& package)
++{
++ // Either, the model was reset, or the filter changed
++ // In the former case throw all away, in the latter case, recycle
++ if (package.version > lastDiscardVersion)
++ {
++ // Recycle packages: Send again with current version
++ // Do not increment sentOut or sentOutForReAdd here: it was not decremented!
++
++ if (needPrepare)
++ {
++ emit packageToPrepare(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd));
++ }
++ else
++ {
++ emit packageToFilter(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd));
++ }
++ }
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagefiltermodelpriv.h b/libs/database/models/imagefiltermodelpriv.h
+new file mode 100644
+index 0000000..a9e3f22
+--- /dev/null
++++ b/libs/database/models/imagefiltermodelpriv.h
+@@ -0,0 +1,159 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-11
++ * Description : Qt item model for database entries - private shared header
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGEFILTERMODELPRIV_H
++#define IMAGEFILTERMODELPRIV_H
++
++// Qt includes
++
++#include <QHash>
++#include <QMutex>
++#include <QMutexLocker>
++#include <QSet>
++#include <QThread>
++#include <QTimer>
++#include <QWaitCondition>
++
++// Local includes
++
++#include "imageinfo.h"
++#include "imagefiltermodel.h"
++
++#include "digikam_export.h"
++// Yes, we need the EXPORT macro in a private header because
++// this private header is shared across binary objects.
++// This does NOT make this classes here any more public!
++
++namespace Digikam
++{
++
++const int PrepareChunkSize = 101;
++const int FilterChunkSize = 2001;
++
++class ImageFilterModelTodoPackage
++{
++public:
++
++ ImageFilterModelTodoPackage()
++ : version(0), isForReAdd(false)
++ {
++ }
++
++ ImageFilterModelTodoPackage(const QVector<ImageInfo>& infos, const QVector<QVariant>& extraValues, int version, bool isForReAdd)
++ : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd)
++ {
++ }
++
++ QVector<ImageInfo> infos;
++ QVector<QVariant> extraValues;
++ unsigned int version;
++ bool isForReAdd;
++ QHash<qlonglong, bool> filterResults;
++};
++
++// ------------------------------------------------------------------------------------------------
++
++class ImageFilterModelPreparer;
++class ImageFilterModelFilterer;
++
++class DIGIKAM_DATABASE_EXPORT ImageFilterModel::ImageFilterModelPrivate : public QObject
++{
++ Q_OBJECT
++
++public:
++
++ ImageFilterModelPrivate();
++ ~ImageFilterModelPrivate();
++
++ void init(ImageFilterModel* q);
++ void setupWorkers();
++ void infosToProcess(const QList<ImageInfo>& infos);
++ void infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues, bool forReAdd = true);
++
++public:
++
++ ImageFilterModel* q;
++
++ ImageModel* imageModel;
++
++ ImageFilterSettings filter;
++ ImageSortSettings sorter;
++ VersionImageFilterSettings versionFilter;
++ GroupImageFilterSettings groupFilter;
++
++ volatile unsigned int version;
++ unsigned int lastDiscardVersion;
++ unsigned int lastFilteredVersion;
++ int sentOut;
++ int sentOutForReAdd;
++
++ QTimer* updateFilterTimer;
++
++ bool needPrepare;
++ bool needPrepareComments;
++ bool needPrepareTags;
++ bool needPrepareGroups;
++
++ QMutex mutex;
++ ImageFilterSettings filterCopy;
++ VersionImageFilterSettings versionFilterCopy;
++ GroupImageFilterSettings groupFilterCopy;
++ ImageFilterModelPreparer* preparer;
++ ImageFilterModelFilterer* filterer;
++
++ QHash<qlonglong, bool> filterResults;
++ bool hasOneMatch;
++ bool hasOneMatchForText;
++
++ QList<ImageFilterModelPrepareHook*> prepareHooks;
++
++/*
++ QHash<int, QSet<qlonglong> > categoryCountHashInt;
++ QHash<QString, QSet<qlonglong> > categoryCountHashString;
++
++public:
++
++ void cacheCategoryCount(int id, qlonglong imageid) const
++ { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashInt[id].insert(imageid); }
++ void cacheCategoryCount(const QString& id, qlonglong imageid) const
++ { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashString[id].insert(imageid); }
++*/
++
++public Q_SLOTS:
++
++ void preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void packageFinished(const ImageFilterModelTodoPackage& package);
++ void packageDiscarded(const ImageFilterModelTodoPackage& package);
++
++Q_SIGNALS:
++
++ void packageToPrepare(const ImageFilterModelTodoPackage& package);
++ void packageToFilter(const ImageFilterModelTodoPackage& package);
++ void reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void reAddingFinished();
++};
++
++} // namespace Digikam
++
++#endif // IMAGEFILTERMODELPRIV_H
+diff --git a/libs/database/models/imagefiltermodelthreads.cpp b/libs/database/models/imagefiltermodelthreads.cpp
+new file mode 100644
+index 0000000..aa5c462
+--- /dev/null
++++ b/libs/database/models/imagefiltermodelthreads.cpp
+@@ -0,0 +1,40 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagefiltermodel.h"
++#include "imagefiltermodelpriv.h"
++#include "imagefiltermodelthreads.h"
++
++namespace Digikam
++{
++
++ImageFilterModelWorker::ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d)
++ : d(d)
++{
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagefiltermodelthreads.h b/libs/database/models/imagefiltermodelthreads.h
+new file mode 100644
+index 0000000..83fa987
+--- /dev/null
++++ b/libs/database/models/imagefiltermodelthreads.h
+@@ -0,0 +1,100 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-11
++ * Description : Qt item model for database entries - private header
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGEFILTERMODELTHREADS_H
++#define IMAGEFILTERMODELTHREADS_H
++
++// Qt includes
++
++#include <QThread>
++
++// Local includes
++
++#include "digikam_export.h"
++#include "workerobject.h"
++
++namespace Digikam
++{
++
++class DIGIKAM_DATABASE_EXPORT ImageFilterModelWorker : public WorkerObject
++{
++ Q_OBJECT
++
++public:
++
++ explicit ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d);
++
++ bool checkVersion(const ImageFilterModelTodoPackage& package)
++ {
++ return d->version == package.version;
++ }
++
++public Q_SLOTS:
++
++ virtual void process(ImageFilterModelTodoPackage package) = 0;
++
++Q_SIGNALS:
++
++ void processed(const ImageFilterModelTodoPackage& package);
++ void discarded(const ImageFilterModelTodoPackage& package);
++
++protected:
++
++ ImageFilterModel::ImageFilterModelPrivate* d;
++};
++
++// -----------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT ImageFilterModelPreparer : public ImageFilterModelWorker
++{
++ Q_OBJECT
++
++public:
++
++ explicit ImageFilterModelPreparer(ImageFilterModel::ImageFilterModelPrivate* const d)
++ : ImageFilterModelWorker(d)
++ {
++ }
++
++ void process(ImageFilterModelTodoPackage package);
++};
++
++// ----------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT ImageFilterModelFilterer : public ImageFilterModelWorker
++{
++ Q_OBJECT
++
++public:
++
++ explicit ImageFilterModelFilterer(ImageFilterModel::ImageFilterModelPrivate* const d)
++ : ImageFilterModelWorker(d)
++ {
++ }
++
++ void process(ImageFilterModelTodoPackage package);
++};
++
++} // namespace Digikam
++
++#endif // IMAGEFILTERMODELTHREADS_H
+diff --git a/libs/database/models/imagefiltersettings.cpp b/libs/database/models/imagefiltersettings.cpp
+new file mode 100644
+index 0000000..b61e7f9
+--- /dev/null
++++ b/libs/database/models/imagefiltersettings.cpp
+@@ -0,0 +1,952 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Filter values for use with ImageFilterModel
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagefiltersettings.h"
++
++// C++ includes
++
++#include <cmath>
++
++// Qt includes
++
++#include <QDateTime>
++
++// Local includes
++
++#include "digikam_debug.h"
++#include "coredbfields.h"
++#include "digikam_globals.h"
++#include "imageinfo.h"
++#include "tagscache.h"
++#include "versionmanagersettings.h"
++
++namespace Digikam
++{
++
++ImageFilterSettings::ImageFilterSettings()
++{
++ m_untaggedFilter = false;
++ m_isUnratedExcluded = false;
++ m_ratingFilter = 0;
++ m_mimeTypeFilter = MimeFilter::AllFiles;
++ m_ratingCond = GreaterEqualCondition;
++ m_matchingCond = OrCondition;
++ m_geolocationCondition = GeolocationNoFilter;
++}
++
++DatabaseFields::Set ImageFilterSettings::watchFlags() const
++{
++ DatabaseFields::Set set;
++
++ if (isFilteringByDay())
++ {
++ set |= DatabaseFields::CreationDate;
++ }
++
++ if (isFilteringByText())
++ {
++ set |= DatabaseFields::Name;
++ set |= DatabaseFields::Comment;
++ }
++
++ if (isFilteringByRating())
++ {
++ set |= DatabaseFields::Rating;
++ }
++
++ if (isFilteringByTypeMime())
++ {
++ set |= DatabaseFields::Category;
++ set |= DatabaseFields::Format;
++ }
++
++ if (isFilteringByGeolocation())
++ {
++ set |= DatabaseFields::ImagePositionsAll;
++ }
++
++ if (isFilteringByColorLabels())
++ {
++ set |= DatabaseFields::ColorLabel;
++ }
++
++ if (isFilteringByPickLabels())
++ {
++ set |= DatabaseFields::PickLabel;
++ }
++
++ return set;
++}
++
++bool ImageFilterSettings::isFilteringByDay() const
++{
++ if (!m_dayFilter.isEmpty())
++ {
++ return true;
++ }
++
++ return false;
++}
++
++bool ImageFilterSettings::isFilteringByTags() const
++{
++ if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter)
++ {
++ return true;
++ }
++
++ return false;
++}
++
++bool ImageFilterSettings::isFilteringByColorLabels() const
++{
++ if (!m_colorLabelTagFilter.isEmpty())
++ {
++ return true;
++ }
++
++ return false;
++}
++
++bool ImageFilterSettings::isFilteringByPickLabels() const
++{
++ if (!m_pickLabelTagFilter.isEmpty())
++ {
++ return true;
++ }
++
++ return false;
++}
++
++bool ImageFilterSettings::isFilteringByText() const
++{
++ if (!m_textFilterSettings.text.isEmpty())
++ {
++ return true;
++ }
++
++ return false;
++}
++
++bool ImageFilterSettings::isFilteringByTypeMime() const
++{
++ if (m_mimeTypeFilter != MimeFilter::AllFiles)
++ {
++ return true;
++ }
++
++ return false;
++}
++
++bool ImageFilterSettings::isFilteringByGeolocation() const
++{
++ return (m_geolocationCondition != GeolocationNoFilter);
++}
++
++bool ImageFilterSettings::isFilteringByRating() const
++{
++ if (m_ratingFilter != 0 || m_ratingCond != GreaterEqualCondition || m_isUnratedExcluded)
++ {
++ return true;
++ }
++
++ return false;
++}
++
++bool ImageFilterSettings::isFilteringInternally() const
++{
++ return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty());
++}
++
++bool ImageFilterSettings::isFiltering() const
++{
++ return isFilteringByDay() ||
++ isFilteringByTags() ||
++ isFilteringByText() ||
++ isFilteringByRating() ||
++ isFilteringByTypeMime() ||
++ isFilteringByColorLabels() ||
++ isFilteringByPickLabels() ||
++ isFilteringByGeolocation();
++}
++
++void ImageFilterSettings::setDayFilter(const QList<QDateTime>& days)
++{
++ m_dayFilter.clear();
++
++ for (QList<QDateTime>::const_iterator it = days.constBegin(); it != days.constEnd(); ++it)
++ {
++ m_dayFilter.insert(*it, true);
++ }
++}
++
++void ImageFilterSettings::setTagFilter(const QList<int>& includedTags,
++ const QList<int>& excludedTags,
++ MatchingCondition matchingCondition,
++ bool showUnTagged,
++ const QList<int>& clTagIds,
++ const QList<int>& plTagIds)
++{
++ m_includeTagFilter = includedTags;
++ m_excludeTagFilter = excludedTags;
++ m_matchingCond = matchingCondition;
++ m_untaggedFilter = showUnTagged;
++ m_colorLabelTagFilter = clTagIds;
++ m_pickLabelTagFilter = plTagIds;
++}
++
++void ImageFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded)
++{
++ m_ratingFilter = rating;
++ m_ratingCond = ratingCondition;
++ m_isUnratedExcluded = isUnratedExcluded;
++}
++
++void ImageFilterSettings::setMimeTypeFilter(int mime)
++{
++ m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime;
++}
++
++void ImageFilterSettings::setGeolocationFilter(const GeolocationCondition& condition)
++{
++ m_geolocationCondition = condition;
++}
++
++void ImageFilterSettings::setTextFilter(const SearchTextFilterSettings& settings)
++{
++ m_textFilterSettings = settings;
++}
++
++void ImageFilterSettings::setTagNames(const QHash<int, QString>& hash)
++{
++ m_tagNameHash = hash;
++}
++
++void ImageFilterSettings::setAlbumNames(const QHash<int, QString>& hash)
++{
++ m_albumNameHash = hash;
++}
++
++void ImageFilterSettings::setUrlWhitelist(const QList<QUrl>& urlList, const QString& id)
++{
++ if (urlList.isEmpty())
++ {
++ m_urlWhitelists.remove(id);
++ }
++ else
++ {
++ m_urlWhitelists.insert(id, urlList);
++ }
++}
++
++void ImageFilterSettings::setIdWhitelist(const QList<qlonglong>& idList, const QString& id)
++{
++ if (idList.isEmpty())
++ {
++ m_idWhitelists.remove(id);
++ }
++ else
++ {
++ m_idWhitelists.insert(id, idList);
++ }
++}
++
++template <class ContainerA, class ContainerB>
++bool containsAnyOf(const ContainerA& listA, const ContainerB& listB)
++{
++ foreach (const typename ContainerA::value_type& a, listA)
++ {
++ if (listB.contains(a))
++ {
++ return true;
++ }
++ }
++ return false;
++}
++
++template <class ContainerA, typename Value, class ContainerB>
++bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception)
++{
++ foreach (const typename ContainerB::value_type& n, noneOfList)
++ {
++ if (n != exception && list.contains(n))
++ {
++ return false;
++ }
++ }
++ return true;
++}
++
++bool ImageFilterSettings::matches(const ImageInfo& info, bool* const foundText) const
++{
++ if (foundText)
++ {
++ *foundText = false;
++ }
++
++ if (!isFilteringInternally())
++ {
++ return true;
++ }
++
++ bool match = false;
++
++ if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty())
++ {
++ QList<int> tagIds = info.tagIds();
++ QList<int>::const_iterator it;
++
++ match = m_includeTagFilter.isEmpty();
++
++ if (m_matchingCond == OrCondition)
++ {
++ for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it)
++ {
++ if (tagIds.contains(*it))
++ {
++ match = true;
++ break;
++ }
++ }
++
++ match |= (m_untaggedFilter && tagIds.isEmpty());
++ }
++ else // AND matching condition...
++ {
++ // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match
++ if (!m_untaggedFilter)
++ {
++ for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it)
++ {
++ if (!tagIds.contains(*it))
++ {
++ break;
++ }
++ }
++
++ if (it == m_includeTagFilter.end())
++ {
++ match = true;
++ }
++ }
++ }
++
++ for (it = m_excludeTagFilter.begin(); it != m_excludeTagFilter.end(); ++it)
++ {
++ if (tagIds.contains(*it))
++ {
++ match = false;
++ break;
++ }
++ }
++ }
++ else if (m_untaggedFilter)
++ {
++ match = !TagsCache::instance()->containsPublicTags(info.tagIds());
++ }
++ else
++ {
++ match = true;
++ }
++
++ //-- Filter by pick labels ------------------------------------------------
++
++ if (!m_pickLabelTagFilter.isEmpty())
++ {
++ QList<int> tagIds = info.tagIds();
++ bool matchPL = false;
++
++ if (containsAnyOf(m_pickLabelTagFilter, tagIds))
++ {
++ matchPL = true;
++ }
++ else if (!matchPL)
++ {
++ int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel);
++
++ if (m_pickLabelTagFilter.contains(noPickLabelTagId))
++ {
++ // Searching for "has no ColorLabel" requires special handling:
++ // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag
++ matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId);
++ }
++ }
++
++ match &= matchPL;
++ }
++
++ //-- Filter by color labels ------------------------------------------------
++
++ if (!m_colorLabelTagFilter.isEmpty())
++ {
++ QList<int> tagIds = info.tagIds();
++ bool matchCL = false;
++
++ if (containsAnyOf(m_colorLabelTagFilter, tagIds))
++ {
++ matchCL = true;
++ }
++ else if (!matchCL)
++ {
++ int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel);
++
++ if (m_colorLabelTagFilter.contains(noColorLabelTagId))
++ {
++ // Searching for "has no ColorLabel" requires special handling:
++ // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag
++ matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId);
++ }
++ }
++
++ match &= matchCL;
++ }
++
++ //-- Filter by date -----------------------------------------------------------
++
++ if (!m_dayFilter.isEmpty())
++ {
++ match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime()));
++ }
++
++ //-- Filter by rating ---------------------------------------------------------
++
++ if (m_ratingFilter >= 0)
++ {
++ // for now we treat -1 (no rating) just like a rating of 0.
++ int rating = info.rating();
++
++ if (rating == -1)
++ {
++ rating = 0;
++ }
++
++ if(m_isUnratedExcluded && rating == 0)
++ {
++ match = false;
++ }
++ else
++ {
++ if (m_ratingCond == GreaterEqualCondition)
++ {
++ // If the rating is not >=, i.e it is <, then it does not match.
++ if (rating < m_ratingFilter)
++ {
++ match = false;
++ }
++ }
++ else if (m_ratingCond == EqualCondition)
++ {
++ // If the rating is not =, i.e it is !=, then it does not match.
++ if (rating != m_ratingFilter)
++ {
++ match = false;
++ }
++ }
++ else
++ {
++ // If the rating is not <=, i.e it is >, then it does not match.
++ if (rating > m_ratingFilter)
++ {
++ match = false;
++ }
++ }
++ }
++ }
++
++ // -- Filter by mime type -----------------------------------------------------
++
++ switch (m_mimeTypeFilter)
++ {
++ // info.format is a standardized string: Only one possibility per mime type
++ case MimeFilter::ImageFiles:
++ {
++ if (info.category() != DatabaseItem::Image)
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::JPGFiles:
++ {
++ if (info.format() != QLatin1String("JPG"))
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::PNGFiles:
++ {
++ if (info.format() != QLatin1String("PNG"))
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::TIFFiles:
++ {
++ if (info.format() != QLatin1String("TIFF"))
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::DNGFiles:
++ {
++ if (info.format() != QLatin1String("RAW-DNG"))
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::NoRAWFiles:
++ {
++ if (info.format().startsWith(QLatin1String("RAW")))
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::RAWFiles:
++ {
++ if (!info.format().startsWith(QLatin1String("RAW")))
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::MoviesFiles:
++ {
++ if (info.category() != DatabaseItem::Video)
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::AudioFiles:
++ {
++ if (info.category() != DatabaseItem::Audio)
++ {
++ match = false;
++ }
++
++ break;
++ }
++ case MimeFilter::RasterFiles:
++ {
++ if (info.format() != QLatin1String("PSD") && // Adobe Photoshop Document
++ info.format() != QLatin1String("PSB") && // Adobe Photoshop Big
++ info.format() != QLatin1String("XCF") && // Gimp
++ info.format() != QLatin1String("KRA") && // Krita
++ info.format() != QLatin1String("ORA") // Open Raster
++ )
++ {
++ match = false;
++ }
++
++ break;
++ }
++ default:
++ {
++ // All Files: do nothing...
++ break;
++ }
++ }
++
++ //-- Filter by geolocation ----------------------------------------------------
++
++ if (m_geolocationCondition!=GeolocationNoFilter)
++ {
++ if (m_geolocationCondition==GeolocationNoCoordinates)
++ {
++ if (info.hasCoordinates())
++ {
++ match = false;
++ }
++ }
++ else if (m_geolocationCondition==GeolocationHasCoordinates)
++ {
++ if (!info.hasCoordinates())
++ {
++ match = false;
++ }
++ }
++ }
++
++ //-- Filter by text -----------------------------------------------------------
++
++ if (!m_textFilterSettings.text.isEmpty())
++ {
++ bool textMatch = false;
++
++ // Image name
++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName &&
++ info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
++ {
++ textMatch = true;
++ }
++
++ // Image title
++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle &&
++ info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
++ {
++ textMatch = true;
++ }
++
++ // Image comment
++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment &&
++ info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
++ {
++ textMatch = true;
++ }
++
++ // Tag names
++ foreach(int id, info.tagIds())
++ {
++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::TagName &&
++ m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
++ {
++ textMatch = true;
++ }
++ }
++
++ // Album names
++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName &&
++ m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
++ {
++ textMatch = true;
++ }
++
++ // Image Aspect Ratio
++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio)
++ {
++ QRegExp expRatio (QLatin1String("^\\d+:\\d+$"));
++ QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$"));
++
++ if (expRatio.indexIn(m_textFilterSettings.text) > -1 && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+"))))
++ {
++ QString trimmedTextFilterSettingsText = m_textFilterSettings.text;
++ QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1String(":"), QString::SkipEmptyParts);
++
++ if (numberStringList.length() == 2)
++ {
++ QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1);
++ bool canConverseNum = false;
++ bool canConverseDenom = false;
++ int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10);
++
++ if (canConverseNum && canConverseDenom)
++ {
++ if (fabs(info.aspectRatio() - (double)num / denom) < 0.1)
++ textMatch = true;
++ }
++ }
++ }
++ else if (expFloat.indexIn(m_textFilterSettings.text) > -1)
++ {
++ QString trimmedTextFilterSettingsText = m_textFilterSettings.text;
++ bool canConverse = false;
++ double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse);
++
++ if (canConverse)
++ {
++ if (fabs(info.aspectRatio() - ratio) < 0.1)
++ textMatch = true;
++ }
++ }
++ }
++
++ // Image Pixel Size
++ // See bug #341053 for details.
++
++ if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize)
++ {
++ QSize size = info.dimensions();
++ int pixelSize = size.height()*size.width();
++ QString text = m_textFilterSettings.text;
++
++ if(text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && pixelSize > (text.remove(0,1)).toInt())
++ {
++ textMatch = true;
++ }
++ else if(text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && pixelSize < (text.remove(0,1)).toInt())
++ {
++ textMatch = true;
++ }
++ else if(text.contains(QRegExp(QLatin1String("^\\d+$"))) && pixelSize == text.toInt())
++ {
++ textMatch = true;
++ }
++ }
++
++ match &= textMatch;
++
++ if (foundText)
++ {
++ *foundText = textMatch;
++ }
++ }
++
++ // -- filter by URL-whitelists ------------------------------------------------
++ // NOTE: whitelists are always AND for now.
++
++ if (match)
++ {
++ const QUrl url = info.fileUrl();
++
++ for (QHash<QString, QList<QUrl>>::const_iterator it = m_urlWhitelists.constBegin();
++ it!=m_urlWhitelists.constEnd(); ++it)
++ {
++ match = it->contains(url);
++
++ if (!match)
++ {
++ break;
++ }
++ }
++ }
++
++ if (match)
++ {
++ const qlonglong id = info.id();
++
++ for (QHash<QString, QList<qlonglong> >::const_iterator it = m_idWhitelists.constBegin();
++ it!=m_idWhitelists.constEnd(); ++it)
++ {
++ match = it->contains(id);
++
++ if (!match)
++ {
++ break;
++ }
++ }
++ }
++
++ return match;
++}
++
++// -------------------------------------------------------------------------------------------------
++
++VersionImageFilterSettings::VersionImageFilterSettings()
++{
++ m_includeTagFilter = 0;
++ m_exceptionTagFilter = 0;
++}
++
++VersionImageFilterSettings::VersionImageFilterSettings(const VersionManagerSettings& settings)
++{
++ setVersionManagerSettings(settings);
++}
++
++bool VersionImageFilterSettings::operator==(const VersionImageFilterSettings& other) const
++{
++ return m_excludeTagFilter == other.m_excludeTagFilter &&
++ m_exceptionLists == other.m_exceptionLists;
++}
++
++bool VersionImageFilterSettings::matches(const ImageInfo& info) const
++{
++ if (!isFiltering())
++ {
++ return true;
++ }
++
++ const qlonglong id = info.id();
++
++ for (QHash<QString, QList<qlonglong> >::const_iterator it = m_exceptionLists.constBegin();
++ it != m_exceptionLists.constEnd(); ++it)
++ {
++ if (it->contains(id))
++ {
++ return true;
++ }
++ }
++
++ bool match = true;
++ QList<int> tagIds = info.tagIds();
++
++ if (!tagIds.contains(m_includeTagFilter))
++ {
++ for (QList<int>::const_iterator it = m_excludeTagFilter.begin();
++ it != m_excludeTagFilter.end(); ++it)
++ {
++ if (tagIds.contains(*it))
++ {
++ match = false;
++ break;
++ }
++ }
++ }
++
++ if (!match)
++ {
++ if (tagIds.contains(m_exceptionTagFilter))
++ {
++ match = true;
++ }
++ }
++
++ return match;
++}
++
++bool VersionImageFilterSettings::isHiddenBySettings(const ImageInfo& info) const
++{
++ QList<int> tagIds = info.tagIds();
++
++ foreach(int tagId, m_excludeTagFilter)
++ {
++ if (tagIds.contains(tagId))
++ {
++ return true;
++ }
++ }
++
++ return false;
++}
++
++bool VersionImageFilterSettings::isExemptedBySettings(const ImageInfo& info) const
++{
++ return info.tagIds().contains(m_exceptionTagFilter);
++}
++
++void VersionImageFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings)
++{
++ m_excludeTagFilter.clear();
++
++ if (!settings.enabled)
++ {
++ return;
++ }
++
++ if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal))
++ {
++ m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion());
++ }
++
++ if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates))
++ {
++ m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion());
++ }
++
++ m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion());
++ m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible());
++}
++
++void VersionImageFilterSettings::setExceptionList(const QList<qlonglong>& idList, const QString& id)
++{
++ if (idList.isEmpty())
++ {
++ m_exceptionLists.remove(id);
++ }
++ else
++ {
++ m_exceptionLists.insert(id, idList);
++ }
++}
++
++bool VersionImageFilterSettings::isFiltering() const
++{
++ return !m_excludeTagFilter.isEmpty();
++}
++
++bool VersionImageFilterSettings::isFilteringByTags() const
++{
++ return isFiltering();
++}
++
++// -------------------------------------------------------------------------------------------------
++
++GroupImageFilterSettings::GroupImageFilterSettings()
++ : m_allOpen(false)
++{
++}
++
++bool GroupImageFilterSettings::operator==(const GroupImageFilterSettings& other) const
++{
++ return (m_allOpen == other.m_allOpen &&
++ m_openGroups == other.m_openGroups);
++}
++
++bool GroupImageFilterSettings::matches(const ImageInfo& info) const
++{
++ if (m_allOpen)
++ {
++ return true;
++ }
++
++ if (info.isGrouped())
++ {
++ return m_openGroups.contains(info.groupImage().id());
++ }
++ return true;
++}
++
++void GroupImageFilterSettings::setOpen(qlonglong group, bool open)
++{
++ if (open)
++ {
++ m_openGroups << group;
++ }
++ else
++ {
++ m_openGroups.remove(group);
++ }
++}
++
++bool GroupImageFilterSettings::isOpen(qlonglong group) const
++{
++ return m_openGroups.contains(group);
++}
++
++void GroupImageFilterSettings::setAllOpen(bool open)
++{
++ m_allOpen = open;
++}
++
++bool GroupImageFilterSettings::isAllOpen() const
++{
++ return m_allOpen;
++}
++
++bool GroupImageFilterSettings::isFiltering() const
++{
++ return !m_allOpen;
++}
++
++DatabaseFields::Set GroupImageFilterSettings::watchFlags() const
++{
++ return DatabaseFields::ImageRelations;
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagefiltersettings.h b/libs/database/models/imagefiltersettings.h
+new file mode 100644
+index 0000000..0e7beae
+--- /dev/null
++++ b/libs/database/models/imagefiltersettings.h
+@@ -0,0 +1,349 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Filter values for use with ImageFilterModel
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
++ * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGEFILTERSETTINGS_H
++#define IMAGEFILTERSETTINGS_H
++
++// Qt includes
++
++#include <QHash>
++#include <QList>
++#include <QMap>
++#include <QString>
++#include <QSet>
++#include <QUrl>
++
++// Local includes
++
++#include "searchtextbar.h"
++#include "mimefilter.h"
++#include "digikam_export.h"
++
++namespace Digikam
++{
++
++class ImageInfo;
++class VersionManagerSettings;
++
++namespace DatabaseFields
++{
++ class Set;
++}
++
++// ---------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT SearchTextFilterSettings : public SearchTextSettings
++{
++
++public:
++
++ enum TextFilterFields
++ {
++ None = 0x00,
++ ImageName = 0x01,
++ ImageTitle = 0x02,
++ ImageComment = 0x04,
++ TagName = 0x08,
++ AlbumName = 0x10,
++ ImageAspectRatio = 0x20,
++ ImagePixelSize = 0x40,
++ All = ImageName | ImageTitle | ImageComment | TagName | AlbumName | ImageAspectRatio | ImagePixelSize
++ };
++
++public:
++
++ SearchTextFilterSettings()
++ {
++ textFields = None;
++ }
++
++ explicit SearchTextFilterSettings(const SearchTextSettings& settings)
++ {
++ caseSensitive = settings.caseSensitive;
++ text = settings.text;
++ textFields = None;
++ }
++
++ TextFilterFields textFields;
++};
++
++// ---------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT ImageFilterSettings
++{
++public:
++
++ ImageFilterSettings();
++
++ /**
++ * Returns true if the given ImageInfo matches the filter criteria.
++ * Optionally, foundText is set to true if it matched by text search.
++ */
++ bool matches(const ImageInfo& info, bool* const foundText = 0) const;
++
++public:
++
++ /// --- Tags filter ---
++
++ /// Possible logical matching condition used to sort tags id.
++ enum MatchingCondition
++ {
++ OrCondition,
++ AndCondition
++ };
++
++ void setTagFilter(const QList<int>& includedTags,
++ const QList<int>& excludedTags,
++ MatchingCondition matchingCond,
++ bool showUnTagged,
++ const QList<int>& clTagIds,
++ const QList<int>& plTagIds);
++
++public:
++
++ /// --- Rating filter ---
++
++ /// Possible conditions used to filter rating: >=, =, <=
++ enum RatingCondition
++ {
++ GreaterEqualCondition,
++ EqualCondition,
++ LessEqualCondition
++ };
++
++ void setRatingFilter(int rating, RatingCondition ratingCond, bool isUnratedExcluded);
++
++public:
++
++ /// --- Date filter ---
++ void setDayFilter(const QList<QDateTime>& days);
++
++public:
++
++ /// --- Text filter ---
++ void setTextFilter(const SearchTextFilterSettings& settings);
++ void setTagNames(const QHash<int, QString>& tagNameHash);
++ void setAlbumNames(const QHash<int, QString>& albumNameHash);
++
++public:
++
++ /// --- Mime filter ---
++ void setMimeTypeFilter(int mimeTypeFilter);
++
++public:
++
++ /// --- Geolocation filter
++ enum GeolocationCondition
++ {
++ GeolocationNoFilter = 0,
++ GeolocationNoCoordinates = 1 << 1,
++ GeolocationHasCoordinates = 1 << 2
++ };
++
++ void setGeolocationFilter(const GeolocationCondition& condition);
++
++public:
++
++ /// Returns if the day is a filter criteria
++ bool isFilteringByDay() const;
++
++ /// Returns if the type mime is a filter criteria
++ bool isFilteringByTypeMime() const;
++
++ /// Returns whether geolocation is a filter criteria
++ bool isFilteringByGeolocation() const;
++
++ /// Returns if the rating is a filter criteria
++ bool isFilteringByRating() const;
++
++ /// Returns if the pick labels is a filter criteria
++ bool isFilteringByPickLabels() const;
++
++ /// Returns if the color labels is a filter criteria
++ bool isFilteringByColorLabels() const;
++
++ /// Returns if the tag is a filter criteria
++ bool isFilteringByTags() const;
++
++ /// Returns if the text (including comment) is a filter criteria
++ bool isFilteringByText() const;
++
++ /// Returns if images will be filtered by these criteria at all
++ bool isFiltering() const;
++
++public:
++
++ /// --- URL whitelist filter
++ void setUrlWhitelist(const QList<QUrl>& urlList, const QString& id);
++
++public:
++
++ /// --- ID whitelist filter
++ void setIdWhitelist(const QList<qlonglong>& idList, const QString& id);
++
++public:
++
++ /// --- Change notification ---
++
++ /** Returns database fields a change in which would affect the current filtering.
++ * To find out if an image tag change affects filtering, test isFilteringByTags().
++ * The text filter will also be affected by changes in tags and album names.
++ */
++ DatabaseFields::Set watchFlags() const;
++
++private:
++
++ /**
++ * @brief Returns whether some internal filtering (whitelist by id or URL) or normal filtering is going on
++ */
++ bool isFilteringInternally() const;
++
++private:
++
++ /// --- Tags filter ---
++ bool m_untaggedFilter;
++ QList<int> m_includeTagFilter;
++ QList<int> m_excludeTagFilter;
++ MatchingCondition m_matchingCond;
++ QList<int> m_colorLabelTagFilter;
++ QList<int> m_pickLabelTagFilter;
++
++ /// --- Rating filter ---
++ int m_ratingFilter;
++ RatingCondition m_ratingCond;
++ bool m_isUnratedExcluded;
++
++ /// --- Date filter ---
++ QMap<QDateTime, bool> m_dayFilter;
++
++ /// --- Text filter ---
++ SearchTextFilterSettings m_textFilterSettings;
++
++ /// Helpers for text search: Set these if you want to search album or tag names with text search
++ QHash<int, QString> m_tagNameHash;
++ QHash<int, QString> m_albumNameHash;
++
++ /// --- Mime filter ---
++ MimeFilter::TypeMimeFilter m_mimeTypeFilter;
++
++ /// --- Geolocation filter
++ GeolocationCondition m_geolocationCondition;
++
++ /// --- URL whitelist filter
++ QHash<QString,QList<QUrl>> m_urlWhitelists;
++
++ /// --- ID whitelist filter
++ QHash<QString,QList<qlonglong> > m_idWhitelists;
++};
++
++// ---------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT VersionImageFilterSettings
++{
++public:
++
++ VersionImageFilterSettings();
++ explicit VersionImageFilterSettings(const VersionManagerSettings& settings);
++
++ bool operator==(const VersionImageFilterSettings& other) const;
++
++ /**
++ * Returns true if the given ImageInfo matches the filter criteria.
++ */
++ bool matches(const ImageInfo& info) const;
++
++ bool isHiddenBySettings(const ImageInfo& info) const;
++ bool isExemptedBySettings(const ImageInfo& info) const;
++
++ /// --- Tags filter ---
++
++ void setVersionManagerSettings(const VersionManagerSettings& settings);
++
++ /**
++ * Add list with exceptions: These images will be exempted from filtering by this filter
++ */
++ void setExceptionList(const QList<qlonglong>& idlist, const QString& id);
++
++ /// Returns if images will be filtered by these criteria at all
++ bool isFiltering() const;
++
++ /// Returns if the tag is a filter criteria
++ bool isFilteringByTags() const;
++
++ /// DatabaseFields::Set watchFlags() const: Would return 0
++
++protected:
++
++ QList<int> m_excludeTagFilter;
++ int m_includeTagFilter;
++ int m_exceptionTagFilter;
++ QHash<QString,QList<qlonglong> > m_exceptionLists;
++};
++
++// ---------------------------------------------------------------------------------------
++
++class DIGIKAM_DATABASE_EXPORT GroupImageFilterSettings
++{
++public:
++
++ GroupImageFilterSettings();
++
++ bool operator==(const GroupImageFilterSettings& other) const;
++
++ /**
++ * Returns true if the given ImageInfo matches the filter criteria.
++ */
++ bool matches(const ImageInfo& info) const;
++
++ /**
++ * Open or close a group.
++ */
++ void setOpen(qlonglong group, bool open);
++ bool isOpen(qlonglong group) const;
++
++ /**
++ * Open all groups
++ */
++ void setAllOpen(bool open);
++ bool isAllOpen() const;
++
++ /// Returns if images will be filtered by these criteria at all
++ bool isFiltering() const;
++
++ DatabaseFields::Set watchFlags() const;
++
++protected:
++
++ bool m_allOpen;
++ QSet<qlonglong> m_openGroups;
++};
++
++} // namespace Digikam
++
++Q_DECLARE_METATYPE(Digikam::ImageFilterSettings::GeolocationCondition)
++
++#endif // IMAGEFILTERSETTINGS_H
+diff --git a/libs/database/models/imagelistmodel.cpp b/libs/database/models/imagelistmodel.cpp
+new file mode 100644
+index 0000000..fafce34
+--- /dev/null
++++ b/libs/database/models/imagelistmodel.cpp
+@@ -0,0 +1,70 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2010-12-06
++ * Description : An image model based on a static list
++ *
++ * Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagelistmodel.h"
++
++// Local includes
++
++#include "digikam_debug.h"
++#include "coredbaccess.h"
++#include "coredbchangesets.h"
++#include "coredbwatch.h"
++#include "imageinfo.h"
++#include "imageinfolist.h"
++
++namespace Digikam
++{
++
++ImageListModel::ImageListModel(QObject* parent)
++ : ImageThumbnailModel(parent)
++{
++ connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)),
++ this, SLOT(slotCollectionImageChange(CollectionImageChangeset)));
++}
++
++ImageListModel::~ImageListModel()
++{
++}
++
++void ImageListModel::slotCollectionImageChange(const CollectionImageChangeset& changeset)
++{
++ if (isEmpty())
++ {
++ return;
++ }
++
++ switch (changeset.operation())
++ {
++ case CollectionImageChangeset::Added:
++ break;
++ case CollectionImageChangeset::Removed:
++ case CollectionImageChangeset::RemovedAll:
++ removeImageInfos(ImageInfoList(changeset.ids()));
++ break;
++
++ default:
++ break;
++ }
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagelistmodel.h b/libs/database/models/imagelistmodel.h
+new file mode 100644
+index 0000000..a225b1b
+--- /dev/null
++++ b/libs/database/models/imagelistmodel.h
+@@ -0,0 +1,63 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2010-12-06
++ * Description : An image model based on a static list
++ *
++ * Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGELISTMODEL_H
++#define IMAGELISTMODEL_H
++
++// Local includes
++
++#include "imagethumbnailmodel.h"
++#include "digikam_export.h"
++
++namespace Digikam
++{
++
++class ImageChangeset;
++class CollectionImageChangeset;
++
++class DIGIKAM_DATABASE_EXPORT ImageListModel : public ImageThumbnailModel
++{
++ Q_OBJECT
++
++public:
++
++ explicit ImageListModel(QObject* parent = 0);
++ ~ImageListModel();
++
++ // NOTE: necessary methods to add and remove ImageInfos to the model are inherited from ImageModel
++
++Q_SIGNALS:
++
++ /**
++ * Emitted when images are removed from the model because they are removed in the database
++ */
++ void imageInfosRemoved(const QList<ImageInfo>& infos);
++
++protected Q_SLOTS:
++
++ void slotCollectionImageChange(const CollectionImageChangeset& changeset);
++};
++
++} // namespace Digikam
++
++#endif // IMAGELISTMODEL_H
+diff --git a/libs/database/models/imagemodel.cpp b/libs/database/models/imagemodel.cpp
+new file mode 100644
+index 0000000..41b43cf
+--- /dev/null
++++ b/libs/database/models/imagemodel.cpp
+@@ -0,0 +1,1368 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagemodel.h"
++
++// Qt includes
++
++#include <QHash>
++#include <QItemSelection>
++
++// Local includes
++
++#include "digikam_debug.h"
++#include "coredbchangesets.h"
++#include "coredbfields.h"
++#include "coredbwatch.h"
++#include "imageinfo.h"
++#include "imageinfolist.h"
++#include "abstractitemdragdrophandler.h"
++
++namespace Digikam
++{
++
++class ImageModel::Private
++{
++public:
++
++ Private()
++ {
++ preprocessor = 0;
++ keepFilePathCache = false;
++ sendRemovalSignals = false;
++ incrementalUpdater = 0;
++ refreshing = false;
++ reAdding = false;
++ incrementalRefreshRequested = false;
++ }
++
++ ImageInfoList infos;
++ QList<QVariant> extraValues;
++ QHash<qlonglong, int> idHash;
++
++ bool keepFilePathCache;
++ QHash<QString, qlonglong> filePathHash;
++
++ bool sendRemovalSignals;
++
++ QObject* preprocessor;
++ bool refreshing;
++ bool reAdding;
++ bool incrementalRefreshRequested;
++
++ DatabaseFields::Set watchFlags;
++
++ class ImageModelIncrementalUpdater* incrementalUpdater;
++
++ ImageInfoList pendingInfos;
++ QList<QVariant> pendingExtraValues;
++
++ inline bool isValid(const QModelIndex& index)
++ {
++ if (!index.isValid())
++ {
++ return false;
++ }
++
++ if (index.row() < 0 || index.row() >= infos.size())
++ {
++ qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index;
++ return false;
++ }
++
++ return true;
++ }
++ inline bool extraValueValid(const QModelIndex& index)
++ {
++ // we assume isValid() being called before, no duplicate checks
++ if (index.row() >= extraValues.size())
++ {
++ qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index;
++ return false;
++ }
++
++ return true;
++ }
++};
++
++typedef QPair<int, int> IntPair; // to make foreach macro happy
++typedef QList<IntPair> IntPairList;
++
++class ImageModelIncrementalUpdater
++{
++public:
++
++ explicit ImageModelIncrementalUpdater(ImageModel::Private* d);
++
++ void appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved);
++ QList<IntPair> oldIndexes();
++
++ static QList<IntPair> toContiguousPairs(const QList<int>& ids);
++
++public:
++
++ QHash<qlonglong, int> oldIds;
++ QList<QVariant> oldExtraValues;
++ QList<ImageInfo> newInfos;
++ QList<QVariant> newExtraValues;
++ QList<IntPairList> modelRemovals;
++};
++
++ImageModel::ImageModel(QObject* parent)
++ : QAbstractListModel(parent),
++ d(new Private)
++{
++ connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)),
++ this, SLOT(slotImageChange(ImageChangeset)));
++
++ connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)),
++ this, SLOT(slotImageTagChange(ImageTagChangeset)));
++}
++
++ImageModel::~ImageModel()
++{
++ delete d->incrementalUpdater;
++ delete d;
++}
++
++// ------------ Access methods -------------
++
++void ImageModel::setKeepsFilePathCache(bool keepCache)
++{
++ d->keepFilePathCache = keepCache;
++}
++
++bool ImageModel::keepsFilePathCache() const
++{
++ return d->keepFilePathCache;
++}
++
++bool ImageModel::isEmpty() const
++{
++ return d->infos.isEmpty();
++}
++
++void ImageModel::setWatchFlags(const DatabaseFields::Set& set)
++{
++ d->watchFlags = set;
++}
++
++ImageInfo ImageModel::imageInfo(const QModelIndex& index) const
++{
++ if (!d->isValid(index))
++ {
++ return ImageInfo();
++ }
++
++ return d->infos.at(index.row());
++}
++
++ImageInfo& ImageModel::imageInfoRef(const QModelIndex& index) const
++{
++ return d->infos[index.row()];
++}
++
++qlonglong ImageModel::imageId(const QModelIndex& index) const
++{
++ if (!d->isValid(index))
++ {
++ return 0;
++ }
++
++ return d->infos.at(index.row()).id();
++}
++
++QList<ImageInfo> ImageModel::imageInfos(const QList<QModelIndex>& indexes) const
++{
++ QList<ImageInfo> infos;
++
++ foreach(const QModelIndex& index, indexes)
++ {
++ infos << imageInfo(index);
++ }
++
++ return infos;
++}
++
++QList<qlonglong> ImageModel::imageIds(const QList<QModelIndex>& indexes) const
++{
++ QList<qlonglong> ids;
++
++ foreach(const QModelIndex& index, indexes)
++ {
++ ids << imageId(index);
++ }
++
++ return ids;
++}
++
++ImageInfo ImageModel::imageInfo(int row) const
++{
++ if (row >= d->infos.size())
++ {
++ return ImageInfo();
++ }
++
++ return d->infos.at(row);
++}
++
++ImageInfo& ImageModel::imageInfoRef(int row) const
++{
++ return d->infos[row];
++}
++
++qlonglong ImageModel::imageId(int row) const
++{
++ if (row < 0 || row >= d->infos.size())
++ {
++ return -1;
++ }
++
++ return d->infos.at(row).id();
++}
++
++QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info) const
++{
++ return indexForImageId(info.id());
++}
++
++QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const
++{
++ return indexForImageId(info.id(), extraValue);
++}
++
++QList<QModelIndex> ImageModel::indexesForImageInfo(const ImageInfo& info) const
++{
++ return indexesForImageId(info.id());
++}
++
++QModelIndex ImageModel::indexForImageId(qlonglong id) const
++{
++ int index = d->idHash.value(id, -1);
++
++ if (index != -1)
++ {
++ return createIndex(index, 0);
++ }
++
++ return QModelIndex();
++}
++
++QModelIndex ImageModel::indexForImageId(qlonglong id, const QVariant& extraValue) const
++{
++ if (d->extraValues.isEmpty())
++ return indexForImageId(id);
++
++ QHash<qlonglong, int>::const_iterator it;
++
++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
++ {
++ if (d->extraValues.at(it.value()) == extraValue)
++ return createIndex(it.value(), 0);
++ }
++
++ return QModelIndex();
++}
++
++QList<QModelIndex> ImageModel::indexesForImageId(qlonglong id) const
++{
++ QList<QModelIndex> indexes;
++ QHash<qlonglong, int>::const_iterator it;
++
++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
++ {
++ indexes << createIndex(it.value(), 0);
++ }
++
++ return indexes;
++}
++
++int ImageModel::numberOfIndexesForImageInfo(const ImageInfo& info) const
++{
++ return numberOfIndexesForImageId(info.id());
++}
++
++int ImageModel::numberOfIndexesForImageId(qlonglong id) const
++{
++ if (d->extraValues.isEmpty())
++ {
++ return 0;
++ }
++
++ int count = 0;
++ QHash<qlonglong,int>::const_iterator it;
++
++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
++ {
++ ++count;
++ }
++
++ return count;
++}
++
++// static method
++ImageInfo ImageModel::retrieveImageInfo(const QModelIndex& index)
++{
++ if (!index.isValid())
++ {
++ return ImageInfo();
++ }
++
++ ImageModel* const model = index.data(ImageModelPointerRole).value<ImageModel*>();
++ int row = index.data(ImageModelInternalId).toInt();
++
++ if (!model)
++ {
++ return ImageInfo();
++ }
++
++ return model->imageInfo(row);
++}
++
++// static method
++qlonglong ImageModel::retrieveImageId(const QModelIndex& index)
++{
++ if (!index.isValid())
++ {
++ return 0;
++ }
++
++ ImageModel* const model = index.data(ImageModelPointerRole).value<ImageModel*>();
++ int row = index.data(ImageModelInternalId).toInt();
++
++ if (!model)
++ {
++ return 0;
++ }
++
++ return model->imageId(row);
++}
++
++QModelIndex ImageModel::indexForPath(const QString& filePath) const
++{
++ if (d->keepFilePathCache)
++ {
++ return indexForImageId(d->filePathHash.value(filePath));
++ }
++ else
++ {
++ const int size = d->infos.size();
++
++ for (int i=0; i<size; ++i)
++ {
++ if (d->infos.at(i).filePath() == filePath)
++ {
++ return createIndex(i, 0);
++ }
++ }
++ }
++
++ return QModelIndex();
++}
++
++QList<QModelIndex> ImageModel::indexesForPath(const QString& filePath) const
++{
++ if (d->keepFilePathCache)
++ {
++ return indexesForImageId(d->filePathHash.value(filePath));
++ }
++ else
++ {
++ QList<QModelIndex> indexes;
++ const int size = d->infos.size();
++
++ for (int i=0; i<size; ++i)
++ {
++ if (d->infos.at(i).filePath() == filePath)
++ {
++ indexes << createIndex(i, 0);
++ }
++ }
++
++ return indexes;
++ }
++}
++
++ImageInfo ImageModel::imageInfo(const QString& filePath) const
++{
++ if (d->keepFilePathCache)
++ {
++ qlonglong id = d->filePathHash.value(filePath);
++
++ if (id)
++ {
++ int index = d->idHash.value(id, -1);
++
++ if (index != -1)
++ {
++ return d->infos.at(index);
++ }
++ }
++ }
++ else
++ {
++ foreach(const ImageInfo& info, d->infos)
++ {
++ if (info.filePath() == filePath)
++ {
++ return info;
++ }
++ }
++ }
++
++ return ImageInfo();
++}
++
++QList<ImageInfo> ImageModel::imageInfos(const QString& filePath) const
++{
++ QList<ImageInfo> infos;
++
++ if (d->keepFilePathCache)
++ {
++ qlonglong id = d->filePathHash.value(filePath);
++
++ if (id)
++ {
++ foreach(int index, d->idHash.values(id))
++ {
++ infos << d->infos.at(index);
++ }
++ }
++ }
++ else
++ {
++ foreach(const ImageInfo& info, d->infos)
++ {
++ if (info.filePath() == filePath)
++ {
++ infos << info;
++ }
++ }
++ }
++
++ return infos;
++}
++
++void ImageModel::addImageInfo(const ImageInfo& info)
++{
++ addImageInfos(QList<ImageInfo>() << info, QList<QVariant>());
++}
++
++void ImageModel::addImageInfos(const QList<ImageInfo>& infos)
++{
++ addImageInfos(infos, QList<QVariant>());
++}
++
++void ImageModel::addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (infos.isEmpty())
++ {
++ return;
++ }
++
++ if (d->incrementalUpdater)
++ {
++ d->incrementalUpdater->appendInfos(infos, extraValues);
++ }
++ else
++ {
++ appendInfos(infos, extraValues);
++ }
++}
++
++void ImageModel::addImageInfoSynchronously(const ImageInfo& info)
++{
++ addImageInfosSynchronously(QList<ImageInfo>() << info, QList<QVariant>());
++}
++
++void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos)
++{
++ addImageInfos(infos, QList<QVariant>());
++}
++
++void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (infos.isEmpty())
++ {
++ return;
++ }
++
++ publiciseInfos(infos, extraValues);
++ emit processAdded(infos, extraValues);
++}
++
++void ImageModel::ensureHasImageInfo(const ImageInfo& info)
++{
++ ensureHasImageInfos(QList<ImageInfo>() << info, QList<QVariant>());
++}
++
++void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos)
++{
++ ensureHasImageInfos(infos, QList<QVariant>());
++}
++
++void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (extraValues.isEmpty())
++ {
++ if (!d->pendingExtraValues.isEmpty())
++ {
++ qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
++ return;
++ }
++ }
++ else
++ {
++ if (d->pendingInfos.size() != d->pendingExtraValues.size())
++ {
++ qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
++ return;
++ }
++ }
++
++ d->pendingInfos << infos;
++ d->pendingExtraValues << extraValues;
++ cleanSituationChecks();
++}
++
++void ImageModel::clearImageInfos()
++{
++ d->infos.clear();
++ d->extraValues.clear();
++ d->idHash.clear();
++ d->filePathHash.clear();
++ delete d->incrementalUpdater;
++ d->incrementalUpdater = 0;
++ d->pendingInfos.clear();
++ d->pendingExtraValues.clear();
++ d->refreshing = false;
++ d->reAdding = false;
++ d->incrementalRefreshRequested = false;
++
++ beginResetModel();
++ endResetModel();
++
++ imageInfosCleared();
++}
++
++void ImageModel::setImageInfos(const QList<ImageInfo>& infos)
++{
++ clearImageInfos();
++ addImageInfos(infos);
++}
++
++QList<ImageInfo> ImageModel::imageInfos() const
++{
++ return d->infos;
++}
++
++QList<qlonglong> ImageModel::imageIds() const
++{
++ return d->idHash.keys();
++}
++
++bool ImageModel::hasImage(qlonglong id) const
++{
++ return d->idHash.contains(id);
++}
++
++bool ImageModel::hasImage(const ImageInfo& info) const
++{
++ return d->idHash.contains(info.id());
++}
++
++bool ImageModel::hasImage(const ImageInfo& info, const QVariant& extraValue) const
++{
++ return hasImage(info.id(), extraValue);
++}
++
++bool ImageModel::hasImage(qlonglong id, const QVariant& extraValue) const
++{
++ if (d->extraValues.isEmpty())
++ return hasImage(id);
++
++ QHash<qlonglong, int>::const_iterator it;
++
++ for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
++ {
++ if (d->extraValues.at(it.value()) == extraValue)
++ return true;
++ }
++
++ return false;;
++}
++
++QList<ImageInfo> ImageModel::uniqueImageInfos() const
++{
++ if (d->extraValues.isEmpty())
++ {
++ return d->infos;
++ }
++
++ QList<ImageInfo> uniqueInfos;
++ const int size = d->infos.size();
++
++ for (int i=0; i<size; ++i)
++ {
++ const ImageInfo& info = d->infos.at(i);
++
++ if (d->idHash.value(info.id()) == i)
++ {
++ uniqueInfos << info;
++ }
++ }
++
++ return uniqueInfos;
++}
++
++void ImageModel::emitDataChangedForAll()
++{
++ if (d->infos.isEmpty())
++ {
++ return;
++ }
++
++ QModelIndex first = createIndex(0, 0);
++ QModelIndex last = createIndex(d->infos.size() - 1, 0);
++ emit dataChanged(first, last);
++}
++
++void ImageModel::emitDataChangedForSelection(const QItemSelection& selection)
++{
++ if (!selection.isEmpty())
++ {
++ foreach(const QItemSelectionRange& range, selection)
++ {
++ emit dataChanged(range.topLeft(), range.bottomRight());
++ }
++ }
++}
++
++void ImageModel::ensureHasGroupedImages(const ImageInfo& groupLeader)
++{
++ ensureHasImageInfos(groupLeader.groupedImages());
++}
++
++// ------------ Preprocessing -------------
++
++void ImageModel::setPreprocessor(QObject* preprocessor)
++{
++ unsetPreprocessor(d->preprocessor);
++ d->preprocessor = preprocessor;
++}
++
++void ImageModel::unsetPreprocessor(QObject* preprocessor)
++{
++ if (preprocessor && d->preprocessor == preprocessor)
++ {
++ disconnect(this, SIGNAL(preprocess(QList<ImageInfo>,QList<QVariant>)), 0, 0);
++ disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
++ disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished()));
++ }
++}
++
++void ImageModel::appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (infos.isEmpty())
++ {
++ return;
++ }
++
++ if (d->preprocessor)
++ {
++ d->reAdding = true;
++ emit preprocess(infos, extraValues);
++ }
++ else
++ {
++ publiciseInfos(infos, extraValues);
++ }
++}
++
++void ImageModel::appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ // This method does deduplication. It is private because in context of readding or refreshing it is of no use.
++
++ if (extraValues.isEmpty())
++ {
++ QList<ImageInfo> checkedInfos;
++
++ foreach (const ImageInfo& info, infos)
++ {
++ if (!hasImage(info))
++ {
++ checkedInfos << info;
++ }
++ }
++
++ appendInfos(checkedInfos, QList<QVariant>());
++ }
++ else
++ {
++ QList<ImageInfo> checkedInfos;
++ QList<QVariant> checkedExtraValues;
++ const int size = infos.size();
++
++ for (int i=0; i<size; i++)
++ {
++ if (!hasImage(infos[i], extraValues[i]))
++ {
++ checkedInfos << infos[i];
++ checkedExtraValues << extraValues[i];
++ }
++ }
++
++ appendInfos(checkedInfos, checkedExtraValues);
++ }
++}
++
++void ImageModel::reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ // addImageInfos -> appendInfos -> preprocessor -> reAddImageInfos
++ publiciseInfos(infos, extraValues);
++}
++
++void ImageModel::reAddingFinished()
++{
++ d->reAdding = false;
++ cleanSituationChecks();
++}
++
++void ImageModel::startRefresh()
++{
++ d->refreshing = true;
++}
++
++void ImageModel::finishRefresh()
++{
++ d->refreshing = false;
++ cleanSituationChecks();
++}
++
++bool ImageModel::isRefreshing() const
++{
++ return d->refreshing;
++}
++
++void ImageModel::cleanSituationChecks()
++{
++ // For starting an incremental refresh we want a clear situation:
++ // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(),
++ // any batches sent to preprocessor for re-adding have been re-added.
++ if (d->refreshing || d->reAdding)
++ {
++ return;
++ }
++
++ if (!d->pendingInfos.isEmpty())
++ {
++ appendInfosChecked(d->pendingInfos, d->pendingExtraValues);
++ d->pendingInfos.clear();
++ d->pendingExtraValues.clear();
++ cleanSituationChecks();
++ return;
++ }
++
++ if (d->incrementalRefreshRequested)
++ {
++ d->incrementalRefreshRequested = false;
++ emit readyForIncrementalRefresh();
++ }
++ else
++ {
++ emit allRefreshingFinished();
++ }
++}
++
++void ImageModel::publiciseInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (infos.isEmpty())
++ {
++ return;
++ }
++
++ Q_ASSERT(infos.size() == extraValues.size() || (extraValues.isEmpty() && d->extraValues.isEmpty()));
++
++ emit imageInfosAboutToBeAdded(infos);
++ const int firstNewIndex = d->infos.size();
++ const int lastNewIndex = d->infos.size() + infos.size() - 1;
++ beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex);
++ d->infos << infos;
++ d->extraValues << extraValues;
++
++ for (int i=firstNewIndex; i<=lastNewIndex; ++i)
++ {
++ const ImageInfo& info = d->infos.at(i);
++ qlonglong id = info.id();
++ d->idHash.insertMulti(id, i);
++
++ if (d->keepFilePathCache)
++ {
++ d->filePathHash[info.filePath()] = id;
++ }
++ }
++
++ endInsertRows();
++ emit imageInfosAdded(infos);
++}
++
++void ImageModel::requestIncrementalRefresh()
++{
++ if (d->reAdding)
++ {
++ d->incrementalRefreshRequested = true;
++ }
++ else
++ {
++ emit readyForIncrementalRefresh();
++ }
++}
++
++bool ImageModel::hasIncrementalRefreshPending() const
++{
++ return d->incrementalRefreshRequested;
++}
++
++void ImageModel::startIncrementalRefresh()
++{
++ delete d->incrementalUpdater;
++
++ d->incrementalUpdater = new ImageModelIncrementalUpdater(d);
++}
++
++void ImageModel::finishIncrementalRefresh()
++{
++ if (!d->incrementalUpdater)
++ {
++ return;
++ }
++
++ // remove old entries
++ QList<QPair<int, int> > pairs = d->incrementalUpdater->oldIndexes();
++ removeRowPairs(pairs);
++
++ // add new indexes
++ appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues);
++
++ delete d->incrementalUpdater;
++ d->incrementalUpdater = 0;
++}
++
++void ImageModel::removeIndex(const QModelIndex& index)
++{
++ removeIndexes(QList<QModelIndex>() << index);
++}
++
++void ImageModel::removeIndexes(const QList<QModelIndex>& indexes)
++{
++ QList<int> listIndexes;
++
++ foreach(const QModelIndex& index, indexes)
++ {
++ if (d->isValid(index))
++ {
++ listIndexes << index.row();
++ }
++ }
++
++ if (listIndexes.isEmpty())
++ {
++ return;
++ }
++
++ removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
++}
++
++void ImageModel::removeImageInfo(const ImageInfo& info)
++{
++ removeImageInfos(QList<ImageInfo>() << info);
++}
++
++void ImageModel::removeImageInfos(const QList<ImageInfo>& infos)
++{
++ QList<int> listIndexes;
++
++ foreach(const ImageInfo& info, infos)
++ {
++ QModelIndex index = indexForImageId(info.id());
++
++ if (index.isValid())
++ {
++ listIndexes << index.row();
++ }
++ }
++ removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
++}
++
++void ImageModel::removeImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (extraValues.isEmpty())
++ {
++ removeImageInfos(infos);
++ return;
++ }
++
++ QList<int> listIndexes;
++
++ for (int i=0; i<infos.size(); ++i)
++ {
++ QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i));
++
++ if (index.isValid())
++ {
++ listIndexes << index.row();
++ }
++ }
++
++ removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
++}
++
++void ImageModel::setSendRemovalSignals(bool send)
++{
++ d->sendRemovalSignals = send;
++}
++
++template <class List, typename T>
++static bool pairsContain(const List& list, T value)
++{
++ typename List::const_iterator middle;
++ typename List::const_iterator begin = list.begin();
++ typename List::const_iterator end = list.end();
++ int n = int(end - begin);
++ int half;
++
++ while (n > 0)
++ {
++ half = n >> 1;
++ middle = begin + half;
++
++ if (middle->first <= value && middle->second >= value)
++ {
++ return true;
++ }
++ else if (middle->second < value)
++ {
++ begin = middle + 1;
++ n -= half + 1;
++ }
++ else
++ {
++ n = half;
++ }
++ }
++
++ return false;
++}
++
++void ImageModel::removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove)
++{
++ if (d->incrementalUpdater)
++ {
++ d->incrementalUpdater->aboutToBeRemovedInModel(toRemove);
++ }
++
++ removeRowPairs(toRemove);
++}
++
++void ImageModel::removeRowPairs(const QList<QPair<int, int> >& toRemove)
++{
++ if (toRemove.isEmpty())
++ {
++ return;
++ }
++
++ // Remove old indexes
++ // Keep in mind that when calling beginRemoveRows all structures announced to be removed
++ // must still be valid, and this includes our hashes as well, which limits what we can optimize
++
++ int removedRows = 0, offset = 0;
++ typedef QPair<int, int> IntPair; // to make foreach macro happy
++
++ foreach(const IntPair& pair, toRemove)
++ {
++ const int begin = pair.first - offset;
++ const int end = pair.second - offset; // inclusive
++ removedRows = end - begin + 1;
++
++ // when removing from the list, all subsequent indexes are affected
++ offset += removedRows;
++
++ QList<ImageInfo> removedInfos;
++
++ if (d->sendRemovalSignals)
++ {
++ qCopy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin());
++ emit imageInfosAboutToBeRemoved(removedInfos);
++ }
++
++ imageInfosAboutToBeRemoved(begin, end);
++ beginRemoveRows(QModelIndex(), begin, end);
++
++ // update idHash - which points to indexes of d->infos, and these change now!
++ QHash<qlonglong, int>::iterator it;
++
++ for (it = d->idHash.begin(); it != d->idHash.end(); )
++ {
++ if (it.value() >= begin)
++ {
++ if (it.value() > end)
++ {
++ // after the removed interval: adjust index
++ it.value() -= removedRows;
++ }
++ else
++ {
++ // in the removed interval
++ it = d->idHash.erase(it);
++ continue;
++ }
++ }
++
++ ++it;
++ }
++
++ // remove from list
++ d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1));
++
++ if (!d->extraValues.isEmpty())
++ {
++ d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1));
++ }
++
++ endRemoveRows();
++
++ if (d->sendRemovalSignals)
++ {
++ emit imageInfosRemoved(removedInfos);
++ }
++ }
++
++ // tidy up: remove old indexes from file path hash now
++ if (d->keepFilePathCache)
++ {
++ QHash<QString, qlonglong>::iterator it;
++
++ for (it = d->filePathHash.begin(); it != d->filePathHash.end(); )
++ {
++ if (pairsContain(toRemove, it.value()))
++ {
++ it = d->filePathHash.erase(it);
++ }
++ else
++ {
++ ++it;
++ }
++ }
++ }
++}
++
++ImageModelIncrementalUpdater::ImageModelIncrementalUpdater(ImageModel::Private* d)
++{
++ oldIds = d->idHash;
++ oldExtraValues = d->extraValues;
++}
++
++void ImageModelIncrementalUpdater::appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
++{
++ if (extraValues.isEmpty())
++ {
++ foreach(const ImageInfo& info, infos)
++ {
++ QHash<qlonglong,int>::iterator it = oldIds.find(info.id());
++
++ if (it != oldIds.end())
++ {
++ oldIds.erase(it);
++ }
++ else
++ {
++ newInfos << info;
++ }
++ }
++ }
++ else
++ {
++ for (int i=0; i<infos.size(); ++i)
++ {
++ const ImageInfo& info = infos.at(i);
++ bool found = false;
++ QHash<qlonglong,int>::iterator it;
++
++ for (it = oldIds.find(info.id()); it != oldIds.end() && it.key() == info.id(); ++it)
++ {
++ // first check is for bug #262596. Not sure if needed.
++ if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value()))
++ {
++ found = true;
++ break;
++ }
++ }
++
++ if (found)
++ {
++ oldIds.erase(it);
++ // do not erase from oldExtraValues - oldIds is a hash id -> index.
++ }
++ else
++ {
++ newInfos << info;
++ newExtraValues << extraValues.at(i);
++ }
++ }
++ }
++}
++
++void ImageModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove)
++{
++ modelRemovals << toRemove;
++}
++
++QList<QPair<int, int> > ImageModelIncrementalUpdater::oldIndexes()
++{
++ // first, apply all changes to indexes by direct removal in model
++ // while the updater was active
++ foreach(const IntPairList& list, modelRemovals)
++ {
++ int removedRows = 0, offset = 0;
++
++ foreach(const IntPair& pair, list)
++ {
++ const int begin = pair.first - offset;
++ const int end = pair.second - offset; // inclusive
++ removedRows = end - begin + 1;
++
++ // when removing from the list, all subsequent indexes are affected
++ offset += removedRows;
++
++ // update idHash - which points to indexes of d->infos, and these change now!
++ QHash<qlonglong, int>::iterator it;
++
++ for (it = oldIds.begin(); it != oldIds.end(); )
++ {
++ if (it.value() >= begin)
++ {
++ if (it.value() > end)
++ {
++ // after the removed interval: adjust index
++ it.value() -= removedRows;
++ }
++ else
++ {
++ // in the removed interval
++ it = oldIds.erase(it);
++ continue;
++ }
++ }
++
++ ++it;
++ }
++ }
++ }
++
++ modelRemovals.clear();
++
++ return toContiguousPairs(oldIds.values());
++}
++
++QList<QPair<int, int> > ImageModelIncrementalUpdater::toContiguousPairs(const QList<int>& unsorted)
++{
++ // Take the given indices and return them as contiguous pairs [begin, end]
++
++ QList<QPair<int, int> > pairs;
++
++ if (unsorted.isEmpty())
++ {
++ return pairs;
++ }
++
++ QList<int> indices(unsorted);
++ qSort(indices);
++
++ QPair<int, int> pair(indices.first(), indices.first());
++
++ for (int i=1; i<indices.size(); ++i)
++ {
++ const int &index = indices.at(i);
++
++ if (index == pair.second + 1)
++ {
++ pair.second = index;
++ continue;
++ }
++
++ pairs << pair; // insert last pair
++ pair.first = index;
++ pair.second = index;
++ }
++
++ pairs << pair;
++
++ return pairs;
++}
++
++// ------------ QAbstractItemModel implementation -------------
++
++QVariant ImageModel::data(const QModelIndex& index, int role) const
++{
++ if (!d->isValid(index))
++ {
++ return QVariant();
++ }
++
++ switch (role)
++ {
++ case Qt::DisplayRole:
++ case Qt::ToolTipRole:
++ return d->infos.at(index.row()).name();
++
++ case ImageModelPointerRole:
++ return QVariant::fromValue(const_cast<ImageModel*>(this));
++
++ case ImageModelInternalId:
++ return index.row();
++
++ case CreationDateRole:
++ return d->infos.at(index.row()).dateTime();
++
++ case ExtraDataRole:
++
++ if (d->extraValueValid(index))
++ {
++ return d->extraValues.at(index.row());
++ }
++ else
++ {
++ return QVariant();
++ }
++
++ case ExtraDataDuplicateCount:
++ {
++ qlonglong id = d->infos.at(index.row()).id();
++ return numberOfIndexesForImageId(id);
++ }
++ }
++
++ return QVariant();
++}
++
++QVariant ImageModel::headerData(int section, Qt::Orientation orientation, int role) const
++{
++ Q_UNUSED(section)
++ Q_UNUSED(orientation)
++ Q_UNUSED(role)
++ return QVariant();
++}
++
++int ImageModel::rowCount(const QModelIndex& parent) const
++{
++ if (parent.isValid())
++ {
++ return 0;
++ }
++
++ return d->infos.size();
++}
++
++Qt::ItemFlags ImageModel::flags(const QModelIndex& index) const
++{
++ if (!d->isValid(index))
++ {
++ return 0;
++ }
++
++ Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
++
++ f |= dragDropFlags(index);
++
++ return f;
++}
++
++QModelIndex ImageModel::index(int row, int column, const QModelIndex& parent) const
++{
++ if (column != 0 || row < 0 || parent.isValid() || row >= d->infos.size())
++ {
++ return QModelIndex();
++ }
++
++ return createIndex(row, 0);
++}
++
++// ------------ Database watch -------------
++
++void ImageModel::slotImageChange(const ImageChangeset& changeset)
++{
++ if (d->infos.isEmpty())
++ {
++ return;
++ }
++
++ if (d->watchFlags & changeset.changes())
++ {
++ QItemSelection items;
++
++ foreach(const qlonglong& id, changeset.ids())
++ {
++ QModelIndex index = indexForImageId(id);
++
++ if (index.isValid())
++ {
++ items.select(index, index);
++ }
++ }
++
++ if (!items.isEmpty())
++ {
++ emitDataChangedForSelection(items);
++ emit imageChange(changeset, items);
++ }
++ }
++}
++
++void ImageModel::slotImageTagChange(const ImageTagChangeset& changeset)
++{
++ if (d->infos.isEmpty())
++ {
++ return;
++ }
++
++ QItemSelection items;
++
++ foreach(const qlonglong& id, changeset.ids())
++ {
++ QModelIndex index = indexForImageId(id);
++
++ if (index.isValid())
++ {
++ items.select(index, index);
++ }
++ }
++
++ if (!items.isEmpty())
++ {
++ emitDataChangedForSelection(items);
++ emit imageTagChange(changeset, items);
++ }
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagemodel.h b/libs/database/models/imagemodel.h
+new file mode 100644
+index 0000000..dcf94c2
+--- /dev/null
++++ b/libs/database/models/imagemodel.h
+@@ -0,0 +1,364 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGEMODEL_H
++#define IMAGEMODEL_H
++
++// Qt includes
++
++#include <QAbstractListModel>
++
++// Local includes
++
++#include "dragdropimplementations.h"
++#include "imageinfo.h"
++#include "digikam_export.h"
++
++class QItemSelection;
++
++namespace Digikam
++{
++
++class ImageChangeset;
++class ImageTagChangeset;
++
++namespace DatabaseFields
++{
++class Set;
++}
++
++class DIGIKAM_DATABASE_EXPORT ImageModel : public QAbstractListModel, public DragDropModelImplementation
++{
++ Q_OBJECT
++
++public:
++
++ enum ImageModelRoles
++ {
++ /// An ImageModel* pointer to this model
++ ImageModelPointerRole = Qt::UserRole,
++ ImageModelInternalId = Qt::UserRole + 1,
++ /// Returns a thumbnail pixmap. May be implemented by subclasses.
++ /// Returns either a valid pixmap or a null QVariant.
++ ThumbnailRole = Qt::UserRole + 2,
++ /// Returns a QDateTime with the creation date
++ CreationDateRole = Qt::UserRole + 3,
++ /// Return (optional) extraData field
++ ExtraDataRole = Qt::UserRole + 5,
++ /// Returns the number of duplicate indexes for the same image id
++ ExtraDataDuplicateCount = Qt::UserRole + 6,
++
++ // Roles which are defined here but not implemented by ImageModel
++ /// Returns position of item in Left Light Table preview.
++ LTLeftPanelRole = Qt::UserRole + 50,
++ /// Returns position of item in Right Light Table preview.
++ LTRightPanelRole = Qt::UserRole + 51,
++
++ // For use by subclasses
++ SubclassRoles = Qt::UserRole + 100,
++ // For use by filter models
++ FilterModelRoles = Qt::UserRole + 500
++ };
++
++public:
++
++ explicit ImageModel(QObject* parent = 0);
++ ~ImageModel();
++
++ /** If a cache is kept, lookup by file path is fast,
++ * without a cache it is O(n). Default is false.
++ */
++ void setKeepsFilePathCache(bool keepCache);
++ bool keepsFilePathCache() const;
++
++ /** Set a set of database fields to watch.
++ * If either of these is changed, dataChanged() will be emitted.
++ * Default is no flag (no signal will be emitted).
++ */
++ void setWatchFlags(const DatabaseFields::Set& set);
++
++ /** Returns the ImageInfo object, reference or image id from the underlying data
++ * pointed to by the index.
++ * If the index is not valid, imageInfo will return a null ImageInfo, imageId will
++ * return 0, imageInfoRef must not be called with an invalid index.
++ */
++ ImageInfo imageInfo(const QModelIndex& index) const;
++ ImageInfo& imageInfoRef(const QModelIndex& index) const;
++ qlonglong imageId(const QModelIndex& index) const;
++ QList<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
++ QList<qlonglong> imageIds(const QList<QModelIndex>& indexes) const;
++
++ /** Returns the ImageInfo object, reference or image id from the underlying data
++ * of the given row (parent is the invalid QModelIndex, column is 0).
++ * Note that imageInfoRef will crash if index is invalid.
++ */
++ ImageInfo imageInfo(int row) const;
++ ImageInfo& imageInfoRef(int row) const;
++ qlonglong imageId(int row) const;
++
++ /** Return the index for the given ImageInfo or id, if contained in this model.
++ */
++ QModelIndex indexForImageInfo(const ImageInfo& info) const;
++ QModelIndex indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const;
++ QModelIndex indexForImageId(qlonglong id) const;
++ QModelIndex indexForImageId(qlonglong id, const QVariant& extraValue) const;
++ QList<QModelIndex> indexesForImageInfo(const ImageInfo& info) const;
++ QList<QModelIndex> indexesForImageId(qlonglong id) const;
++
++ int numberOfIndexesForImageInfo(const ImageInfo& info) const;
++ int numberOfIndexesForImageId(qlonglong id) const;
++
++ /** Returns the index or ImageInfo object from the underlying data
++ * for the given file path. This is fast if keepsFilePathCache is enabled.
++ * The file path is as returned by ImageInfo.filePath().
++ * In case of multiple occurrences of the same file, the simpler variants return
++ * any one found first, use the QList methods to retrieve all occurrences.
++ */
++ QModelIndex indexForPath(const QString& filePath) const;
++ ImageInfo imageInfo(const QString& filePath) const;
++ QList<QModelIndex> indexesForPath(const QString& filePath) const;
++ QList<ImageInfo> imageInfos(const QString& filePath) const;
++
++ /** Main entry point for subclasses adding image infos to the model.
++ * If you list entries not unique per image id, you must add an extraValue
++ * so that every entry is unique by imageId and extraValues.
++ * Please note that these methods do not prevent addition of duplicate entries.
++ */
++ void addImageInfo(const ImageInfo& info);
++ void addImageInfos(const QList<ImageInfo>& infos);
++ void addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++
++ /** Clears image infos and resets model.
++ */
++ void clearImageInfos();
++
++ /** Clears and adds the infos.
++ */
++ void setImageInfos(const QList<ImageInfo>& infos);
++
++ /**
++ * Directly remove the given indexes or infos from the model.
++ */
++ void removeIndex(const QModelIndex& indexes);
++ void removeIndexes(const QList<QModelIndex>& indexes);
++ void removeImageInfo(const ImageInfo& info);
++ void removeImageInfos(const QList<ImageInfo>& infos);
++ void removeImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++
++ /**
++ * addImageInfo() is asynchronous if a prepocessor is set.
++ * This method first adds the info, synchronously.
++ * Only afterwards, the preprocessor will have the opportunity to process it.
++ * This method also bypasses any incremental updates.
++ * Please note that these methods do not prevent addition of duplicate entries.
++ */
++ void addImageInfoSynchronously(const ImageInfo& info);
++ void addImageInfosSynchronously(const QList<ImageInfo>& infos);
++ void addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++
++ /**
++ * Add the given entries. Method returns immediately, the
++ * addition may happen later asynchronously.
++ * These methods prevent the addition of duplicate entries.
++ */
++ void ensureHasImageInfo(const ImageInfo& info);
++ void ensureHasImageInfos(const QList<ImageInfo>& infos);
++ void ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++
++ /**
++ * Ensure that all images grouped on the given leader are contained in the model.
++ */
++ void ensureHasGroupedImages(const ImageInfo& groupLeader);
++
++ QList<ImageInfo> imageInfos() const;
++ QList<qlonglong> imageIds() const;
++ QList<ImageInfo> uniqueImageInfos() const;
++
++ bool hasImage(qlonglong id) const;
++ bool hasImage(const ImageInfo& info) const;
++ bool hasImage(const ImageInfo& info, const QVariant& extraValue) const;
++ bool hasImage(qlonglong id, const QVariant& extraValue) const;
++
++ bool isEmpty() const;
++
++ // Drag and Drop
++ DECLARE_MODEL_DRAG_DROP_METHODS
++
++ /**
++ * Install an object as a preprocessor for ImageInfos added to this model.
++ * For every QList of ImageInfos added to addImageInfo, the signal preprocess()
++ * will be emitted. The preprocessor may process the items and shall then readd
++ * them by calling reAddImageInfos(). It may take some time to process.
++ * It shall discard any held infos when the modelReset() signal is sent.
++ * It shall call readdFinished() when no reset occurred and all infos on the way have been readded.
++ * This means that only after calling this method, you shall make three connections
++ * (preprocess -> your slot, your signal -> reAddImageInfos, your signal -> reAddingFinished)
++ * and make or already hold a connection modelReset() -> your slot.
++ * There is only one preprocessor at a time, a previously set object will be disconnected.
++ */
++ void setPreprocessor(QObject* processor);
++ void unsetPreprocessor(QObject* processor);
++
++ /**
++ * Returns true if this model is currently refreshing.
++ * For a preprocessor this means that, although the preprocessor may currently have
++ * processed all it got, more batches are to be expected.
++ */
++ bool isRefreshing() const;
++
++ /**
++ * Enable sending of imageInfosAboutToBeRemoved and imageInfosRemoved signals.
++ * Default: false
++ */
++ void setSendRemovalSignals(bool send);
++
++ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
++ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
++ virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
++ virtual Qt::ItemFlags flags(const QModelIndex& index) const;
++ virtual QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const;
++
++ /** Retrieves the imageInfo object from the data() method of the given index.
++ * The index may be from a QSortFilterProxyModel as long as an ImageModel is at the end. */
++ static ImageInfo retrieveImageInfo(const QModelIndex& index);
++ static qlonglong retrieveImageId(const QModelIndex& index);
++
++Q_SIGNALS:
++
++ /** Informs that ImageInfos will be added to the model.
++ * This signal is sent before the model data is changed and views are informed.
++ */
++ void imageInfosAboutToBeAdded(const QList<ImageInfo>& infos);
++
++ /** Informs that ImageInfos have been added to the model.
++ * This signal is sent after the model data is changed and views are informed.
++ */
++ void imageInfosAdded(const QList<ImageInfo>& infos);
++
++ /** Informs that ImageInfos will be removed from the model.
++ * This signal is sent before the model data is changed and views are informed.
++ * Note: You need to explicitly enable sending of this signal. It is not sent
++ * in clearImageInfos().
++ */
++ void imageInfosAboutToBeRemoved(const QList<ImageInfo>& infos);
++
++ /** Informs that ImageInfos have been removed from the model.
++ * This signal is sent after the model data is changed and views are informed. *
++ * Note: You need to explicitly enable sending of this signal. It is not sent
++ * in clearImageInfos().
++ */
++ void imageInfosRemoved(const QList<ImageInfo>& infos);
++
++ /** Connect to this signal only if you are the current preprocessor.
++ */
++ void preprocess(const QList<ImageInfo>& infos, const QList<QVariant>&);
++ void processAdded(const QList<ImageInfo>& infos, const QList<QVariant>&);
++
++ /** If an ImageChangeset affected indexes of this model with changes as set in watchFlags(),
++ * this signal contains the changeset and the affected indexes.
++ */
++ void imageChange(const ImageChangeset&, const QItemSelection&);
++
++ /** If an ImageTagChangeset affected indexes of this model,
++ * this signal contains the changeset and the affected indexes.
++ */
++ void imageTagChange(const ImageTagChangeset&, const QItemSelection&);
++
++ /** Signals that the model is right now ready to start an incremental refresh.
++ * This is guaranteed only for the scope of emitting this signal.
++ */
++ void readyForIncrementalRefresh();
++
++ /** Signals that the model has finished currently with all scheduled
++ * refreshing, full or incremental, and all preprocessing.
++ * The model is in polished, clean situation right now.
++ */
++ void allRefreshingFinished();
++
++public Q_SLOTS:
++
++ void reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void reAddingFinished();
++
++protected:
++
++ /** Subclasses that add ImageInfos in batches shall call startRefresh()
++ * when they start sending batches and finishRefresh() when they have finished.
++ * No incremental refreshes will be started while listing.
++ * A clearImageInfos() always stops listing, calling finishRefresh() is then not necessary.
++ */
++ void startRefresh();
++ void finishRefresh();
++
++ /** As soon as the model is ready to start an incremental refresh, the signal
++ * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline
++ * if the model is ready right now.
++ */
++ void requestIncrementalRefresh();
++ bool hasIncrementalRefreshPending() const;
++
++ /** Starts an incremental refresh operation. You shall only call this method from a slot
++ * connected to readyForIncrementalRefresh(). To initiate an incremental refresh,
++ * call requestIncrementalRefresh().
++ */
++ void startIncrementalRefresh();
++ void finishIncrementalRefresh();
++
++ void emitDataChangedForAll();
++ void emitDataChangedForSelection(const QItemSelection& selection);
++
++ // Called when the internal storage is cleared
++ virtual void imageInfosCleared() {};
++
++ // Called before rowsAboutToBeRemoved
++ virtual void imageInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {};
++
++protected Q_SLOTS:
++
++ virtual void slotImageChange(const ImageChangeset& changeset);
++ virtual void slotImageTagChange(const ImageTagChangeset& changeset);
++
++private:
++
++ void appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void publiciseInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
++ void cleanSituationChecks();
++ void removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove);
++ void removeRowPairs(const QList<QPair<int, int> >& toRemove);
++
++public:
++
++ // Declared public because it's used in ImageModelIncrementalUpdater class
++ class Private;
++
++private:
++
++ Private* const d;
++};
++
++} // namespace Digikam
++
++Q_DECLARE_METATYPE(Digikam::ImageModel*)
++
++#endif // IMAGEMODEL_H
+diff --git a/libs/database/models/imagesortsettings.cpp b/libs/database/models/imagesortsettings.cpp
+new file mode 100644
+index 0000000..39ee6e1
+--- /dev/null
++++ b/libs/database/models/imagesortsettings.cpp
+@@ -0,0 +1,400 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Filter values for use with ImageFilterModel
++ *
++ * Copyright (C) 2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagesortsettings.h"
++
++// Qt includes
++
++#include <QDateTime>
++#include <QRectF>
++
++// Local includes
++
++#include "coredbfields.h"
++#include "imageinfo.h"
++
++namespace Digikam
++{
++
++ImageSortSettings::ImageSortSettings()
++{
++ categorizationMode = NoCategories;
++ categorizationSortOrder = DefaultOrder;
++ categorizationCaseSensitivity = Qt::CaseSensitive;
++ sortRole = SortByFileName;
++ sortOrder = DefaultOrder;
++ strTypeNatural = true;
++ sortCaseSensitivity = Qt::CaseSensitive;
++ currentCategorizationSortOrder = Qt::AscendingOrder;
++ currentSortOrder = Qt::AscendingOrder;
++}
++
++bool ImageSortSettings::operator==(const ImageSortSettings& other) const
++{
++ return
++ categorizationMode == other.categorizationMode &&
++ categorizationSortOrder == other.categorizationSortOrder &&
++ categorizationCaseSensitivity == other.categorizationCaseSensitivity &&
++ sortRole == other.sortRole &&
++ sortOrder == other.sortOrder &&
++ sortCaseSensitivity == other.sortCaseSensitivity;
++}
++
++void ImageSortSettings::setCategorizationMode(CategorizationMode mode)
++{
++ categorizationMode = mode;
++
++ if (categorizationSortOrder == DefaultOrder)
++ {
++ currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode);
++ }
++}
++
++void ImageSortSettings::setCategorizationSortOrder(SortOrder order)
++{
++ categorizationSortOrder = order;
++
++ if (categorizationSortOrder == DefaultOrder)
++ {
++ currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode);
++ }
++ else
++ {
++ currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder;
++ }
++}
++
++void ImageSortSettings::setSortRole(SortRole role)
++{
++ sortRole = role;
++
++ if (sortOrder == DefaultOrder)
++ {
++ currentSortOrder = defaultSortOrderForSortRole(sortRole);
++ }
++}
++
++void ImageSortSettings::setSortOrder(SortOrder order)
++{
++ sortOrder = order;
++
++ if (sortOrder == DefaultOrder)
++ {
++ currentSortOrder = defaultSortOrderForSortRole(sortRole);
++ }
++ else
++ {
++ currentSortOrder = (Qt::SortOrder)order;
++ }
++}
++
++void ImageSortSettings::setStringTypeNatural(bool natural)
++{
++ strTypeNatural = natural;
++}
++
++Qt::SortOrder ImageSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode)
++{
++ switch (mode)
++ {
++ case NoCategories:
++ case OneCategory:
++ case CategoryByAlbum:
++ case CategoryByFormat:
++ default:
++ return Qt::AscendingOrder;
++ }
++}
++
++Qt::SortOrder ImageSortSettings::defaultSortOrderForSortRole(SortRole role)
++{
++ switch (role)
++ {
++ case SortByFileName:
++ case SortByFilePath:
++ return Qt::AscendingOrder;
++ case SortByFileSize:
++ return Qt::DescendingOrder;
++ case SortByModificationDate:
++ case SortByCreationDate:
++ return Qt::AscendingOrder;
++ case SortByRating:
++ case SortByImageSize:
++ return Qt::DescendingOrder;
++ case SortByAspectRatio:
++ return Qt::DescendingOrder;
++ case SortBySimilarity:
++ return Qt::DescendingOrder;
++ default:
++ return Qt::AscendingOrder;
++ }
++}
++
++int ImageSortSettings::compareCategories(const ImageInfo& left, const ImageInfo& right) const
++{
++ switch (categorizationMode)
++ {
++ case NoCategories:
++ case OneCategory:
++ return 0;
++ case CategoryByAlbum:
++ {
++ int leftAlbum = left.albumId();
++ int rightAlbum = right.albumId();
++
++ // return comparation result
++ if (leftAlbum == rightAlbum)
++ {
++ return 0;
++ }
++ else if (lessThanByOrder(leftAlbum, rightAlbum, currentCategorizationSortOrder))
++ {
++ return -1;
++ }
++ else
++ {
++ return 1;
++ }
++ }
++ case CategoryByFormat:
++ {
++ return naturalCompare(left.format(), right.format(),
++ currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural);
++ }
++ default:
++ return 0;
++ }
++}
++
++bool ImageSortSettings::lessThan(const ImageInfo& left, const ImageInfo& right) const
++{
++ int result = compare(left, right, sortRole);
++
++ if (result != 0)
++ {
++ return result < 0;
++ }
++
++ // are they identical?
++ if (left == right)
++ {
++ return false;
++ }
++
++ // If left and right equal for first sort order, use a hierarchy of all sort orders
++ if ( (result = compare(left, right, SortByFileName)) != 0)
++ {
++ return result < 0;
++ }
++
++ if ( (result = compare(left, right, SortByCreationDate)) != 0)
++ {
++ return result < 0;
++ }
++
++ if ( (result = compare(left, right, SortByModificationDate)) != 0)
++ {
++ return result < 0;
++ }
++
++ if ( (result = compare(left, right, SortByFilePath)) != 0)
++ {
++ return result < 0;
++ }
++
++ if ( (result = compare(left, right, SortByFileSize)) != 0)
++ {
++ return result < 0;
++ }
++
++ if ( (result = compare(left, right, SortBySimilarity)) != 0)
++ {
++ return result < 0;
++ }
++
++ return false;
++}
++
++int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right) const
++{
++ return compare(left, right, sortRole);
++}
++
++int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right, SortRole role) const
++{
++ switch (role)
++ {
++ case SortByFileName:
++ {
++ bool versioning = (left.name().contains(QLatin1String("_v"), Qt::CaseInsensitive) ||
++ right.name().contains(QLatin1String("_v"), Qt::CaseInsensitive));
++ return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural, versioning);
++ }
++ case SortByFilePath:
++ return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural);
++ case SortByFileSize:
++ return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder);
++ case SortByModificationDate:
++ return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder);
++ case SortByCreationDate:
++ return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder);
++ case SortByRating:
++ // I have the feeling that inverting the sort order for rating is the natural order
++ return - compareByOrder(left.rating(), right.rating(), currentSortOrder);
++ case SortByImageSize:
++ {
++ QSize leftSize = left.dimensions();
++ QSize rightSize = right.dimensions();
++ int leftPixels = leftSize.width() * leftSize.height();
++ int rightPixels = rightSize.width() * rightSize.height();
++ return compareByOrder(leftPixels, rightPixels, currentSortOrder);
++ }
++ case SortByAspectRatio:
++ {
++ QSize leftSize = left.dimensions();
++ QSize rightSize = right.dimensions();
++ int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000;
++ int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000;
++ return compareByOrder(leftAR, rightAR, currentSortOrder);
++ }
++ case SortBySimilarity:
++ {
++ qlonglong leftReferenceImageId = left.currentReferenceImage();
++ qlonglong rightReferenceImageId = right.currentReferenceImage();
++ // make sure that the original image has always the highest similarity.
++ double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity();
++ double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity();
++ return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder);
++ }
++ default:
++ return 1;
++ }
++}
++
++bool ImageSortSettings::lessThan(const QVariant& left, const QVariant& right) const
++{
++ if (left.type() != right.type())
++ {
++ return false;
++ }
++
++ switch (left.type())
++ {
++ case QVariant::Int:
++ return compareByOrder(left.toInt(), right.toInt(), currentSortOrder);
++ case QVariant::UInt:
++ return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder);
++ case QVariant::LongLong:
++ return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder);
++ case QVariant::ULongLong:
++ return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder);
++ case QVariant::Double:
++ return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder);
++ case QVariant::Date:
++ return compareByOrder(left.toDate(), right.toDate(), currentSortOrder);
++ case QVariant::DateTime:
++ return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder);
++ case QVariant::Time:
++ return compareByOrder(left.toTime(), right.toTime(), currentSortOrder);
++ case QVariant::Rect:
++ case QVariant::RectF:
++ {
++ QRectF rectLeft = left.toRectF();
++ QRectF rectRight = right.toRectF();
++ int result;
++
++ if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0)
++ {
++ return result < 0;
++ }
++
++ if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0)
++ {
++ return result < 0;
++ }
++
++ QSizeF sizeLeft = rectLeft.size(), sizeRight = rectRight.size();
++
++ if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0)
++ {
++ return result < 0;
++ }
++ // FIXME: fall through?? If not, add "break" here
++ }
++ default:
++ return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural);
++ }
++}
++
++DatabaseFields::Set ImageSortSettings::watchFlags() const
++{
++ DatabaseFields::Set set;
++
++ switch (sortRole)
++ {
++ case SortByFileName:
++ set |= DatabaseFields::Name;
++ break;
++ case SortByFilePath:
++ set |= DatabaseFields::Name;
++ break;
++ case SortByFileSize:
++ set |= DatabaseFields::FileSize;
++ break;
++ case SortByModificationDate:
++ set |= DatabaseFields::ModificationDate;
++ break;
++ case SortByCreationDate:
++ set |= DatabaseFields::CreationDate;
++ break;
++ case SortByRating:
++ set |= DatabaseFields::Rating;
++ break;
++ case SortByImageSize:
++ set |= DatabaseFields::Width | DatabaseFields::Height;
++ break;
++ case SortByAspectRatio:
++ set |= DatabaseFields::Width | DatabaseFields::Height;
++ break;
++ case SortBySimilarity:
++ // TODO: Not sure what to do here....
++ set |= DatabaseFields::Name;
++ break;
++ }
++
++ switch (categorizationMode)
++ {
++ case NoCategories:
++ case OneCategory:
++ case CategoryByAlbum:
++ break;
++ case CategoryByFormat:
++ set |= DatabaseFields::Format;
++ break;
++ }
++
++ return set;
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagesortsettings.h b/libs/database/models/imagesortsettings.h
+new file mode 100644
+index 0000000..2a5fd8c
+--- /dev/null
++++ b/libs/database/models/imagesortsettings.h
+@@ -0,0 +1,225 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-05-31
++ * Description : Sort settings for use with ImageFilterModel
++ *
++ * Copyright (C) 2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGESORTSETTINGS_H
++#define IMAGESORTSETTINGS_H
++
++// Qt includes
++
++#include <QHash>
++#include <QList>
++#include <QMap>
++#include <QString>
++#include <QCollator>
++
++// Local includes
++
++#include "digikam_export.h"
++
++namespace Digikam
++{
++
++class ImageInfo;
++
++namespace DatabaseFields
++{
++ class Set;
++}
++
++class DIGIKAM_DATABASE_EXPORT ImageSortSettings
++{
++public:
++
++ ImageSortSettings();
++
++ bool operator==(const ImageSortSettings& other) const;
++
++ /** Compares the categories of left and right.
++ * Return -1 if left is less than right, 0 if both fall in the same category,
++ * and 1 if left is greater than right.
++ * Adheres to set categorization mode and current category sort order.
++ */
++ int compareCategories(const ImageInfo& left, const ImageInfo& right) const;
++
++ /** Returns true if left is less than right.
++ * Adheres to current sort role and sort order.
++ */
++ bool lessThan(const ImageInfo& left, const ImageInfo& right) const;
++
++ /** Compares the ImageInfos left and right.
++ * Return -1 if left is less than right, 1 if left is greater than right,
++ * and 0 if left equals right comparing the current sort role's value.
++ * Adheres to set sort role and sort order.
++ */
++ int compare(const ImageInfo& left, const ImageInfo& right) const;
++
++ /** Returns true if left QVariant is less than right.
++ * Adheres to current sort role and sort order.
++ * Use for extraValue, if necessary.
++ */
++ bool lessThan(const QVariant& left, const QVariant& right) const;
++
++ enum SortOrder
++ {
++ AscendingOrder = Qt::AscendingOrder,
++ DescendingOrder = Qt::DescendingOrder,
++ DefaultOrder /// sort order depends on the chosen sort role
++ };
++
++ /// --- Categories ---
++
++ enum CategorizationMode
++ {
++ NoCategories, /// categorization switched off
++ OneCategory, /// all items in one global category
++ CategoryByAlbum,
++ CategoryByFormat
++ };
++
++ CategorizationMode categorizationMode;
++ SortOrder categorizationSortOrder;
++
++ void setCategorizationMode(CategorizationMode mode);
++ void setCategorizationSortOrder(SortOrder order);
++
++ /// Only Ascending or Descending, never DefaultOrder
++ Qt::SortOrder currentCategorizationSortOrder;
++ Qt::CaseSensitivity categorizationCaseSensitivity;
++
++ bool isCategorized() const { return categorizationMode >= CategoryByAlbum; }
++
++ /// --- Image Sorting ---
++
++ enum SortRole
++ {
++ // Note: For legacy reasons, the order of the first five entries must remain unchanged
++ SortByFileName,
++ SortByFilePath,
++ SortByCreationDate,
++ SortByFileSize,
++ SortByRating,
++ SortByModificationDate,
++ SortByImageSize, // pixel number
++ SortByAspectRatio, // width / height * 100000
++ SortBySimilarity
++ };
++
++ SortRole sortRole;
++ SortOrder sortOrder;
++ bool strTypeNatural;
++
++ void setSortRole(SortRole role);
++ void setSortOrder(SortOrder order);
++ void setStringTypeNatural(bool natural);
++
++ Qt::SortOrder currentSortOrder;
++ Qt::CaseSensitivity sortCaseSensitivity;
++
++ int compare(const ImageInfo& left, const ImageInfo& right, SortRole sortRole) const;
++
++ // --- ---
++
++ static Qt::SortOrder defaultSortOrderForCategorizationMode(CategorizationMode mode);
++ static Qt::SortOrder defaultSortOrderForSortRole(SortRole role);
++
++ /// --- Change notification ---
++
++ /** Returns database fields a change in which would affect the current sorting.
++ */
++ DatabaseFields::Set watchFlags() const;
++
++ /// --- Utilities ---
++
++ /** Returns a < b if sortOrder is Ascending, or b < a if order is descending.
++ */
++ template <typename T>
++ static inline bool lessThanByOrder(const T& a, const T& b, Qt::SortOrder sortOrder)
++ {
++ if (sortOrder == Qt::AscendingOrder)
++ {
++ return a < b;
++ }
++ else
++ {
++ return b < a;
++ }
++ }
++
++ /** Returns the usual compare result of -1, 0, or 1 for lessThan, equals and greaterThan.
++ */
++ template <typename T>
++ static inline int compareValue(const T& a, const T& b)
++ {
++ if (a == b)
++ {
++ return 0;
++ }
++
++ if (a < b)
++ {
++ return -1;
++ }
++ else
++ {
++ return 1;
++ }
++ }
++
++ /** Takes a typical result from a compare method (0 is equal, -1 is less than, 1 is greater than)
++ * and applies the given sort order to it.
++ */
++ static inline int compareByOrder(int compareResult, Qt::SortOrder sortOrder)
++ {
++ if (sortOrder == Qt::AscendingOrder)
++ {
++ return compareResult;
++ }
++ else
++ {
++ return - compareResult;
++ }
++ }
++
++ template <typename T>
++ static inline int compareByOrder(const T& a, const T& b, Qt::SortOrder sortOrder)
++ {
++ return compareByOrder(compareValue(a, b), sortOrder);
++ }
++
++ /** Compares the two string by natural comparison and adheres to given sort order
++ */
++ static inline int naturalCompare(const QString& a, const QString& b, Qt::SortOrder sortOrder,
++ Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive,
++ bool natural = true, bool versioning = false)
++ {
++ QCollator collator;
++ collator.setNumericMode(natural);
++ collator.setIgnorePunctuation(versioning);
++ collator.setCaseSensitivity(caseSensitive);
++ return (compareByOrder(collator.compare(a, b), sortOrder));
++ }
++};
++
++} // namespace Digikam
++
++#endif // IMAGESORTSETTINGS_H
+diff --git a/libs/database/models/imagethumbnailmodel.cpp b/libs/database/models/imagethumbnailmodel.cpp
+new file mode 100644
+index 0000000..b7f5661
+--- /dev/null
++++ b/libs/database/models/imagethumbnailmodel.cpp
+@@ -0,0 +1,323 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries with support for thumbnail loading
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imagethumbnailmodel.h"
++
++// Qt includes
++
++#include <QHash>
++
++// Local includes
++
++#include "digikam_debug.h"
++#include "thumbnailloadthread.h"
++#include "digikam_export.h"
++#include "digikam_globals.h"
++
++namespace Digikam
++{
++
++class ImageThumbnailModel::ImageThumbnailModelPriv
++{
++public:
++
++ ImageThumbnailModelPriv() :
++ thread(0),
++ preloadThread(0),
++ thumbSize(0),
++ lastGlobalThumbSize(0),
++ preloadThumbSize(0),
++ emitDataChanged(true)
++ {
++ staticListContainingThumbnailRole << ImageModel::ThumbnailRole;
++ }
++
++ ThumbnailLoadThread* thread;
++ ThumbnailLoadThread* preloadThread;
++ ThumbnailSize thumbSize;
++ ThumbnailSize lastGlobalThumbSize;
++ ThumbnailSize preloadThumbSize;
++ QRect detailRect;
++ QVector<int> staticListContainingThumbnailRole;
++
++ bool emitDataChanged;
++
++ int preloadThumbnailSize() const
++ {
++ if (preloadThumbSize.size())
++ {
++ return preloadThumbSize.size();
++ }
++
++ return thumbSize.size();
++ }
++};
++
++ImageThumbnailModel::ImageThumbnailModel(QObject* parent)
++ : ImageModel(parent), d(new ImageThumbnailModelPriv)
++{
++ setKeepsFilePathCache(true);
++}
++
++ImageThumbnailModel::~ImageThumbnailModel()
++{
++ delete d->preloadThread;
++ delete d;
++}
++
++void ImageThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread)
++{
++ d->thread = thread;
++
++ connect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)),
++ this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap)));
++}
++
++ThumbnailLoadThread* ImageThumbnailModel::thumbnailLoadThread() const
++{
++ return d->thread;
++}
++
++ThumbnailSize ImageThumbnailModel::thumbnailSize() const
++{
++ return d->thumbSize;
++}
++
++void ImageThumbnailModel::setThumbnailSize(const ThumbnailSize& size)
++{
++ d->lastGlobalThumbSize = size;
++ d->thumbSize = size;
++}
++
++void ImageThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size)
++{
++ d->preloadThumbSize = size;
++}
++
++void ImageThumbnailModel::setEmitDataChanged(bool emitSignal)
++{
++ d->emitDataChanged = emitSignal;
++}
++
++void ImageThumbnailModel::setPreloadThumbnails(bool preload)
++{
++ if (preload)
++ {
++ if (!d->preloadThread)
++ {
++ d->preloadThread = new ThumbnailLoadThread;
++ d->preloadThread->setPixmapRequested(false);
++ d->preloadThread->setPriority(QThread::LowestPriority);
++ }
++
++ connect(this, SIGNAL(allRefreshingFinished()),
++ this, SLOT(preloadAllThumbnails()));
++ }
++ else
++ {
++ delete d->preloadThread;
++ d->preloadThread = 0;
++ disconnect(this, SIGNAL(allRefreshingFinished()),
++ this, SLOT(preloadAllThumbnails()));
++ }
++}
++
++void ImageThumbnailModel::prepareThumbnails(const QList<QModelIndex>& indexesToPrepare)
++{
++ prepareThumbnails(indexesToPrepare, d->thumbSize);
++}
++
++void ImageThumbnailModel::prepareThumbnails(const QList<QModelIndex>& indexesToPrepare, const ThumbnailSize& thumbSize)
++{
++ if (!d->thread)
++ {
++ return;
++ }
++
++ QList<ThumbnailIdentifier> ids;
++ foreach(const QModelIndex& index, indexesToPrepare)
++ {
++ ids << imageInfoRef(index).thumbnailIdentifier();
++ }
++ d->thread->findGroup(ids, thumbSize.size());
++}
++
++void ImageThumbnailModel::preloadThumbnails(const QList<ImageInfo>& infos)
++{
++ if (!d->preloadThread)
++ {
++ return;
++ }
++
++ QList<ThumbnailIdentifier> ids;
++ foreach(const ImageInfo& info, infos)
++ {
++ ids << info.thumbnailIdentifier();
++ }
++ d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize());
++}
++
++void ImageThumbnailModel::preloadThumbnails(const QList<QModelIndex>& indexesToPreload)
++{
++ if (!d->preloadThread)
++ {
++ return;
++ }
++
++ QList<ThumbnailIdentifier> ids;
++ foreach(const QModelIndex& index, indexesToPreload)
++ {
++ ids << imageInfoRef(index).thumbnailIdentifier();
++ }
++ d->preloadThread->stopAllTasks();
++ d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize());
++}
++
++void ImageThumbnailModel::preloadAllThumbnails()
++{
++ preloadThumbnails(imageInfos());
++}
++
++void ImageThumbnailModel::imageInfosCleared()
++{
++ if (d->preloadThread)
++ {
++ d->preloadThread->stopAllTasks();
++ }
++}
++
++QVariant ImageThumbnailModel::data(const QModelIndex& index, int role) const
++{
++ if (role == ThumbnailRole && d->thread && index.isValid())
++ {
++ QPixmap thumbnail;
++ ImageInfo info = imageInfo(index);
++ QString path = info.filePath();
++
++ if (info.isNull())
++ {
++ return QVariant(QVariant::Pixmap);
++ }
++
++ if (!d->detailRect.isNull())
++ {
++ if (d->thread->find(info.thumbnailIdentifier(), d->detailRect, thumbnail, d->thumbSize.size()))
++ {
++ return thumbnail;
++ }
++ }
++ else
++ {
++ if (d->thread->find(info.thumbnailIdentifier(), thumbnail, d->thumbSize.size()))
++ {
++ return thumbnail;
++ }
++ }
++
++ return QVariant(QVariant::Pixmap);
++ }
++
++ return ImageModel::data(index, role);
++}
++
++bool ImageThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role)
++{
++ if (role == ThumbnailRole)
++ {
++ switch (value.type())
++ {
++ case QVariant::Invalid:
++ d->thumbSize = d->lastGlobalThumbSize;
++ d->detailRect = QRect();
++ break;
++
++ case QVariant::Int:
++
++ if (value.isNull())
++ {
++ d->thumbSize = d->lastGlobalThumbSize;
++ }
++ else
++ {
++ d->thumbSize = value.toInt();
++ }
++ break;
++
++ case QVariant::Rect:
++
++ if (value.isNull())
++ {
++ d->detailRect = QRect();
++ }
++ else
++ {
++ d->detailRect = value.toRect();
++ }
++ break;
++
++ default:
++ break;
++ }
++ }
++
++ return ImageModel::setData(index, value, role);
++}
++
++void ImageThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb)
++{
++ if (thumb.isNull())
++ {
++ return;
++ }
++
++ // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all.
++ QModelIndexList indexes;
++ ThumbnailIdentifier thumbId = loadingDescription.thumbnailIdentifier();
++ if (thumbId.filePath.isEmpty())
++ {
++ indexes = indexesForImageId(thumbId.id);
++ }
++ else
++ {
++ indexes = indexesForPath(thumbId.filePath);
++ }
++ foreach(const QModelIndex& index, indexes)
++ {
++ if (thumb.isNull())
++ {
++ emit thumbnailFailed(index, loadingDescription.previewParameters.size);
++ }
++ else
++ {
++ emit thumbnailAvailable(index, loadingDescription.previewParameters.size);
++
++ if (d->emitDataChanged)
++ {
++ emit dataChanged(index, index, d->staticListContainingThumbnailRole);
++ }
++ }
++ }
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imagethumbnailmodel.h b/libs/database/models/imagethumbnailmodel.h
+new file mode 100644
+index 0000000..366ca65
+--- /dev/null
++++ b/libs/database/models/imagethumbnailmodel.h
+@@ -0,0 +1,140 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2009-03-05
++ * Description : Qt item model for database entries with support for thumbnail loading
++ *
++ * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
++ * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGETHUMBNAILMODEL_H
++#define IMAGETHUMBNAILMODEL_H
++
++// Local includes
++
++#include "imagemodel.h"
++#include "thumbnailsize.h"
++#include "digikam_export.h"
++
++namespace Digikam
++{
++
++class LoadingDescription;
++class ThumbnailLoadThread;
++
++class DIGIKAM_DATABASE_EXPORT ImageThumbnailModel : public ImageModel
++{
++ Q_OBJECT
++
++public:
++
++ /**
++ * An ImageModel that supports thumbnail loading.
++ * You need to set a ThumbnailLoadThread to enable thumbnail loading.
++ * Adjust the thumbnail size to your needs.
++ * Note that setKeepsFilePathCache is enabled per default.
++ */
++ explicit ImageThumbnailModel(QObject* parent);
++ ~ImageThumbnailModel();
++
++ /** Enable thumbnail loading and set the thread that shall be used.
++ * The thumbnail size of this thread will be adjusted.
++ */
++ void setThumbnailLoadThread(ThumbnailLoadThread* thread);
++ ThumbnailLoadThread* thumbnailLoadThread() const;
++
++ /// Set the thumbnail size to use
++ void setThumbnailSize(const ThumbnailSize& thumbSize);
++
++ /// If you want to fix a size for preloading, do it here.
++ void setPreloadThumbnailSize(const ThumbnailSize& thumbSize);
++
++ void setExifRotate(bool rotate);
++
++ /**
++ * Enable emitting dataChanged() when a thumbnail becomes available.
++ * The thumbnailAvailable() signal will be emitted in any case.
++ * Default is true.
++ */
++ void setEmitDataChanged(bool emitSignal);
++
++ /**
++ * Enable preloading of thumbnails:
++ * If preloading is enabled, for every entry in the model a thumbnail generation is started.
++ * Default: false.
++ */
++ void setPreloadThumbnails(bool preload);
++
++ ThumbnailSize thumbnailSize() const;
++
++ /**
++ * Handles the ThumbnailRole.
++ * If the pixmap is available, returns it in the QVariant.
++ * If it still needs to be loaded, returns a null QVariant and emits
++ * thumbnailAvailable() as soon as it is available.
++ */
++ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
++
++ /**
++ * You can override the current thumbnail size by giving an integer value for ThumbnailRole.
++ * Set a null QVariant to use the thumbnail size set by setThumbnailSize() again.
++ * The index given here is ignored for this purpose.
++ */
++ virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole);
++
++public Q_SLOTS:
++
++ /** Prepare the thumbnail loading for the given indexes
++ */
++ void prepareThumbnails(const QList<QModelIndex>& indexesToPrepare);
++ void prepareThumbnails(const QList<QModelIndex>& indexesToPrepare, const ThumbnailSize& thumbSize);
++
++ /**
++ * Preload thumbnail for the given infos resp. indexes.
++ * Note: Use setPreloadThumbnails to automatically preload all entries in the model.
++ * Note: This only ensures thumbnail generation. It is not guaranteed that pixmaps
++ * are stored in the cache. For thumbnails that are expect to be drawn immediately,
++ * include them in prepareThumbnails().
++ * Note: Stops preloading of previously added thumbnails.
++ */
++ void preloadThumbnails(const QList<ImageInfo>&);
++ void preloadThumbnails(const QList<QModelIndex>&);
++ void preloadAllThumbnails();
++
++Q_SIGNALS:
++
++ void thumbnailAvailable(const QModelIndex& index, int requestedSize);
++ void thumbnailFailed(const QModelIndex& index, int requestedSize);
++
++protected:
++
++ virtual void imageInfosCleared();
++
++protected Q_SLOTS:
++
++ void slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb);
++
++private:
++
++ class ImageThumbnailModelPriv;
++ ImageThumbnailModelPriv* const d;
++};
++
++} // namespace Digikam
++
++#endif /* IMAGETHUMBNAILMODEL_H */
+diff --git a/libs/database/models/imageversionsmodel.cpp b/libs/database/models/imageversionsmodel.cpp
+new file mode 100644
+index 0000000..e6ba582
+--- /dev/null
++++ b/libs/database/models/imageversionsmodel.cpp
+@@ -0,0 +1,183 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2010-07-13
++ * Description : Model for image versions
++ *
++ * Copyright (C) 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#include "imageversionsmodel.h"
++
++// KDE includes
++
++#include <klocalizedstring.h>
++
++// Local includes
++
++#include "digikam_debug.h"
++#include "workingwidget.h"
++
++namespace Digikam
++{
++
++class ImageVersionsModel::Private
++{
++public:
++
++ Private()
++ {
++ data = 0;
++ paintTree = false;
++ }
++
++ ///Complete paths with filenames and tree level
++ QList<QPair<QString, int> >* data;
++ ///This is for delegate to paint it as selected
++ QString currentSelectedImage;
++ ///If true, the delegate will paint items as a tree
++ ///if false, it will be painted as a list
++ bool paintTree;
++};
++
++ImageVersionsModel::ImageVersionsModel(QObject* parent)
++ : QAbstractListModel(parent),
++ d(new Private)
++{
++ d->data = new QList<QPair<QString, int> >;
++}
++
++ImageVersionsModel::~ImageVersionsModel()
++{
++ //qDeleteAll(d->data);
++ delete d;
++}
++
++Qt::ItemFlags ImageVersionsModel::flags(const QModelIndex& index) const
++{
++ if (!index.isValid())
++ {
++ return 0;
++ }
++
++ return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
++}
++
++QVariant ImageVersionsModel::data(const QModelIndex& index, int role) const
++{
++ if (!index.isValid())
++ {
++ return QVariant();
++ }
++
++ if (role == Qt::DisplayRole && !d->data->isEmpty())
++ {
++ return d->data->at(index.row()).first;
++ }
++ else if (role == Qt::UserRole && !d->data->isEmpty())
++ {
++ return d->data->at(index.row()).second;
++ }
++ else if (role == Qt::DisplayRole && d->data->isEmpty())
++ {
++ //TODO: make this text Italic
++ return QVariant(QString(i18n("No image selected")));
++ }
++
++ return QVariant();
++}
++
++int ImageVersionsModel::rowCount(const QModelIndex& parent) const
++{
++ Q_UNUSED(parent)
++ return d->data->count();
++}
++
++void ImageVersionsModel::setupModelData(QList<QPair<QString, int> >& data)
++{
++ beginResetModel();
++
++ d->data->clear();
++
++ if (!data.isEmpty())
++ {
++ d->data->append(data);
++ }
++ else
++ {
++ d->data->append(qMakePair(QString(i18n("This is the original image")), 0));
++ }
++
++ endResetModel();
++}
++
++void ImageVersionsModel::clearModelData()
++{
++ beginResetModel();
++
++ if (!d->data->isEmpty())
++ {
++ d->data->clear();
++ }
++
++ endResetModel();
++}
++
++void ImageVersionsModel::slotAnimationStep()
++{
++ emit dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 1));
++}
++
++QString ImageVersionsModel::currentSelectedImage() const
++{
++ return d->currentSelectedImage;
++}
++
++void ImageVersionsModel::setCurrentSelectedImage(const QString& path)
++{
++ d->currentSelectedImage = path;
++}
++
++QModelIndex ImageVersionsModel::currentSelectedImageIndex() const
++{
++ return index(listIndexOf(d->currentSelectedImage), 0);
++}
++
++bool ImageVersionsModel::paintTree() const
++{
++ return d->paintTree;
++}
++
++void ImageVersionsModel::setPaintTree(bool paint)
++{
++ d->paintTree = paint;
++}
++
++int ImageVersionsModel::listIndexOf(const QString& item) const
++{
++ for (int i = 0; i < d->data->size(); ++i)
++ {
++ if (d->data->at(i).first == item)
++ {
++ return i;
++ }
++ }
++
++ return -1;
++}
++
++} // namespace Digikam
+diff --git a/libs/database/models/imageversionsmodel.h b/libs/database/models/imageversionsmodel.h
+new file mode 100644
+index 0000000..ed08529
+--- /dev/null
++++ b/libs/database/models/imageversionsmodel.h
+@@ -0,0 +1,75 @@
++/* ============================================================
++ *
++ * This file is a part of digiKam project
++ * http://www.digikam.org
++ *
++ * Date : 2010-07-13
++ * Description : Model for image versions
++ *
++ * Copyright (C) 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General
++ * Public License as published by the Free Software Foundation;
++ * either version 2, or (at your option)
++ * any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * ============================================================ */
++
++#ifndef IMAGEVERSIONSMODEL_H
++#define IMAGEVERSIONSMODEL_H
++
++// Qt includes
++
++#include <QModelIndex>
++#include <QPixmap>
++
++// Local includes
++
++#include "digikam_export.h"
++
++namespace Digikam
++{
++
++class DIGIKAM_DATABASE_EXPORT ImageVersionsModel : public QAbstractListModel
++{
++ Q_OBJECT
++
++public:
++
++ explicit ImageVersionsModel(QObject* parent = 0);
++ ~ImageVersionsModel();
++
++ Qt::ItemFlags flags(const QModelIndex& index) const;
++ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
++ int rowCount(const QModelIndex& parent = QModelIndex()) const;
++
++ void setupModelData(QList<QPair<QString, int> >& data);
++ void clearModelData();
++
++ QString currentSelectedImage() const;
++ void setCurrentSelectedImage(const QString& path);
++ QModelIndex currentSelectedImageIndex() const;
++
++ bool paintTree() const;
++ int listIndexOf(const QString& item) const;
++
++public Q_SLOTS:
++
++ void slotAnimationStep();
++ void setPaintTree(bool paint);
++
++private:
++
++ class Private;
++ Private* const d;
++};
++
++} // namespace Digikam
++
++#endif // IMAGEVERSIONSMODEL_H
+diff --git a/libs/models/CMakeLists.txt b/libs/models/CMakeLists.txt
+index cbabfaa..804456b 100644
+--- a/libs/models/CMakeLists.txt
++++ b/libs/models/CMakeLists.txt
+@@ -9,18 +9,6 @@ if (POLICY CMP0063)
+ cmake_policy(SET CMP0063 NEW)
+ endif (POLICY CMP0063)
+
+-set(libdatabasemodels_SRCS
+- imagemodel.cpp
+- imagefiltermodel.cpp
+- imagefiltermodelpriv.cpp
+- imagefiltermodelthreads.cpp
+- imagefiltersettings.cpp
+- imagelistmodel.cpp
+- imagesortsettings.cpp
+- imagethumbnailmodel.cpp
+- imageversionsmodel.cpp
+-)
+-
+ set(libalbummodels_SRCS
+ imagealbummodel.cpp
+ imagealbumfiltermodel.cpp
+@@ -52,5 +40,4 @@ endif()
+ #for digikam core lib
+ add_library(digikamgenericmodels_src OBJECT ${libgenericmodels_SRCS})
+
+-add_library(digikamdatabasemodels_src OBJECT ${libdatabasemodels_SRCS})
+-add_library(digikammodels_src OBJECT ${libalbummodels_SRCS} ${libgenericmodels_SRCS})
++add_library(digikammodels_src OBJECT ${libalbummodels_SRCS} ${libgenericmodels_SRCS})
+diff --git a/libs/models/imagefiltermodel.cpp b/libs/models/imagefiltermodel.cpp
+deleted file mode 100644
+index 3d57e05..0000000
+--- a/libs/models/imagefiltermodel.cpp
++++ /dev/null
+@@ -1,1116 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagefiltermodel.h"
+-#include "imagefiltermodelpriv.h"
+-#include "imagefiltermodelthreads.h"
+-
+-// Local includes
+-
+-#include "digikam_debug.h"
+-#include "coredbaccess.h"
+-#include "coredbchangesets.h"
+-#include "coredbwatch.h"
+-#include "imageinfolist.h"
+-#include "imagemodel.h"
+-
+-namespace Digikam
+-{
+-
+-ImageSortFilterModel::ImageSortFilterModel(QObject* parent)
+- : DCategorizedSortFilterProxyModel(parent), m_chainedModel(0)
+-{
+-}
+-
+-void ImageSortFilterModel::setSourceImageModel(ImageModel* source)
+-{
+- if (m_chainedModel)
+- {
+- m_chainedModel->setSourceImageModel(source);
+- }
+- else
+- {
+- setDirectSourceImageModel(source);
+- }
+-}
+-
+-void ImageSortFilterModel::setSourceFilterModel(ImageSortFilterModel* source)
+-{
+- if (source)
+- {
+- ImageModel* const model = sourceImageModel();
+-
+- if (model)
+- {
+- source->setSourceImageModel(model);
+- }
+- }
+-
+- m_chainedModel = source;
+- setSourceModel(source);
+-}
+-
+-void ImageSortFilterModel::setDirectSourceImageModel(ImageModel* model)
+-{
+- setSourceModel(model);
+-}
+-
+-void ImageSortFilterModel::setSourceModel(QAbstractItemModel* model)
+-{
+- // made it protected, only setSourceImageModel is public
+- DCategorizedSortFilterProxyModel::setSourceModel(model);
+-}
+-
+-ImageModel* ImageSortFilterModel::sourceImageModel() const
+-{
+- if (m_chainedModel)
+- {
+- return m_chainedModel->sourceImageModel();
+- }
+-
+- return static_cast<ImageModel*>(sourceModel());
+-}
+-
+-ImageSortFilterModel* ImageSortFilterModel::sourceFilterModel() const
+-{
+- return m_chainedModel;
+-}
+-
+-ImageFilterModel* ImageSortFilterModel::imageFilterModel() const
+-{
+- // reimplemented in ImageFilterModel
+- if (m_chainedModel)
+- {
+- return m_chainedModel->imageFilterModel();
+- }
+-
+- return 0;
+-}
+-
+-QModelIndex ImageSortFilterModel::mapToSourceImageModel(const QModelIndex& index) const
+-{
+- if (m_chainedModel)
+- {
+- return m_chainedModel->mapToSourceImageModel(mapToSource(index));
+- }
+-
+- return mapToSource(index);
+-}
+-
+-QModelIndex ImageSortFilterModel::mapFromSourceImageModel(const QModelIndex& albummodel_index) const
+-{
+- if (m_chainedModel)
+- {
+- return mapFromSource(m_chainedModel->mapFromSourceImageModel(albummodel_index));
+- }
+-
+- return mapFromSource(albummodel_index);
+-}
+-
+-
+-QModelIndex ImageSortFilterModel::mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const
+-{
+- if (m_chainedModel)
+- {
+- return m_chainedModel->mapToSourceImageModel(sourceModel_index);
+- }
+- return sourceModel_index;
+-}
+-
+-// -------------- Convenience mappers -------------------------------------------------------------------
+-
+-QList<QModelIndex> ImageSortFilterModel::mapListToSource(const QList<QModelIndex>& indexes) const
+-{
+- QList<QModelIndex> sourceIndexes;
+- foreach(const QModelIndex& index, indexes)
+- {
+- sourceIndexes << mapToSourceImageModel(index);
+- }
+- return sourceIndexes;
+-}
+-
+-QList<QModelIndex> ImageSortFilterModel::mapListFromSource(const QList<QModelIndex>& sourceIndexes) const
+-{
+- QList<QModelIndex> indexes;
+- foreach(const QModelIndex& index, sourceIndexes)
+- {
+- indexes << mapFromSourceImageModel(index);
+- }
+- return indexes;
+-}
+-
+-ImageInfo ImageSortFilterModel::imageInfo(const QModelIndex& index) const
+-{
+- return sourceImageModel()->imageInfo(mapToSourceImageModel(index));
+-}
+-
+-qlonglong ImageSortFilterModel::imageId(const QModelIndex& index) const
+-{
+- return sourceImageModel()->imageId(mapToSourceImageModel(index));
+-}
+-
+-QList<ImageInfo> ImageSortFilterModel::imageInfos(const QList<QModelIndex>& indexes) const
+-{
+- QList<ImageInfo> infos;
+- ImageModel* const model = sourceImageModel();
+-
+- foreach(const QModelIndex& index, indexes)
+- {
+- infos << model->imageInfo(mapToSourceImageModel(index));
+- }
+-
+- return infos;
+-}
+-
+-QList<qlonglong> ImageSortFilterModel::imageIds(const QList<QModelIndex>& indexes) const
+-{
+- QList<qlonglong> ids;
+- ImageModel* const model = sourceImageModel();
+-
+- foreach(const QModelIndex& index, indexes)
+- {
+- ids << model->imageId(mapToSourceImageModel(index));
+- }
+-
+- return ids;
+-}
+-
+-QModelIndex ImageSortFilterModel::indexForPath(const QString& filePath) const
+-{
+- return mapFromSourceImageModel(sourceImageModel()->indexForPath(filePath));
+-}
+-
+-QModelIndex ImageSortFilterModel::indexForImageInfo(const ImageInfo& info) const
+-{
+- return mapFromSourceImageModel(sourceImageModel()->indexForImageInfo(info));
+-}
+-
+-QModelIndex ImageSortFilterModel::indexForImageId(qlonglong id) const
+-{
+- return mapFromSourceImageModel(sourceImageModel()->indexForImageId(id));
+-}
+-
+-QList<ImageInfo> ImageSortFilterModel::imageInfosSorted() const
+-{
+- QList<ImageInfo> infos;
+- const int size = rowCount();
+- ImageModel* const model = sourceImageModel();
+-
+- for (int i=0; i<size; ++i)
+- {
+- infos << model->imageInfo(mapToSourceImageModel(index(i, 0)));
+- }
+-
+- return infos;
+-}
+-
+-// --------------------------------------------------------------------------------------------
+-
+-ImageFilterModel::ImageFilterModel(QObject* parent)
+- : ImageSortFilterModel(parent),
+- d_ptr(new ImageFilterModelPrivate)
+-{
+- d_ptr->init(this);
+-}
+-
+-ImageFilterModel::ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent)
+- : ImageSortFilterModel(parent),
+- d_ptr(&dd)
+-{
+- d_ptr->init(this);
+-}
+-
+-ImageFilterModel::~ImageFilterModel()
+-{
+- Q_D(ImageFilterModel);
+- delete d;
+-}
+-
+-void ImageFilterModel::setDirectSourceImageModel(ImageModel* sourceModel)
+-{
+- Q_D(ImageFilterModel);
+-
+- if (d->imageModel)
+- {
+- d->imageModel->unsetPreprocessor(d);
+- disconnect(d->imageModel, SIGNAL(modelReset()),
+- this, SLOT(slotModelReset()));
+- slotModelReset();
+- }
+-
+- d->imageModel = sourceModel;
+-
+- if (d->imageModel)
+- {
+- d->imageModel->setPreprocessor(d);
+-
+- connect(d->imageModel, SIGNAL(preprocess(QList<ImageInfo>,QList<QVariant>)),
+- d, SLOT(preprocessInfos(QList<ImageInfo>,QList<QVariant>)));
+-
+- connect(d->imageModel, SIGNAL(processAdded(QList<ImageInfo>,QList<QVariant>)),
+- d, SLOT(processAddedInfos(QList<ImageInfo>,QList<QVariant>)));
+-
+- connect(d, SIGNAL(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)),
+- d->imageModel, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
+-
+- connect(d, SIGNAL(reAddingFinished()),
+- d->imageModel, SLOT(reAddingFinished()));
+-
+- connect(d->imageModel, SIGNAL(modelReset()),
+- this, SLOT(slotModelReset()));
+-
+- connect(d->imageModel, SIGNAL(imageChange(ImageChangeset,QItemSelection)),
+- this, SLOT(slotImageChange(ImageChangeset)));
+-
+- connect(d->imageModel, SIGNAL(imageTagChange(ImageTagChangeset,QItemSelection)),
+- this, SLOT(slotImageTagChange(ImageTagChangeset)));
+- }
+-
+- setSourceModel(d->imageModel);
+-}
+-
+-QVariant ImageFilterModel::data(const QModelIndex& index, int role) const
+-{
+- Q_D(const ImageFilterModel);
+-
+- if (!index.isValid())
+- {
+- return QVariant();
+- }
+-
+- switch (role)
+- {
+- // Attention: This breaks should there ever be another filter model between this and the ImageModel
+-
+- case DCategorizedSortFilterProxyModel::CategoryDisplayRole:
+- return categoryIdentifier(d->imageModel->imageInfoRef(mapToSource(index)));
+- case CategorizationModeRole:
+- return d->sorter.categorizationMode;
+- case SortOrderRole:
+- return d->sorter.sortRole;
+- //case CategoryCountRole:
+- // return categoryCount(d->imageModel->imageInfoRef(mapToSource(index)));
+- case CategoryAlbumIdRole:
+- return d->imageModel->imageInfoRef(mapToSource(index)).albumId();
+- case CategoryFormatRole:
+- return d->imageModel->imageInfoRef(mapToSource(index)).format();
+- case GroupIsOpenRole:
+- return d->groupFilter.isAllOpen() ||
+- d->groupFilter.isOpen(d->imageModel->imageInfoRef(mapToSource(index)).id());
+- case ImageFilterModelPointerRole:
+- return QVariant::fromValue(const_cast<ImageFilterModel*>(this));
+- }
+-
+- return DCategorizedSortFilterProxyModel::data(index, role);
+-}
+-
+-ImageFilterModel* ImageFilterModel::imageFilterModel() const
+-{
+- return const_cast<ImageFilterModel*>(this);
+-}
+-
+-DatabaseFields::Set ImageFilterModel::suggestedWatchFlags() const
+-{
+- DatabaseFields::Set watchFlags;
+- watchFlags |= DatabaseFields::Name | DatabaseFields::FileSize | DatabaseFields::ModificationDate;
+- watchFlags |= DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::Orientation |
+- DatabaseFields::Width | DatabaseFields::Height;
+- watchFlags |= DatabaseFields::Comment;
+- watchFlags |= DatabaseFields::ImageRelations;
+- return watchFlags;
+-}
+-
+-// -------------- Filter settings --------------
+-
+-void ImageFilterModel::setDayFilter(const QList<QDateTime>& days)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setDayFilter(days);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
+- ImageFilterSettings::MatchingCondition matchingCond,
+- bool showUnTagged, const QList<int>& clTagIds, const QList<int>& plTagIds)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setTagFilter(includedTags, excludedTags, matchingCond, showUnTagged, clTagIds, plTagIds);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setRatingFilter(rating, ratingCond, isUnratedExcluded);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setUrlWhitelist(const QList<QUrl> urlList, const QString& id)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setUrlWhitelist(urlList, id);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setIdWhitelist(const QList<qlonglong>& idList, const QString& id)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setIdWhitelist(idList, id);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setMimeTypeFilter(int mimeTypeFilter)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setMimeTypeFilter(mimeTypeFilter);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setGeolocationFilter(condition);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setTextFilter(const SearchTextFilterSettings& settings)
+-{
+- Q_D(ImageFilterModel);
+- d->filter.setTextFilter(settings);
+- setImageFilterSettings(d->filter);
+-}
+-
+-void ImageFilterModel::setImageFilterSettings(const ImageFilterSettings& settings)
+-{
+- Q_D(ImageFilterModel);
+-
+- {
+- QMutexLocker lock(&d->mutex);
+- d->version++;
+- d->filter = settings;
+- d->filterCopy = settings;
+- d->versionFilterCopy = d->versionFilter;
+- d->groupFilterCopy = d->groupFilter;
+-
+- d->needPrepareComments = settings.isFilteringByText();
+- d->needPrepareTags = settings.isFilteringByTags();
+- d->needPrepareGroups = true;
+- d->needPrepare = d->needPrepareComments || d->needPrepareTags || d->needPrepareGroups;
+-
+- d->hasOneMatch = false;
+- d->hasOneMatchForText = false;
+- }
+-
+- d->filterResults.clear();
+-
+- //d->categoryCountHashInt.clear();
+- //d->categoryCountHashString.clear();
+- if (d->imageModel)
+- {
+- d->infosToProcess(d->imageModel->imageInfos());
+- }
+-
+- emit filterSettingsChanged(settings);
+-}
+-
+-void ImageFilterModel::setVersionManagerSettings(const VersionManagerSettings& settings)
+-{
+- Q_D(ImageFilterModel);
+- d->versionFilter.setVersionManagerSettings(settings);
+- setVersionImageFilterSettings(d->versionFilter);
+-}
+-
+-void ImageFilterModel::setExceptionList(const QList<qlonglong>& idList, const QString& id)
+-{
+- Q_D(ImageFilterModel);
+- d->versionFilter.setExceptionList(idList, id);
+- setVersionImageFilterSettings(d->versionFilter);
+-}
+-
+-void ImageFilterModel::setVersionImageFilterSettings(const VersionImageFilterSettings& settings)
+-{
+- Q_D(ImageFilterModel);
+- d->versionFilter = settings;
+- slotUpdateFilter();
+-}
+-
+-bool ImageFilterModel::isGroupOpen(qlonglong group) const
+-{
+- Q_D(const ImageFilterModel);
+- return d->groupFilter.isOpen(group);
+-}
+-
+-bool ImageFilterModel::isAllGroupsOpen() const
+-{
+- Q_D(const ImageFilterModel);
+- return d->groupFilter.isAllOpen();
+-}
+-
+-void ImageFilterModel::setGroupOpen(qlonglong group, bool open)
+-{
+- Q_D(ImageFilterModel);
+- d->groupFilter.setOpen(group, open);
+- setGroupImageFilterSettings(d->groupFilter);
+-}
+-
+-void ImageFilterModel::toggleGroupOpen(qlonglong group)
+-{
+- setGroupOpen(group, !isGroupOpen(group));
+-}
+-
+-void ImageFilterModel::setAllGroupsOpen(bool open)
+-{
+- Q_D(ImageFilterModel);
+- d->groupFilter.setAllOpen(open);
+- setGroupImageFilterSettings(d->groupFilter);
+-}
+-
+-void ImageFilterModel::setGroupImageFilterSettings(const GroupImageFilterSettings& settings)
+-{
+- Q_D(ImageFilterModel);
+- d->groupFilter = settings;
+- slotUpdateFilter();
+-}
+-
+-void ImageFilterModel::slotUpdateFilter()
+-{
+- Q_D(ImageFilterModel);
+- setImageFilterSettings(d->filter);
+-}
+-
+-ImageFilterSettings ImageFilterModel::imageFilterSettings() const
+-{
+- Q_D(const ImageFilterModel);
+- return d->filter;
+-}
+-
+-ImageSortSettings ImageFilterModel::imageSortSettings() const
+-{
+- Q_D(const ImageFilterModel);
+- return d->sorter;
+-}
+-
+-VersionImageFilterSettings ImageFilterModel::versionImageFilterSettings() const
+-{
+- Q_D(const ImageFilterModel);
+- return d->versionFilter;
+-}
+-
+-GroupImageFilterSettings ImageFilterModel::groupImageFilterSettings() const
+-{
+- Q_D(const ImageFilterModel);
+- return d->groupFilter;
+-}
+-
+-void ImageFilterModel::slotModelReset()
+-{
+- Q_D(ImageFilterModel);
+- {
+- QMutexLocker lock(&d->mutex);
+- // discard all packages on the way that are marked as send out for re-add
+- d->lastDiscardVersion = d->version;
+- d->sentOutForReAdd = 0;
+- // discard all packages on the way
+- d->version++;
+- d->sentOut = 0;
+-
+- d->hasOneMatch = false;
+- d->hasOneMatchForText = false;
+- }
+- d->filterResults.clear();
+-}
+-
+-bool ImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
+-{
+- Q_D(const ImageFilterModel);
+-
+- if (source_parent.isValid())
+- {
+- return false;
+- }
+-
+- qlonglong id = d->imageModel->imageId(source_row);
+- QHash<qlonglong, bool>::const_iterator it = d->filterResults.constFind(id);
+-
+- if (it != d->filterResults.constEnd())
+- {
+- return it.value();
+- }
+-
+- // usually done in thread and cache, unless source model changed
+- ImageInfo info = d->imageModel->imageInfo(source_row);
+- bool match = d->filter.matches(info);
+- match = match ? d->versionFilter.matches(info) : false;
+-
+- return match ? d->groupFilter.matches(info) : false;
+-}
+-
+-void ImageFilterModel::setSendImageInfoSignals(bool sendSignals)
+-{
+- if (sendSignals)
+- {
+- connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
+- this, SLOT(slotRowsInserted(QModelIndex,int,int)));
+-
+- connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+- this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
+- }
+- else
+- {
+- disconnect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
+- this, SLOT(slotRowsInserted(QModelIndex,int,int)));
+-
+- disconnect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+- this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
+- }
+-}
+-
+-void ImageFilterModel::slotRowsInserted(const QModelIndex& /*parent*/, int start, int end)
+-{
+- QList<ImageInfo> infos;
+-
+- for (int i=start; i<=end; ++i)
+- {
+- infos << imageInfo(index(i, 0));
+- }
+-
+- emit imageInfosAdded(infos);
+-}
+-
+-void ImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end)
+-{
+- QList<ImageInfo> infos;
+-
+- for (int i=start; i<=end; ++i)
+- {
+- infos << imageInfo(index(i, 0));
+- }
+-
+- emit imageInfosAboutToBeRemoved(infos);
+-}
+-
+-// -------------- Threaded preparation & filtering --------------
+-
+-void ImageFilterModel::addPrepareHook(ImageFilterModelPrepareHook* hook)
+-{
+- Q_D(ImageFilterModel);
+- QMutexLocker lock(&d->mutex);
+- d->prepareHooks << hook;
+-}
+-
+-void ImageFilterModel::removePrepareHook(ImageFilterModelPrepareHook* hook)
+-{
+- Q_D(ImageFilterModel);
+- QMutexLocker lock(&d->mutex);
+- d->prepareHooks.removeAll(hook);
+-}
+-
+-void ImageFilterModelPreparer::process(ImageFilterModelTodoPackage package)
+-{
+- if (!checkVersion(package))
+- {
+- emit discarded(package);
+- return;
+- }
+-
+- // get thread-local copy
+- bool needPrepareTags, needPrepareComments, needPrepareGroups;
+- QList<ImageFilterModelPrepareHook*> prepareHooks;
+- {
+- QMutexLocker lock(&d->mutex);
+- needPrepareTags = d->needPrepareTags;
+- needPrepareComments = d->needPrepareComments;
+- needPrepareGroups = d->needPrepareGroups;
+- prepareHooks = d->prepareHooks;
+- }
+-
+- //TODO: Make efficient!!
+- if (needPrepareComments)
+- {
+- foreach(const ImageInfo& info, package.infos)
+- {
+- info.comment();
+- }
+- }
+-
+- if (!checkVersion(package))
+- {
+- emit discarded(package);
+- return;
+- }
+-
+- // The downside of QVector: At some point, we may need a QList for an API.
+- // Nonetheless, QList and ImageInfo is fast. We could as well
+- // reimplement ImageInfoList to ImageInfoVector (internally with templates?)
+- ImageInfoList infoList;
+-
+- if (needPrepareTags || needPrepareGroups)
+- {
+- infoList = package.infos.toList();
+- }
+-
+- if (needPrepareTags)
+- {
+- infoList.loadTagIds();
+- }
+-
+- if (needPrepareGroups)
+- {
+- infoList.loadGroupImageIds();
+- }
+-
+- foreach(ImageFilterModelPrepareHook* hook, prepareHooks)
+- {
+- hook->prepare(package.infos);
+- }
+-
+- emit processed(package);
+-}
+-
+-void ImageFilterModelFilterer::process(ImageFilterModelTodoPackage package)
+-{
+- if (!checkVersion(package))
+- {
+- emit discarded(package);
+- return;
+- }
+-
+- // get thread-local copy
+- ImageFilterSettings localFilter;
+- VersionImageFilterSettings localVersionFilter;
+- GroupImageFilterSettings localGroupFilter;
+- bool hasOneMatch;
+- bool hasOneMatchForText;
+- {
+- QMutexLocker lock(&d->mutex);
+- localFilter = d->filterCopy;
+- localVersionFilter = d->versionFilterCopy;
+- localGroupFilter = d->groupFilterCopy;
+- hasOneMatch = d->hasOneMatch;
+- hasOneMatchForText = d->hasOneMatchForText;
+- }
+-
+- // Actual filtering. The variants to spare checking hasOneMatch over and over again.
+- if (hasOneMatch && hasOneMatchForText)
+- {
+- foreach(const ImageInfo& info, package.infos)
+- {
+- package.filterResults[info.id()] = localFilter.matches(info) &&
+- localVersionFilter.matches(info) &&
+- localGroupFilter.matches(info);
+- }
+- }
+- else if (hasOneMatch)
+- {
+- bool matchForText;
+-
+- foreach(const ImageInfo& info, package.infos)
+- {
+- package.filterResults[info.id()] = localFilter.matches(info, &matchForText) &&
+- localVersionFilter.matches(info) &&
+- localGroupFilter.matches(info);
+-
+- if (matchForText)
+- {
+- hasOneMatchForText = true;
+- }
+- }
+- }
+- else
+- {
+- bool result, matchForText;
+-
+- foreach(const ImageInfo& info, package.infos)
+- {
+- result = localFilter.matches(info, &matchForText) &&
+- localVersionFilter.matches(info) &&
+- localGroupFilter.matches(info);
+- package.filterResults[info.id()] = result;
+-
+- if (result)
+- {
+- hasOneMatch = true;
+- }
+-
+- if (matchForText)
+- {
+- hasOneMatchForText = true;
+- }
+- }
+- }
+-
+- if (checkVersion(package))
+- {
+- QMutexLocker lock(&d->mutex);
+- d->hasOneMatch = hasOneMatch;
+- d->hasOneMatchForText = hasOneMatchForText;
+- }
+-
+- emit processed(package);
+-}
+-
+-// -------------- Sorting and Categorization -------------------------------------------------------
+-
+-void ImageFilterModel::setImageSortSettings(const ImageSortSettings& sorter)
+-{
+- Q_D(ImageFilterModel);
+- d->sorter = sorter;
+- setCategorizedModel(d->sorter.categorizationMode != ImageSortSettings::NoCategories);
+- invalidate();
+-}
+-
+-void ImageFilterModel::setCategorizationMode(ImageSortSettings::CategorizationMode mode)
+-{
+- Q_D(ImageFilterModel);
+- d->sorter.setCategorizationMode(mode);
+- setImageSortSettings(d->sorter);
+-}
+-
+-void ImageFilterModel::setCategorizationSortOrder(ImageSortSettings::SortOrder order)
+-{
+- Q_D(ImageFilterModel);
+- d->sorter.setCategorizationSortOrder(order);
+- setImageSortSettings(d->sorter);
+-}
+-
+-void ImageFilterModel::setSortRole(ImageSortSettings::SortRole role)
+-{
+- Q_D(ImageFilterModel);
+- d->sorter.setSortRole(role);
+- setImageSortSettings(d->sorter);
+-}
+-
+-void ImageFilterModel::setSortOrder(ImageSortSettings::SortOrder order)
+-{
+- Q_D(ImageFilterModel);
+- d->sorter.setSortOrder(order);
+- setImageSortSettings(d->sorter);
+-}
+-
+-void ImageFilterModel::setStringTypeNatural(bool natural)
+-{
+- Q_D(ImageFilterModel);
+- d->sorter.setStringTypeNatural(natural);
+- setImageSortSettings(d->sorter);
+-}
+-
+-int ImageFilterModel::compareCategories(const QModelIndex& left, const QModelIndex& right) const
+-{
+- // source indexes
+- Q_D(const ImageFilterModel);
+-
+- if (!d->sorter.isCategorized())
+- {
+- return 0;
+- }
+-
+- if (!left.isValid() || !right.isValid())
+- {
+- return -1;
+- }
+-
+- const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left);
+- const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right);
+-
+- // Check grouping
+- qlonglong leftGroupImageId = leftInfo.groupImageId();
+- qlonglong rightGroupImageId = rightInfo.groupImageId();
+-
+- return compareInfosCategories(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId),
+- rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId));
+-}
+-
+-bool ImageFilterModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const
+-{
+- // source indexes
+- Q_D(const ImageFilterModel);
+-
+- if (!left.isValid() || !right.isValid())
+- {
+- return true;
+- }
+-
+- if (left == right)
+- {
+- return false;
+- }
+-
+- const ImageInfo& leftInfo = d->imageModel->imageInfoRef(left);
+- const ImageInfo& rightInfo = d->imageModel->imageInfoRef(right);
+-
+- if (leftInfo == rightInfo)
+- {
+- return d->sorter.lessThan(left.data(ImageModel::ExtraDataRole), right.data(ImageModel::ExtraDataRole));
+- }
+-
+- // Check grouping
+- qlonglong leftGroupImageId = leftInfo.groupImageId();
+- qlonglong rightGroupImageId = rightInfo.groupImageId();
+-
+- // Either no grouping (-1), or same group image, or same image
+- if (leftGroupImageId == rightGroupImageId)
+- {
+- return infosLessThan(leftInfo, rightInfo);
+- }
+-
+- // We have grouping to handle
+-
+- // Is one grouped on the other? Sort behind leader.
+- if (leftGroupImageId == rightInfo.id())
+- {
+- return false;
+- }
+- if (rightGroupImageId == leftInfo.id())
+- {
+- return true;
+- }
+-
+- // Use the group leader for sorting
+- return infosLessThan(leftGroupImageId == -1 ? leftInfo : ImageInfo(leftGroupImageId),
+- rightGroupImageId == -1 ? rightInfo : ImageInfo(rightGroupImageId));
+-}
+-
+-int ImageFilterModel::compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const
+-{
+- // Note: reimplemented in ImageAlbumFilterModel
+- Q_D(const ImageFilterModel);
+- return d->sorter.compareCategories(left, right);
+-}
+-
+-// Feel free to optimize. QString::number is 3x slower.
+-static inline QString fastNumberToString(int id)
+-{
+- const int size = sizeof(int) * 2;
+- char c[size+1];
+- c[size] = '\0';
+- char* p = c;
+- int number = id;
+-
+- for (int i=0; i<size; ++i)
+- {
+- *p = 'a' + (number & 0xF);
+- number >>= 4;
+- ++p;
+- }
+-
+- return QString::fromLatin1(c);
+-}
+-
+-QString ImageFilterModel::categoryIdentifier(const ImageInfo& i) const
+-{
+- Q_D(const ImageFilterModel);
+-
+- if (!d->sorter.isCategorized())
+- {
+- return QString();
+- }
+-
+- qlonglong groupedImageId = i.groupImageId();
+- ImageInfo info = groupedImageId == -1 ? i : ImageInfo(groupedImageId);
+-
+- switch (d->sorter.categorizationMode)
+- {
+- case ImageSortSettings::NoCategories:
+- return QString();
+- case ImageSortSettings::OneCategory:
+- return QString();
+- case ImageSortSettings::CategoryByAlbum:
+- return fastNumberToString(info.albumId());
+- case ImageSortSettings::CategoryByFormat:
+- return info.format();
+- default:
+- return QString();
+- }
+-}
+-
+-bool ImageFilterModel::infosLessThan(const ImageInfo& left, const ImageInfo& right) const
+-{
+- Q_D(const ImageFilterModel);
+- return d->sorter.lessThan(left, right);
+-}
+-
+-// -------------- Watching changes -----------------------------------------------------------------
+-
+-void ImageFilterModel::slotImageTagChange(const ImageTagChangeset& changeset)
+-{
+- Q_D(ImageFilterModel);
+-
+- if (!d->imageModel || d->imageModel->isEmpty())
+- {
+- return;
+- }
+-
+- // already scheduled to re-filter?
+- if (d->updateFilterTimer->isActive())
+- {
+- return;
+- }
+-
+- // do we filter at all?
+- if (!d->versionFilter.isFilteringByTags() &&
+- !d->filter.isFilteringByTags() &&
+- !d->filter.isFilteringByText())
+- {
+- return;
+- }
+-
+- // is one of our images affected?
+- foreach(const qlonglong& id, changeset.ids())
+- {
+- // if one matching image id is found, trigger a refresh
+- if (d->imageModel->hasImage(id))
+- {
+- d->updateFilterTimer->start();
+- return;
+- }
+- }
+-}
+-
+-void ImageFilterModel::slotImageChange(const ImageChangeset& changeset)
+-{
+- Q_D(ImageFilterModel);
+-
+- if (!d->imageModel || d->imageModel->isEmpty())
+- {
+- return;
+- }
+-
+- // already scheduled to re-filter?
+- if (d->updateFilterTimer->isActive())
+- {
+- return;
+- }
+-
+- // is one of the values affected that we filter or sort by?
+- DatabaseFields::Set set = changeset.changes();
+- bool sortAffected = (set & d->sorter.watchFlags());
+- bool filterAffected = (set & d->filter.watchFlags()) || (set & d->groupFilter.watchFlags());
+-
+- if (!sortAffected && !filterAffected)
+- {
+- return;
+- }
+-
+- // is one of our images affected?
+- bool imageAffected = false;
+-
+- foreach(const qlonglong& id, changeset.ids())
+- {
+- // if one matching image id is found, trigger a refresh
+- if (d->imageModel->hasImage(id))
+- {
+- imageAffected = true;
+- break;
+- }
+- }
+-
+- if (!imageAffected)
+- {
+- return;
+- }
+-
+- if (filterAffected)
+- {
+- d->updateFilterTimer->start();
+- }
+- else
+- {
+- invalidate(); // just resort, reuse filter results
+- }
+-}
+-
+-// -------------------------------------------------------------------------------------------------------
+-
+-NoDuplicatesImageFilterModel::NoDuplicatesImageFilterModel(QObject* parent)
+- : ImageSortFilterModel(parent)
+-{
+-}
+-
+-bool NoDuplicatesImageFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
+-{
+- QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
+-
+- if (index.data(ImageModel::ExtraDataDuplicateCount).toInt() <= 1)
+- {
+- return true;
+- }
+-
+- QModelIndex previousIndex = sourceModel()->index(source_row - 1, 0, source_parent);
+-
+- if (!previousIndex.isValid())
+- {
+- return true;
+- }
+-
+- if (sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(index)) == sourceImageModel()->imageId(mapFromDirectSourceToSourceImageModel(previousIndex)))
+- {
+- return false;
+- }
+- return true;
+-}
+-
+-/*
+-void NoDuplicatesImageFilterModel::setSourceModel(QAbstractItemModel* model)
+-{
+- if (sourceModel())
+- {
+- }
+-
+- ImageSortFilterModel::setSourceModel(model);
+-
+- if (sourceModel())
+- {
+- connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
+- this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int)));
+- }
+-}
+-
+-void NoDuplicatesImageFilterModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int begin, int end)
+-{
+- bool needInvalidate = false;
+-
+- for (int i = begin; i<=end; ++i)
+- {
+- QModelIndex index = sourceModel()->index(i, 0, parent);
+-
+- // filtered out by us?
+- if (!mapFromSource(index).isValid())
+- {
+- continue;
+- }
+-
+- QModelIndex sourceIndex = mapFromDirectSourceToSourceImageModel(index);
+- qlonglong id = sourceImageModel()->imageId(sourceIndex);
+-
+- if (sourceImageModel()->numberOfIndexesForImageId(id) > 1)
+- {
+- needInvalidate = true;
+- }
+- }
+-}*/
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagefiltermodel.h b/libs/models/imagefiltermodel.h
+deleted file mode 100644
+index d131b3e..0000000
+--- a/libs/models/imagefiltermodel.h
++++ /dev/null
+@@ -1,299 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGEFILTERMODEL_H
+-#define IMAGEFILTERMODEL_H
+-
+-// Local includes
+-
+-#include "dcategorizedsortfilterproxymodel.h"
+-#include "textfilter.h"
+-#include "imagefiltersettings.h"
+-#include "imagemodel.h"
+-#include "imagesortsettings.h"
+-#include "digikam_export.h"
+-
+-namespace Digikam
+-{
+-
+-class ImageChangeset;
+-class ImageFilterModel;
+-class ImageTagChangeset;
+-
+-class DIGIKAM_DATABASE_EXPORT ImageFilterModelPrepareHook
+-{
+-public:
+-
+- virtual ~ImageFilterModelPrepareHook() {};
+- virtual void prepare(const QVector<ImageInfo>& infos) = 0;
+-};
+-
+-// -----------------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT ImageSortFilterModel : public DCategorizedSortFilterProxyModel
+-{
+- Q_OBJECT
+-
+-public:
+-
+- explicit ImageSortFilterModel(QObject* parent = 0);
+-
+- void setSourceImageModel(ImageModel* model);
+- ImageModel* sourceImageModel() const;
+-
+- void setSourceFilterModel(ImageSortFilterModel* model);
+- ImageSortFilterModel* sourceFilterModel() const;
+-
+- QModelIndex mapToSourceImageModel(const QModelIndex& index) const;
+- QModelIndex mapFromSourceImageModel(const QModelIndex& imagemodel_index) const;
+- QModelIndex mapFromDirectSourceToSourceImageModel(const QModelIndex& sourceModel_index) const;
+-
+- /// Convenience methods mapped to ImageModel.
+- /// Mentioned indexes returned come from the source image model.
+- QList<QModelIndex> mapListToSource(const QList<QModelIndex>& indexes) const;
+- QList<QModelIndex> mapListFromSource(const QList<QModelIndex>& sourceIndexes) const;
+-
+- ImageInfo imageInfo(const QModelIndex& index) const;
+- qlonglong imageId(const QModelIndex& index) const;
+- QList<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
+- QList<qlonglong> imageIds(const QList<QModelIndex>& indexes) const;
+-
+- QModelIndex indexForPath(const QString& filePath) const;
+- QModelIndex indexForImageInfo(const ImageInfo& info) const;
+- QModelIndex indexForImageId(qlonglong id) const;
+-
+- /** Returns a list of all image infos, sorted according to this model.
+- * If you do not need a sorted list, use ImageModel's imageInfos() method.
+- */
+- QList<ImageInfo> imageInfosSorted() const;
+-
+- /// Returns this, any chained ImageFilterModel, or 0.
+- virtual ImageFilterModel* imageFilterModel() const;
+-
+-protected:
+-
+- /// Reimplement if needed. Called only when model shall be set as (direct) sourceModel.
+- virtual void setDirectSourceImageModel(ImageModel* model);
+-
+- // made protected
+- virtual void setSourceModel(QAbstractItemModel* model);
+-
+-protected:
+-
+- ImageSortFilterModel* m_chainedModel;
+-};
+-
+-// -----------------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT ImageFilterModel : public ImageSortFilterModel
+-{
+- Q_OBJECT
+-
+-public:
+-
+- enum ImageFilterModelRoles
+- {
+- /// Returns the current categorization mode
+- CategorizationModeRole = ImageModel::FilterModelRoles + 1,
+- /// Returns the current sort order
+- SortOrderRole = ImageModel::FilterModelRoles + 2,
+- // / Returns the number of items in the index' category
+- //CategoryCountRole = ImageModel::FilterModelRoles + 3,
+- /// Returns the id of the PAlbum of the index which is used for category
+- CategoryAlbumIdRole = ImageModel::FilterModelRoles + 3,
+- /// Returns the format of the index which is used for category
+- CategoryFormatRole = ImageModel::FilterModelRoles + 4,
+- /// Returns true if the given image is a group leader, and the group is opened
+- GroupIsOpenRole = ImageModel::FilterModelRoles + 5,
+- ImageFilterModelPointerRole = ImageModel::FilterModelRoles + 50
+- };
+-
+-public:
+-
+- explicit ImageFilterModel(QObject* parent = 0);
+- ~ImageFilterModel();
+-
+- /** Add a hook to get added images for preparation tasks before they are added in the model */
+- void addPrepareHook(ImageFilterModelPrepareHook* hook);
+- void removePrepareHook(ImageFilterModelPrepareHook* hook);
+-
+- /** Returns a set of DatabaseFields suggested to set as watch flags on the source ImageModel.
+- * The contained flags will be those that this model can sort or filter by. */
+- DatabaseFields::Set suggestedWatchFlags() const;
+-
+- ImageFilterSettings imageFilterSettings() const;
+- VersionImageFilterSettings versionImageFilterSettings() const;
+- GroupImageFilterSettings groupImageFilterSettings() const;
+- ImageSortSettings imageSortSettings() const;
+-
+- // group is identified by the id of its group leader
+- bool isGroupOpen(qlonglong group) const;
+- bool isAllGroupsOpen() const;
+-
+- /// Enables sending imageInfosAdded and imageInfosAboutToBeRemoved
+- void setSendImageInfoSignals(bool sendSignals);
+-
+- virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+- virtual ImageFilterModel* imageFilterModel() const;
+-
+-public Q_SLOTS:
+-
+- /** Changes the current version image filter settings and refilters. */
+- void setVersionImageFilterSettings(const VersionImageFilterSettings& settings);
+-
+- /** Changes the current version image filter settings and refilters. */
+- void setGroupImageFilterSettings(const GroupImageFilterSettings& settings);
+-
+- /** Adjust the current ImageFilterSettings.
+- * Equivalent to retrieving the current filter settings, adjusting the parameter
+- * and calling setImageFilterSettings.
+- * Provided for convenience.
+- * It is encouraged to use setImageFilterSettings if you change more than one
+- * parameter at a time.
+- */
+- void setDayFilter(const QList<QDateTime>& days);
+- void setTagFilter(const QList<int>& includedTags, const QList<int>& excludedTags,
+- ImageFilterSettings::MatchingCondition matchingCond, bool showUnTagged,
+- const QList<int>& clTagIds, const QList<int>& plTagIds);
+- void setRatingFilter(int rating, ImageFilterSettings::RatingCondition ratingCond, bool isUnratedExcluded);
+- void setMimeTypeFilter(int mimeTypeFilter);
+- void setGeolocationFilter(const ImageFilterSettings::GeolocationCondition& condition);
+- void setTextFilter(const SearchTextFilterSettings& settings);
+-
+- void setCategorizationMode(ImageSortSettings::CategorizationMode mode);
+- void setCategorizationSortOrder(ImageSortSettings::SortOrder order);
+- void setSortRole(ImageSortSettings::SortRole role);
+- void setSortOrder(ImageSortSettings::SortOrder order);
+- void setStringTypeNatural(bool natural);
+- void setUrlWhitelist(const QList<QUrl> urlList, const QString& id);
+- void setIdWhitelist(const QList<qlonglong>& idList, const QString& id);
+-
+- void setVersionManagerSettings(const VersionManagerSettings& settings);
+- void setExceptionList(const QList<qlonglong>& idlist, const QString& id);
+-
+- void setGroupOpen(qlonglong group, bool open);
+- void toggleGroupOpen(qlonglong group);
+- void setAllGroupsOpen(bool open);
+-
+- /** Changes the current image filter settings and refilters. */
+- virtual void setImageFilterSettings(const ImageFilterSettings& settings);
+-
+- /** Changes the current image sort settings and resorts. */
+- virtual void setImageSortSettings(const ImageSortSettings& settings);
+-
+-Q_SIGNALS:
+-
+- /// Signals that the set filter matches at least one index
+- void filterMatches(bool matches);
+-
+- /** Signals that the set text filter matches at least one entry.
+- If no text filter is set, this signal is emitted
+- with 'false' when filterMatches() is emitted.
+- */
+- void filterMatchesForText(bool matchesByText);
+-
+- /** Emitted when the filter settings have been changed
+- (the model may not yet have been updated)
+- */
+- void filterSettingsChanged(const ImageFilterSettings& settings);
+-
+- /** These signals need to be explicitly enabled with setSendImageInfoSignals()
+- */
+- void imageInfosAdded(const QList<ImageInfo>& infos);
+- void imageInfosAboutToBeRemoved(const QList<ImageInfo>& infos);
+-
+-public:
+-
+- // Declared as public because of use in sub-classes.
+- class ImageFilterModelPrivate;
+-
+-protected:
+-
+- ImageFilterModelPrivate* const d_ptr;
+-
+-protected:
+-
+- ImageFilterModel(ImageFilterModelPrivate& dd, QObject* parent);
+-
+- virtual void setDirectSourceImageModel(ImageModel* model);
+-
+- virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
+-
+- virtual int compareCategories(const QModelIndex& left, const QModelIndex& right) const;
+- virtual bool subSortLessThan(const QModelIndex& left, const QModelIndex& right) const;
+- //virtual int categoryCount(const ImageInfo& info) const;
+-
+- /** Reimplement to customize category sorting,
+- * Return negative if category of left < category right,
+- * Return 0 if left and right are in the same category, else return positive.
+- */
+- virtual int compareInfosCategories(const ImageInfo& left, const ImageInfo& right) const;
+-
+- /** Reimplement to customize sorting. Do not take categories into account here.
+- */
+- virtual bool infosLessThan(const ImageInfo& left, const ImageInfo& right) const;
+-
+- /** Returns a unique identifier for the category if info. The string need not be for user display.
+- */
+- virtual QString categoryIdentifier(const ImageInfo& info) const;
+-
+-protected Q_SLOTS:
+-
+- void slotModelReset();
+- void slotUpdateFilter();
+-
+- void slotImageTagChange(const ImageTagChangeset& changeset);
+- void slotImageChange(const ImageChangeset& changeset);
+-
+- void slotRowsInserted(const QModelIndex& parent, int start, int end);
+- void slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end);
+-
+-private:
+-
+- Q_DECLARE_PRIVATE(ImageFilterModel)
+-};
+-
+-// -----------------------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT NoDuplicatesImageFilterModel : public ImageSortFilterModel
+-{
+- Q_OBJECT
+-
+-public:
+-
+- explicit NoDuplicatesImageFilterModel(QObject* parent = 0);
+-
+-protected:
+-
+- virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
+-};
+-
+-} // namespace Digikam
+-
+-Q_DECLARE_METATYPE(Digikam::ImageFilterModel*)
+-
+-#endif // IMAGEMODEL_H
+diff --git a/libs/models/imagefiltermodelpriv.cpp b/libs/models/imagefiltermodelpriv.cpp
+deleted file mode 100644
+index 07d9e79..0000000
+--- a/libs/models/imagefiltermodelpriv.cpp
++++ /dev/null
+@@ -1,258 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagefiltermodelpriv.h"
+-
+-// Local includes
+-
+-#include "digikam_debug.h"
+-#include "imagefiltermodelthreads.h"
+-
+-namespace Digikam
+-{
+-
+-ImageFilterModel::ImageFilterModelPrivate::ImageFilterModelPrivate()
+-{
+- imageModel = 0;
+- version = 0;
+- lastDiscardVersion = 0;
+- sentOut = 0;
+- sentOutForReAdd = 0;
+- updateFilterTimer = 0;
+- needPrepare = false;
+- needPrepareComments = false;
+- needPrepareTags = false;
+- needPrepareGroups = false;
+- preparer = 0;
+- filterer = 0;
+- hasOneMatch = false;
+- hasOneMatchForText = false;
+-
+- setupWorkers();
+-}
+-
+-ImageFilterModel::ImageFilterModelPrivate::~ImageFilterModelPrivate()
+-{
+- // facilitate thread stopping
+- ++version;
+- preparer->deactivate();
+- filterer->deactivate();
+- delete preparer;
+- delete filterer;
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::init(ImageFilterModel* _q)
+-{
+- q = _q;
+-
+- updateFilterTimer = new QTimer(this);
+- updateFilterTimer->setSingleShot(true);
+- updateFilterTimer->setInterval(250);
+-
+- connect(updateFilterTimer, SIGNAL(timeout()),
+- q, SLOT(slotUpdateFilter()));
+-
+- // inter-thread redirection
+- qRegisterMetaType<ImageFilterModelTodoPackage>("ImageFilterModelTodoPackage");
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- infosToProcess(infos, extraValues, true);
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- // These have already been added, we just process them afterwards
+- infosToProcess(infos, extraValues, false);
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::setupWorkers()
+-{
+- preparer = new ImageFilterModelPreparer(this);
+- filterer = new ImageFilterModelFilterer(this);
+-
+- // A package in constructed in infosToProcess.
+- // Normal flow is infosToProcess -> preparer::process -> filterer::process -> packageFinished.
+- // If no preparation is needed, the first step is skipped.
+- // If filter version changes, both will discard old package and send them to packageDiscarded.
+-
+- connect(this, SIGNAL(packageToPrepare(ImageFilterModelTodoPackage)),
+- preparer, SLOT(process(ImageFilterModelTodoPackage)));
+-
+- connect(this, SIGNAL(packageToFilter(ImageFilterModelTodoPackage)),
+- filterer, SLOT(process(ImageFilterModelTodoPackage)));
+-
+- connect(preparer, SIGNAL(processed(ImageFilterModelTodoPackage)),
+- filterer, SLOT(process(ImageFilterModelTodoPackage)));
+-
+- connect(filterer, SIGNAL(processed(ImageFilterModelTodoPackage)),
+- this, SLOT(packageFinished(ImageFilterModelTodoPackage)));
+-
+- connect(preparer, SIGNAL(discarded(ImageFilterModelTodoPackage)),
+- this, SLOT(packageDiscarded(ImageFilterModelTodoPackage)));
+-
+- connect(filterer, SIGNAL(discarded(ImageFilterModelTodoPackage)),
+- this, SLOT(packageDiscarded(ImageFilterModelTodoPackage)));
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList<ImageInfo>& infos)
+-{
+- infosToProcess(infos, QList<QVariant>(), false);
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues, bool forReAdd)
+-{
+- if (infos.isEmpty())
+- {
+- return;
+- }
+-
+- filterer->schedule();
+-
+- if (needPrepare)
+- {
+- preparer->schedule();
+- }
+-
+- Q_ASSERT(extraValues.isEmpty() || infos.size() == extraValues.size());
+-
+- // prepare and filter in chunks
+- const int size = infos.size();
+- const int maxChunkSize = needPrepare ? PrepareChunkSize : FilterChunkSize;
+- const bool hasExtraValues = !extraValues.isEmpty();
+- QList<ImageInfo>::const_iterator it = infos.constBegin(), end;
+- QList<QVariant>::const_iterator xit = extraValues.constBegin(), xend;
+- int index = 0;
+- QVector<ImageInfo> infoVector;
+- QVector<QVariant> extraValueVector;
+-
+- while (it != infos.constEnd())
+- {
+- const int chunkSize = qMin(maxChunkSize, size - index);
+- infoVector.resize(chunkSize);
+- end = it + chunkSize;
+- qCopy(it, end, infoVector.begin());
+-
+- if (hasExtraValues)
+- {
+- extraValueVector.resize(chunkSize);
+- xend = xit + chunkSize;
+- qCopy(xit, xend, extraValueVector.begin());
+- xit = xend;
+- }
+-
+- it = end;
+- index += chunkSize;
+-
+- ++sentOut;
+-
+- if (forReAdd)
+- {
+- ++sentOutForReAdd;
+- }
+-
+- if (needPrepare)
+- {
+- emit packageToPrepare(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd));
+- }
+- else
+- {
+- emit packageToFilter(ImageFilterModelTodoPackage(infoVector, extraValueVector, version, forReAdd));
+- }
+- }
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::packageFinished(const ImageFilterModelTodoPackage& package)
+-{
+- // check if it got discarded on the journey
+- if (package.version != version)
+- {
+- packageDiscarded(package);
+- return;
+- }
+-
+- // incorporate result
+- QHash<qlonglong, bool>::const_iterator it = package.filterResults.constBegin();
+-
+- for (; it != package.filterResults.constEnd(); ++it)
+- {
+- filterResults.insert(it.key(), it.value());
+- }
+-
+- // re-add if necessary
+- if (package.isForReAdd)
+- {
+- emit reAddImageInfos(package.infos.toList(), package.extraValues.toList());
+-
+- if (sentOutForReAdd == 1) // last package
+- {
+- emit reAddingFinished();
+- }
+- }
+-
+- // decrement counters
+- --sentOut;
+-
+- if (package.isForReAdd)
+- {
+- --sentOutForReAdd;
+- }
+-
+- // If all packages have returned, filtered and readded, and no more are expected,
+- // and there is need to tell the filter result to the view, do that
+- if (sentOut == 0 && sentOutForReAdd == 0 && !imageModel->isRefreshing())
+- {
+- q->invalidate(); // use invalidate, not invalidateFilter only. Sorting may have changed as well.
+- emit (q->filterMatches(hasOneMatch));
+- emit (q->filterMatchesForText(hasOneMatchForText));
+- filterer->deactivate();
+- preparer->deactivate();
+- }
+-}
+-
+-void ImageFilterModel::ImageFilterModelPrivate::packageDiscarded(const ImageFilterModelTodoPackage& package)
+-{
+- // Either, the model was reset, or the filter changed
+- // In the former case throw all away, in the latter case, recycle
+- if (package.version > lastDiscardVersion)
+- {
+- // Recycle packages: Send again with current version
+- // Do not increment sentOut or sentOutForReAdd here: it was not decremented!
+-
+- if (needPrepare)
+- {
+- emit packageToPrepare(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd));
+- }
+- else
+- {
+- emit packageToFilter(ImageFilterModelTodoPackage(package.infos, package.extraValues, version, package.isForReAdd));
+- }
+- }
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagefiltermodelpriv.h b/libs/models/imagefiltermodelpriv.h
+deleted file mode 100644
+index a9e3f22..0000000
+--- a/libs/models/imagefiltermodelpriv.h
++++ /dev/null
+@@ -1,159 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-11
+- * Description : Qt item model for database entries - private shared header
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGEFILTERMODELPRIV_H
+-#define IMAGEFILTERMODELPRIV_H
+-
+-// Qt includes
+-
+-#include <QHash>
+-#include <QMutex>
+-#include <QMutexLocker>
+-#include <QSet>
+-#include <QThread>
+-#include <QTimer>
+-#include <QWaitCondition>
+-
+-// Local includes
+-
+-#include "imageinfo.h"
+-#include "imagefiltermodel.h"
+-
+-#include "digikam_export.h"
+-// Yes, we need the EXPORT macro in a private header because
+-// this private header is shared across binary objects.
+-// This does NOT make this classes here any more public!
+-
+-namespace Digikam
+-{
+-
+-const int PrepareChunkSize = 101;
+-const int FilterChunkSize = 2001;
+-
+-class ImageFilterModelTodoPackage
+-{
+-public:
+-
+- ImageFilterModelTodoPackage()
+- : version(0), isForReAdd(false)
+- {
+- }
+-
+- ImageFilterModelTodoPackage(const QVector<ImageInfo>& infos, const QVector<QVariant>& extraValues, int version, bool isForReAdd)
+- : infos(infos), extraValues(extraValues), version(version), isForReAdd(isForReAdd)
+- {
+- }
+-
+- QVector<ImageInfo> infos;
+- QVector<QVariant> extraValues;
+- unsigned int version;
+- bool isForReAdd;
+- QHash<qlonglong, bool> filterResults;
+-};
+-
+-// ------------------------------------------------------------------------------------------------
+-
+-class ImageFilterModelPreparer;
+-class ImageFilterModelFilterer;
+-
+-class DIGIKAM_DATABASE_EXPORT ImageFilterModel::ImageFilterModelPrivate : public QObject
+-{
+- Q_OBJECT
+-
+-public:
+-
+- ImageFilterModelPrivate();
+- ~ImageFilterModelPrivate();
+-
+- void init(ImageFilterModel* q);
+- void setupWorkers();
+- void infosToProcess(const QList<ImageInfo>& infos);
+- void infosToProcess(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues, bool forReAdd = true);
+-
+-public:
+-
+- ImageFilterModel* q;
+-
+- ImageModel* imageModel;
+-
+- ImageFilterSettings filter;
+- ImageSortSettings sorter;
+- VersionImageFilterSettings versionFilter;
+- GroupImageFilterSettings groupFilter;
+-
+- volatile unsigned int version;
+- unsigned int lastDiscardVersion;
+- unsigned int lastFilteredVersion;
+- int sentOut;
+- int sentOutForReAdd;
+-
+- QTimer* updateFilterTimer;
+-
+- bool needPrepare;
+- bool needPrepareComments;
+- bool needPrepareTags;
+- bool needPrepareGroups;
+-
+- QMutex mutex;
+- ImageFilterSettings filterCopy;
+- VersionImageFilterSettings versionFilterCopy;
+- GroupImageFilterSettings groupFilterCopy;
+- ImageFilterModelPreparer* preparer;
+- ImageFilterModelFilterer* filterer;
+-
+- QHash<qlonglong, bool> filterResults;
+- bool hasOneMatch;
+- bool hasOneMatchForText;
+-
+- QList<ImageFilterModelPrepareHook*> prepareHooks;
+-
+-/*
+- QHash<int, QSet<qlonglong> > categoryCountHashInt;
+- QHash<QString, QSet<qlonglong> > categoryCountHashString;
+-
+-public:
+-
+- void cacheCategoryCount(int id, qlonglong imageid) const
+- { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashInt[id].insert(imageid); }
+- void cacheCategoryCount(const QString& id, qlonglong imageid) const
+- { const_cast<ImageFilterModelPrivate*>(this)->categoryCountHashString[id].insert(imageid); }
+-*/
+-
+-public Q_SLOTS:
+-
+- void preprocessInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void processAddedInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void packageFinished(const ImageFilterModelTodoPackage& package);
+- void packageDiscarded(const ImageFilterModelTodoPackage& package);
+-
+-Q_SIGNALS:
+-
+- void packageToPrepare(const ImageFilterModelTodoPackage& package);
+- void packageToFilter(const ImageFilterModelTodoPackage& package);
+- void reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void reAddingFinished();
+-};
+-
+-} // namespace Digikam
+-
+-#endif // IMAGEFILTERMODELPRIV_H
+diff --git a/libs/models/imagefiltermodelthreads.cpp b/libs/models/imagefiltermodelthreads.cpp
+deleted file mode 100644
+index aa5c462..0000000
+--- a/libs/models/imagefiltermodelthreads.cpp
++++ /dev/null
+@@ -1,40 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagefiltermodel.h"
+-#include "imagefiltermodelpriv.h"
+-#include "imagefiltermodelthreads.h"
+-
+-namespace Digikam
+-{
+-
+-ImageFilterModelWorker::ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d)
+- : d(d)
+-{
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagefiltermodelthreads.h b/libs/models/imagefiltermodelthreads.h
+deleted file mode 100644
+index 83fa987..0000000
+--- a/libs/models/imagefiltermodelthreads.h
++++ /dev/null
+@@ -1,100 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-11
+- * Description : Qt item model for database entries - private header
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGEFILTERMODELTHREADS_H
+-#define IMAGEFILTERMODELTHREADS_H
+-
+-// Qt includes
+-
+-#include <QThread>
+-
+-// Local includes
+-
+-#include "digikam_export.h"
+-#include "workerobject.h"
+-
+-namespace Digikam
+-{
+-
+-class DIGIKAM_DATABASE_EXPORT ImageFilterModelWorker : public WorkerObject
+-{
+- Q_OBJECT
+-
+-public:
+-
+- explicit ImageFilterModelWorker(ImageFilterModel::ImageFilterModelPrivate* const d);
+-
+- bool checkVersion(const ImageFilterModelTodoPackage& package)
+- {
+- return d->version == package.version;
+- }
+-
+-public Q_SLOTS:
+-
+- virtual void process(ImageFilterModelTodoPackage package) = 0;
+-
+-Q_SIGNALS:
+-
+- void processed(const ImageFilterModelTodoPackage& package);
+- void discarded(const ImageFilterModelTodoPackage& package);
+-
+-protected:
+-
+- ImageFilterModel::ImageFilterModelPrivate* d;
+-};
+-
+-// -----------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT ImageFilterModelPreparer : public ImageFilterModelWorker
+-{
+- Q_OBJECT
+-
+-public:
+-
+- explicit ImageFilterModelPreparer(ImageFilterModel::ImageFilterModelPrivate* const d)
+- : ImageFilterModelWorker(d)
+- {
+- }
+-
+- void process(ImageFilterModelTodoPackage package);
+-};
+-
+-// ----------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT ImageFilterModelFilterer : public ImageFilterModelWorker
+-{
+- Q_OBJECT
+-
+-public:
+-
+- explicit ImageFilterModelFilterer(ImageFilterModel::ImageFilterModelPrivate* const d)
+- : ImageFilterModelWorker(d)
+- {
+- }
+-
+- void process(ImageFilterModelTodoPackage package);
+-};
+-
+-} // namespace Digikam
+-
+-#endif // IMAGEFILTERMODELTHREADS_H
+diff --git a/libs/models/imagefiltersettings.cpp b/libs/models/imagefiltersettings.cpp
+deleted file mode 100644
+index b61e7f9..0000000
+--- a/libs/models/imagefiltersettings.cpp
++++ /dev/null
+@@ -1,952 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Filter values for use with ImageFilterModel
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagefiltersettings.h"
+-
+-// C++ includes
+-
+-#include <cmath>
+-
+-// Qt includes
+-
+-#include <QDateTime>
+-
+-// Local includes
+-
+-#include "digikam_debug.h"
+-#include "coredbfields.h"
+-#include "digikam_globals.h"
+-#include "imageinfo.h"
+-#include "tagscache.h"
+-#include "versionmanagersettings.h"
+-
+-namespace Digikam
+-{
+-
+-ImageFilterSettings::ImageFilterSettings()
+-{
+- m_untaggedFilter = false;
+- m_isUnratedExcluded = false;
+- m_ratingFilter = 0;
+- m_mimeTypeFilter = MimeFilter::AllFiles;
+- m_ratingCond = GreaterEqualCondition;
+- m_matchingCond = OrCondition;
+- m_geolocationCondition = GeolocationNoFilter;
+-}
+-
+-DatabaseFields::Set ImageFilterSettings::watchFlags() const
+-{
+- DatabaseFields::Set set;
+-
+- if (isFilteringByDay())
+- {
+- set |= DatabaseFields::CreationDate;
+- }
+-
+- if (isFilteringByText())
+- {
+- set |= DatabaseFields::Name;
+- set |= DatabaseFields::Comment;
+- }
+-
+- if (isFilteringByRating())
+- {
+- set |= DatabaseFields::Rating;
+- }
+-
+- if (isFilteringByTypeMime())
+- {
+- set |= DatabaseFields::Category;
+- set |= DatabaseFields::Format;
+- }
+-
+- if (isFilteringByGeolocation())
+- {
+- set |= DatabaseFields::ImagePositionsAll;
+- }
+-
+- if (isFilteringByColorLabels())
+- {
+- set |= DatabaseFields::ColorLabel;
+- }
+-
+- if (isFilteringByPickLabels())
+- {
+- set |= DatabaseFields::PickLabel;
+- }
+-
+- return set;
+-}
+-
+-bool ImageFilterSettings::isFilteringByDay() const
+-{
+- if (!m_dayFilter.isEmpty())
+- {
+- return true;
+- }
+-
+- return false;
+-}
+-
+-bool ImageFilterSettings::isFilteringByTags() const
+-{
+- if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter)
+- {
+- return true;
+- }
+-
+- return false;
+-}
+-
+-bool ImageFilterSettings::isFilteringByColorLabels() const
+-{
+- if (!m_colorLabelTagFilter.isEmpty())
+- {
+- return true;
+- }
+-
+- return false;
+-}
+-
+-bool ImageFilterSettings::isFilteringByPickLabels() const
+-{
+- if (!m_pickLabelTagFilter.isEmpty())
+- {
+- return true;
+- }
+-
+- return false;
+-}
+-
+-bool ImageFilterSettings::isFilteringByText() const
+-{
+- if (!m_textFilterSettings.text.isEmpty())
+- {
+- return true;
+- }
+-
+- return false;
+-}
+-
+-bool ImageFilterSettings::isFilteringByTypeMime() const
+-{
+- if (m_mimeTypeFilter != MimeFilter::AllFiles)
+- {
+- return true;
+- }
+-
+- return false;
+-}
+-
+-bool ImageFilterSettings::isFilteringByGeolocation() const
+-{
+- return (m_geolocationCondition != GeolocationNoFilter);
+-}
+-
+-bool ImageFilterSettings::isFilteringByRating() const
+-{
+- if (m_ratingFilter != 0 || m_ratingCond != GreaterEqualCondition || m_isUnratedExcluded)
+- {
+- return true;
+- }
+-
+- return false;
+-}
+-
+-bool ImageFilterSettings::isFilteringInternally() const
+-{
+- return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty());
+-}
+-
+-bool ImageFilterSettings::isFiltering() const
+-{
+- return isFilteringByDay() ||
+- isFilteringByTags() ||
+- isFilteringByText() ||
+- isFilteringByRating() ||
+- isFilteringByTypeMime() ||
+- isFilteringByColorLabels() ||
+- isFilteringByPickLabels() ||
+- isFilteringByGeolocation();
+-}
+-
+-void ImageFilterSettings::setDayFilter(const QList<QDateTime>& days)
+-{
+- m_dayFilter.clear();
+-
+- for (QList<QDateTime>::const_iterator it = days.constBegin(); it != days.constEnd(); ++it)
+- {
+- m_dayFilter.insert(*it, true);
+- }
+-}
+-
+-void ImageFilterSettings::setTagFilter(const QList<int>& includedTags,
+- const QList<int>& excludedTags,
+- MatchingCondition matchingCondition,
+- bool showUnTagged,
+- const QList<int>& clTagIds,
+- const QList<int>& plTagIds)
+-{
+- m_includeTagFilter = includedTags;
+- m_excludeTagFilter = excludedTags;
+- m_matchingCond = matchingCondition;
+- m_untaggedFilter = showUnTagged;
+- m_colorLabelTagFilter = clTagIds;
+- m_pickLabelTagFilter = plTagIds;
+-}
+-
+-void ImageFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded)
+-{
+- m_ratingFilter = rating;
+- m_ratingCond = ratingCondition;
+- m_isUnratedExcluded = isUnratedExcluded;
+-}
+-
+-void ImageFilterSettings::setMimeTypeFilter(int mime)
+-{
+- m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime;
+-}
+-
+-void ImageFilterSettings::setGeolocationFilter(const GeolocationCondition& condition)
+-{
+- m_geolocationCondition = condition;
+-}
+-
+-void ImageFilterSettings::setTextFilter(const SearchTextFilterSettings& settings)
+-{
+- m_textFilterSettings = settings;
+-}
+-
+-void ImageFilterSettings::setTagNames(const QHash<int, QString>& hash)
+-{
+- m_tagNameHash = hash;
+-}
+-
+-void ImageFilterSettings::setAlbumNames(const QHash<int, QString>& hash)
+-{
+- m_albumNameHash = hash;
+-}
+-
+-void ImageFilterSettings::setUrlWhitelist(const QList<QUrl>& urlList, const QString& id)
+-{
+- if (urlList.isEmpty())
+- {
+- m_urlWhitelists.remove(id);
+- }
+- else
+- {
+- m_urlWhitelists.insert(id, urlList);
+- }
+-}
+-
+-void ImageFilterSettings::setIdWhitelist(const QList<qlonglong>& idList, const QString& id)
+-{
+- if (idList.isEmpty())
+- {
+- m_idWhitelists.remove(id);
+- }
+- else
+- {
+- m_idWhitelists.insert(id, idList);
+- }
+-}
+-
+-template <class ContainerA, class ContainerB>
+-bool containsAnyOf(const ContainerA& listA, const ContainerB& listB)
+-{
+- foreach (const typename ContainerA::value_type& a, listA)
+- {
+- if (listB.contains(a))
+- {
+- return true;
+- }
+- }
+- return false;
+-}
+-
+-template <class ContainerA, typename Value, class ContainerB>
+-bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception)
+-{
+- foreach (const typename ContainerB::value_type& n, noneOfList)
+- {
+- if (n != exception && list.contains(n))
+- {
+- return false;
+- }
+- }
+- return true;
+-}
+-
+-bool ImageFilterSettings::matches(const ImageInfo& info, bool* const foundText) const
+-{
+- if (foundText)
+- {
+- *foundText = false;
+- }
+-
+- if (!isFilteringInternally())
+- {
+- return true;
+- }
+-
+- bool match = false;
+-
+- if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty())
+- {
+- QList<int> tagIds = info.tagIds();
+- QList<int>::const_iterator it;
+-
+- match = m_includeTagFilter.isEmpty();
+-
+- if (m_matchingCond == OrCondition)
+- {
+- for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it)
+- {
+- if (tagIds.contains(*it))
+- {
+- match = true;
+- break;
+- }
+- }
+-
+- match |= (m_untaggedFilter && tagIds.isEmpty());
+- }
+- else // AND matching condition...
+- {
+- // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match
+- if (!m_untaggedFilter)
+- {
+- for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it)
+- {
+- if (!tagIds.contains(*it))
+- {
+- break;
+- }
+- }
+-
+- if (it == m_includeTagFilter.end())
+- {
+- match = true;
+- }
+- }
+- }
+-
+- for (it = m_excludeTagFilter.begin(); it != m_excludeTagFilter.end(); ++it)
+- {
+- if (tagIds.contains(*it))
+- {
+- match = false;
+- break;
+- }
+- }
+- }
+- else if (m_untaggedFilter)
+- {
+- match = !TagsCache::instance()->containsPublicTags(info.tagIds());
+- }
+- else
+- {
+- match = true;
+- }
+-
+- //-- Filter by pick labels ------------------------------------------------
+-
+- if (!m_pickLabelTagFilter.isEmpty())
+- {
+- QList<int> tagIds = info.tagIds();
+- bool matchPL = false;
+-
+- if (containsAnyOf(m_pickLabelTagFilter, tagIds))
+- {
+- matchPL = true;
+- }
+- else if (!matchPL)
+- {
+- int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel);
+-
+- if (m_pickLabelTagFilter.contains(noPickLabelTagId))
+- {
+- // Searching for "has no ColorLabel" requires special handling:
+- // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag
+- matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId);
+- }
+- }
+-
+- match &= matchPL;
+- }
+-
+- //-- Filter by color labels ------------------------------------------------
+-
+- if (!m_colorLabelTagFilter.isEmpty())
+- {
+- QList<int> tagIds = info.tagIds();
+- bool matchCL = false;
+-
+- if (containsAnyOf(m_colorLabelTagFilter, tagIds))
+- {
+- matchCL = true;
+- }
+- else if (!matchCL)
+- {
+- int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel);
+-
+- if (m_colorLabelTagFilter.contains(noColorLabelTagId))
+- {
+- // Searching for "has no ColorLabel" requires special handling:
+- // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag
+- matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId);
+- }
+- }
+-
+- match &= matchCL;
+- }
+-
+- //-- Filter by date -----------------------------------------------------------
+-
+- if (!m_dayFilter.isEmpty())
+- {
+- match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime()));
+- }
+-
+- //-- Filter by rating ---------------------------------------------------------
+-
+- if (m_ratingFilter >= 0)
+- {
+- // for now we treat -1 (no rating) just like a rating of 0.
+- int rating = info.rating();
+-
+- if (rating == -1)
+- {
+- rating = 0;
+- }
+-
+- if(m_isUnratedExcluded && rating == 0)
+- {
+- match = false;
+- }
+- else
+- {
+- if (m_ratingCond == GreaterEqualCondition)
+- {
+- // If the rating is not >=, i.e it is <, then it does not match.
+- if (rating < m_ratingFilter)
+- {
+- match = false;
+- }
+- }
+- else if (m_ratingCond == EqualCondition)
+- {
+- // If the rating is not =, i.e it is !=, then it does not match.
+- if (rating != m_ratingFilter)
+- {
+- match = false;
+- }
+- }
+- else
+- {
+- // If the rating is not <=, i.e it is >, then it does not match.
+- if (rating > m_ratingFilter)
+- {
+- match = false;
+- }
+- }
+- }
+- }
+-
+- // -- Filter by mime type -----------------------------------------------------
+-
+- switch (m_mimeTypeFilter)
+- {
+- // info.format is a standardized string: Only one possibility per mime type
+- case MimeFilter::ImageFiles:
+- {
+- if (info.category() != DatabaseItem::Image)
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::JPGFiles:
+- {
+- if (info.format() != QLatin1String("JPG"))
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::PNGFiles:
+- {
+- if (info.format() != QLatin1String("PNG"))
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::TIFFiles:
+- {
+- if (info.format() != QLatin1String("TIFF"))
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::DNGFiles:
+- {
+- if (info.format() != QLatin1String("RAW-DNG"))
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::NoRAWFiles:
+- {
+- if (info.format().startsWith(QLatin1String("RAW")))
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::RAWFiles:
+- {
+- if (!info.format().startsWith(QLatin1String("RAW")))
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::MoviesFiles:
+- {
+- if (info.category() != DatabaseItem::Video)
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::AudioFiles:
+- {
+- if (info.category() != DatabaseItem::Audio)
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- case MimeFilter::RasterFiles:
+- {
+- if (info.format() != QLatin1String("PSD") && // Adobe Photoshop Document
+- info.format() != QLatin1String("PSB") && // Adobe Photoshop Big
+- info.format() != QLatin1String("XCF") && // Gimp
+- info.format() != QLatin1String("KRA") && // Krita
+- info.format() != QLatin1String("ORA") // Open Raster
+- )
+- {
+- match = false;
+- }
+-
+- break;
+- }
+- default:
+- {
+- // All Files: do nothing...
+- break;
+- }
+- }
+-
+- //-- Filter by geolocation ----------------------------------------------------
+-
+- if (m_geolocationCondition!=GeolocationNoFilter)
+- {
+- if (m_geolocationCondition==GeolocationNoCoordinates)
+- {
+- if (info.hasCoordinates())
+- {
+- match = false;
+- }
+- }
+- else if (m_geolocationCondition==GeolocationHasCoordinates)
+- {
+- if (!info.hasCoordinates())
+- {
+- match = false;
+- }
+- }
+- }
+-
+- //-- Filter by text -----------------------------------------------------------
+-
+- if (!m_textFilterSettings.text.isEmpty())
+- {
+- bool textMatch = false;
+-
+- // Image name
+- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName &&
+- info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
+- {
+- textMatch = true;
+- }
+-
+- // Image title
+- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle &&
+- info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
+- {
+- textMatch = true;
+- }
+-
+- // Image comment
+- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment &&
+- info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
+- {
+- textMatch = true;
+- }
+-
+- // Tag names
+- foreach(int id, info.tagIds())
+- {
+- if (m_textFilterSettings.textFields & SearchTextFilterSettings::TagName &&
+- m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
+- {
+- textMatch = true;
+- }
+- }
+-
+- // Album names
+- if (m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName &&
+- m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive))
+- {
+- textMatch = true;
+- }
+-
+- // Image Aspect Ratio
+- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio)
+- {
+- QRegExp expRatio (QLatin1String("^\\d+:\\d+$"));
+- QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$"));
+-
+- if (expRatio.indexIn(m_textFilterSettings.text) > -1 && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+"))))
+- {
+- QString trimmedTextFilterSettingsText = m_textFilterSettings.text;
+- QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1String(":"), QString::SkipEmptyParts);
+-
+- if (numberStringList.length() == 2)
+- {
+- QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1);
+- bool canConverseNum = false;
+- bool canConverseDenom = false;
+- int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10);
+-
+- if (canConverseNum && canConverseDenom)
+- {
+- if (fabs(info.aspectRatio() - (double)num / denom) < 0.1)
+- textMatch = true;
+- }
+- }
+- }
+- else if (expFloat.indexIn(m_textFilterSettings.text) > -1)
+- {
+- QString trimmedTextFilterSettingsText = m_textFilterSettings.text;
+- bool canConverse = false;
+- double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse);
+-
+- if (canConverse)
+- {
+- if (fabs(info.aspectRatio() - ratio) < 0.1)
+- textMatch = true;
+- }
+- }
+- }
+-
+- // Image Pixel Size
+- // See bug #341053 for details.
+-
+- if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize)
+- {
+- QSize size = info.dimensions();
+- int pixelSize = size.height()*size.width();
+- QString text = m_textFilterSettings.text;
+-
+- if(text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && pixelSize > (text.remove(0,1)).toInt())
+- {
+- textMatch = true;
+- }
+- else if(text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && pixelSize < (text.remove(0,1)).toInt())
+- {
+- textMatch = true;
+- }
+- else if(text.contains(QRegExp(QLatin1String("^\\d+$"))) && pixelSize == text.toInt())
+- {
+- textMatch = true;
+- }
+- }
+-
+- match &= textMatch;
+-
+- if (foundText)
+- {
+- *foundText = textMatch;
+- }
+- }
+-
+- // -- filter by URL-whitelists ------------------------------------------------
+- // NOTE: whitelists are always AND for now.
+-
+- if (match)
+- {
+- const QUrl url = info.fileUrl();
+-
+- for (QHash<QString, QList<QUrl>>::const_iterator it = m_urlWhitelists.constBegin();
+- it!=m_urlWhitelists.constEnd(); ++it)
+- {
+- match = it->contains(url);
+-
+- if (!match)
+- {
+- break;
+- }
+- }
+- }
+-
+- if (match)
+- {
+- const qlonglong id = info.id();
+-
+- for (QHash<QString, QList<qlonglong> >::const_iterator it = m_idWhitelists.constBegin();
+- it!=m_idWhitelists.constEnd(); ++it)
+- {
+- match = it->contains(id);
+-
+- if (!match)
+- {
+- break;
+- }
+- }
+- }
+-
+- return match;
+-}
+-
+-// -------------------------------------------------------------------------------------------------
+-
+-VersionImageFilterSettings::VersionImageFilterSettings()
+-{
+- m_includeTagFilter = 0;
+- m_exceptionTagFilter = 0;
+-}
+-
+-VersionImageFilterSettings::VersionImageFilterSettings(const VersionManagerSettings& settings)
+-{
+- setVersionManagerSettings(settings);
+-}
+-
+-bool VersionImageFilterSettings::operator==(const VersionImageFilterSettings& other) const
+-{
+- return m_excludeTagFilter == other.m_excludeTagFilter &&
+- m_exceptionLists == other.m_exceptionLists;
+-}
+-
+-bool VersionImageFilterSettings::matches(const ImageInfo& info) const
+-{
+- if (!isFiltering())
+- {
+- return true;
+- }
+-
+- const qlonglong id = info.id();
+-
+- for (QHash<QString, QList<qlonglong> >::const_iterator it = m_exceptionLists.constBegin();
+- it != m_exceptionLists.constEnd(); ++it)
+- {
+- if (it->contains(id))
+- {
+- return true;
+- }
+- }
+-
+- bool match = true;
+- QList<int> tagIds = info.tagIds();
+-
+- if (!tagIds.contains(m_includeTagFilter))
+- {
+- for (QList<int>::const_iterator it = m_excludeTagFilter.begin();
+- it != m_excludeTagFilter.end(); ++it)
+- {
+- if (tagIds.contains(*it))
+- {
+- match = false;
+- break;
+- }
+- }
+- }
+-
+- if (!match)
+- {
+- if (tagIds.contains(m_exceptionTagFilter))
+- {
+- match = true;
+- }
+- }
+-
+- return match;
+-}
+-
+-bool VersionImageFilterSettings::isHiddenBySettings(const ImageInfo& info) const
+-{
+- QList<int> tagIds = info.tagIds();
+-
+- foreach(int tagId, m_excludeTagFilter)
+- {
+- if (tagIds.contains(tagId))
+- {
+- return true;
+- }
+- }
+-
+- return false;
+-}
+-
+-bool VersionImageFilterSettings::isExemptedBySettings(const ImageInfo& info) const
+-{
+- return info.tagIds().contains(m_exceptionTagFilter);
+-}
+-
+-void VersionImageFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings)
+-{
+- m_excludeTagFilter.clear();
+-
+- if (!settings.enabled)
+- {
+- return;
+- }
+-
+- if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal))
+- {
+- m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion());
+- }
+-
+- if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates))
+- {
+- m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion());
+- }
+-
+- m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion());
+- m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible());
+-}
+-
+-void VersionImageFilterSettings::setExceptionList(const QList<qlonglong>& idList, const QString& id)
+-{
+- if (idList.isEmpty())
+- {
+- m_exceptionLists.remove(id);
+- }
+- else
+- {
+- m_exceptionLists.insert(id, idList);
+- }
+-}
+-
+-bool VersionImageFilterSettings::isFiltering() const
+-{
+- return !m_excludeTagFilter.isEmpty();
+-}
+-
+-bool VersionImageFilterSettings::isFilteringByTags() const
+-{
+- return isFiltering();
+-}
+-
+-// -------------------------------------------------------------------------------------------------
+-
+-GroupImageFilterSettings::GroupImageFilterSettings()
+- : m_allOpen(false)
+-{
+-}
+-
+-bool GroupImageFilterSettings::operator==(const GroupImageFilterSettings& other) const
+-{
+- return (m_allOpen == other.m_allOpen &&
+- m_openGroups == other.m_openGroups);
+-}
+-
+-bool GroupImageFilterSettings::matches(const ImageInfo& info) const
+-{
+- if (m_allOpen)
+- {
+- return true;
+- }
+-
+- if (info.isGrouped())
+- {
+- return m_openGroups.contains(info.groupImage().id());
+- }
+- return true;
+-}
+-
+-void GroupImageFilterSettings::setOpen(qlonglong group, bool open)
+-{
+- if (open)
+- {
+- m_openGroups << group;
+- }
+- else
+- {
+- m_openGroups.remove(group);
+- }
+-}
+-
+-bool GroupImageFilterSettings::isOpen(qlonglong group) const
+-{
+- return m_openGroups.contains(group);
+-}
+-
+-void GroupImageFilterSettings::setAllOpen(bool open)
+-{
+- m_allOpen = open;
+-}
+-
+-bool GroupImageFilterSettings::isAllOpen() const
+-{
+- return m_allOpen;
+-}
+-
+-bool GroupImageFilterSettings::isFiltering() const
+-{
+- return !m_allOpen;
+-}
+-
+-DatabaseFields::Set GroupImageFilterSettings::watchFlags() const
+-{
+- return DatabaseFields::ImageRelations;
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagefiltersettings.h b/libs/models/imagefiltersettings.h
+deleted file mode 100644
+index 0e7beae..0000000
+--- a/libs/models/imagefiltersettings.h
++++ /dev/null
+@@ -1,349 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Filter values for use with ImageFilterModel
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- * Copyright (C) 2010 by Andi Clemens <andi dot clemens at gmail dot com>
+- * Copyright (C) 2011 by Michael G. Hansen <mike at mghansen dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGEFILTERSETTINGS_H
+-#define IMAGEFILTERSETTINGS_H
+-
+-// Qt includes
+-
+-#include <QHash>
+-#include <QList>
+-#include <QMap>
+-#include <QString>
+-#include <QSet>
+-#include <QUrl>
+-
+-// Local includes
+-
+-#include "searchtextbar.h"
+-#include "mimefilter.h"
+-#include "digikam_export.h"
+-
+-namespace Digikam
+-{
+-
+-class ImageInfo;
+-class VersionManagerSettings;
+-
+-namespace DatabaseFields
+-{
+- class Set;
+-}
+-
+-// ---------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT SearchTextFilterSettings : public SearchTextSettings
+-{
+-
+-public:
+-
+- enum TextFilterFields
+- {
+- None = 0x00,
+- ImageName = 0x01,
+- ImageTitle = 0x02,
+- ImageComment = 0x04,
+- TagName = 0x08,
+- AlbumName = 0x10,
+- ImageAspectRatio = 0x20,
+- ImagePixelSize = 0x40,
+- All = ImageName | ImageTitle | ImageComment | TagName | AlbumName | ImageAspectRatio | ImagePixelSize
+- };
+-
+-public:
+-
+- SearchTextFilterSettings()
+- {
+- textFields = None;
+- }
+-
+- explicit SearchTextFilterSettings(const SearchTextSettings& settings)
+- {
+- caseSensitive = settings.caseSensitive;
+- text = settings.text;
+- textFields = None;
+- }
+-
+- TextFilterFields textFields;
+-};
+-
+-// ---------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT ImageFilterSettings
+-{
+-public:
+-
+- ImageFilterSettings();
+-
+- /**
+- * Returns true if the given ImageInfo matches the filter criteria.
+- * Optionally, foundText is set to true if it matched by text search.
+- */
+- bool matches(const ImageInfo& info, bool* const foundText = 0) const;
+-
+-public:
+-
+- /// --- Tags filter ---
+-
+- /// Possible logical matching condition used to sort tags id.
+- enum MatchingCondition
+- {
+- OrCondition,
+- AndCondition
+- };
+-
+- void setTagFilter(const QList<int>& includedTags,
+- const QList<int>& excludedTags,
+- MatchingCondition matchingCond,
+- bool showUnTagged,
+- const QList<int>& clTagIds,
+- const QList<int>& plTagIds);
+-
+-public:
+-
+- /// --- Rating filter ---
+-
+- /// Possible conditions used to filter rating: >=, =, <=
+- enum RatingCondition
+- {
+- GreaterEqualCondition,
+- EqualCondition,
+- LessEqualCondition
+- };
+-
+- void setRatingFilter(int rating, RatingCondition ratingCond, bool isUnratedExcluded);
+-
+-public:
+-
+- /// --- Date filter ---
+- void setDayFilter(const QList<QDateTime>& days);
+-
+-public:
+-
+- /// --- Text filter ---
+- void setTextFilter(const SearchTextFilterSettings& settings);
+- void setTagNames(const QHash<int, QString>& tagNameHash);
+- void setAlbumNames(const QHash<int, QString>& albumNameHash);
+-
+-public:
+-
+- /// --- Mime filter ---
+- void setMimeTypeFilter(int mimeTypeFilter);
+-
+-public:
+-
+- /// --- Geolocation filter
+- enum GeolocationCondition
+- {
+- GeolocationNoFilter = 0,
+- GeolocationNoCoordinates = 1 << 1,
+- GeolocationHasCoordinates = 1 << 2
+- };
+-
+- void setGeolocationFilter(const GeolocationCondition& condition);
+-
+-public:
+-
+- /// Returns if the day is a filter criteria
+- bool isFilteringByDay() const;
+-
+- /// Returns if the type mime is a filter criteria
+- bool isFilteringByTypeMime() const;
+-
+- /// Returns whether geolocation is a filter criteria
+- bool isFilteringByGeolocation() const;
+-
+- /// Returns if the rating is a filter criteria
+- bool isFilteringByRating() const;
+-
+- /// Returns if the pick labels is a filter criteria
+- bool isFilteringByPickLabels() const;
+-
+- /// Returns if the color labels is a filter criteria
+- bool isFilteringByColorLabels() const;
+-
+- /// Returns if the tag is a filter criteria
+- bool isFilteringByTags() const;
+-
+- /// Returns if the text (including comment) is a filter criteria
+- bool isFilteringByText() const;
+-
+- /// Returns if images will be filtered by these criteria at all
+- bool isFiltering() const;
+-
+-public:
+-
+- /// --- URL whitelist filter
+- void setUrlWhitelist(const QList<QUrl>& urlList, const QString& id);
+-
+-public:
+-
+- /// --- ID whitelist filter
+- void setIdWhitelist(const QList<qlonglong>& idList, const QString& id);
+-
+-public:
+-
+- /// --- Change notification ---
+-
+- /** Returns database fields a change in which would affect the current filtering.
+- * To find out if an image tag change affects filtering, test isFilteringByTags().
+- * The text filter will also be affected by changes in tags and album names.
+- */
+- DatabaseFields::Set watchFlags() const;
+-
+-private:
+-
+- /**
+- * @brief Returns whether some internal filtering (whitelist by id or URL) or normal filtering is going on
+- */
+- bool isFilteringInternally() const;
+-
+-private:
+-
+- /// --- Tags filter ---
+- bool m_untaggedFilter;
+- QList<int> m_includeTagFilter;
+- QList<int> m_excludeTagFilter;
+- MatchingCondition m_matchingCond;
+- QList<int> m_colorLabelTagFilter;
+- QList<int> m_pickLabelTagFilter;
+-
+- /// --- Rating filter ---
+- int m_ratingFilter;
+- RatingCondition m_ratingCond;
+- bool m_isUnratedExcluded;
+-
+- /// --- Date filter ---
+- QMap<QDateTime, bool> m_dayFilter;
+-
+- /// --- Text filter ---
+- SearchTextFilterSettings m_textFilterSettings;
+-
+- /// Helpers for text search: Set these if you want to search album or tag names with text search
+- QHash<int, QString> m_tagNameHash;
+- QHash<int, QString> m_albumNameHash;
+-
+- /// --- Mime filter ---
+- MimeFilter::TypeMimeFilter m_mimeTypeFilter;
+-
+- /// --- Geolocation filter
+- GeolocationCondition m_geolocationCondition;
+-
+- /// --- URL whitelist filter
+- QHash<QString,QList<QUrl>> m_urlWhitelists;
+-
+- /// --- ID whitelist filter
+- QHash<QString,QList<qlonglong> > m_idWhitelists;
+-};
+-
+-// ---------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT VersionImageFilterSettings
+-{
+-public:
+-
+- VersionImageFilterSettings();
+- explicit VersionImageFilterSettings(const VersionManagerSettings& settings);
+-
+- bool operator==(const VersionImageFilterSettings& other) const;
+-
+- /**
+- * Returns true if the given ImageInfo matches the filter criteria.
+- */
+- bool matches(const ImageInfo& info) const;
+-
+- bool isHiddenBySettings(const ImageInfo& info) const;
+- bool isExemptedBySettings(const ImageInfo& info) const;
+-
+- /// --- Tags filter ---
+-
+- void setVersionManagerSettings(const VersionManagerSettings& settings);
+-
+- /**
+- * Add list with exceptions: These images will be exempted from filtering by this filter
+- */
+- void setExceptionList(const QList<qlonglong>& idlist, const QString& id);
+-
+- /// Returns if images will be filtered by these criteria at all
+- bool isFiltering() const;
+-
+- /// Returns if the tag is a filter criteria
+- bool isFilteringByTags() const;
+-
+- /// DatabaseFields::Set watchFlags() const: Would return 0
+-
+-protected:
+-
+- QList<int> m_excludeTagFilter;
+- int m_includeTagFilter;
+- int m_exceptionTagFilter;
+- QHash<QString,QList<qlonglong> > m_exceptionLists;
+-};
+-
+-// ---------------------------------------------------------------------------------------
+-
+-class DIGIKAM_DATABASE_EXPORT GroupImageFilterSettings
+-{
+-public:
+-
+- GroupImageFilterSettings();
+-
+- bool operator==(const GroupImageFilterSettings& other) const;
+-
+- /**
+- * Returns true if the given ImageInfo matches the filter criteria.
+- */
+- bool matches(const ImageInfo& info) const;
+-
+- /**
+- * Open or close a group.
+- */
+- void setOpen(qlonglong group, bool open);
+- bool isOpen(qlonglong group) const;
+-
+- /**
+- * Open all groups
+- */
+- void setAllOpen(bool open);
+- bool isAllOpen() const;
+-
+- /// Returns if images will be filtered by these criteria at all
+- bool isFiltering() const;
+-
+- DatabaseFields::Set watchFlags() const;
+-
+-protected:
+-
+- bool m_allOpen;
+- QSet<qlonglong> m_openGroups;
+-};
+-
+-} // namespace Digikam
+-
+-Q_DECLARE_METATYPE(Digikam::ImageFilterSettings::GeolocationCondition)
+-
+-#endif // IMAGEFILTERSETTINGS_H
+diff --git a/libs/models/imagelistmodel.cpp b/libs/models/imagelistmodel.cpp
+deleted file mode 100644
+index fafce34..0000000
+--- a/libs/models/imagelistmodel.cpp
++++ /dev/null
+@@ -1,70 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2010-12-06
+- * Description : An image model based on a static list
+- *
+- * Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagelistmodel.h"
+-
+-// Local includes
+-
+-#include "digikam_debug.h"
+-#include "coredbaccess.h"
+-#include "coredbchangesets.h"
+-#include "coredbwatch.h"
+-#include "imageinfo.h"
+-#include "imageinfolist.h"
+-
+-namespace Digikam
+-{
+-
+-ImageListModel::ImageListModel(QObject* parent)
+- : ImageThumbnailModel(parent)
+-{
+- connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)),
+- this, SLOT(slotCollectionImageChange(CollectionImageChangeset)));
+-}
+-
+-ImageListModel::~ImageListModel()
+-{
+-}
+-
+-void ImageListModel::slotCollectionImageChange(const CollectionImageChangeset& changeset)
+-{
+- if (isEmpty())
+- {
+- return;
+- }
+-
+- switch (changeset.operation())
+- {
+- case CollectionImageChangeset::Added:
+- break;
+- case CollectionImageChangeset::Removed:
+- case CollectionImageChangeset::RemovedAll:
+- removeImageInfos(ImageInfoList(changeset.ids()));
+- break;
+-
+- default:
+- break;
+- }
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagelistmodel.h b/libs/models/imagelistmodel.h
+deleted file mode 100644
+index a225b1b..0000000
+--- a/libs/models/imagelistmodel.h
++++ /dev/null
+@@ -1,63 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2010-12-06
+- * Description : An image model based on a static list
+- *
+- * Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGELISTMODEL_H
+-#define IMAGELISTMODEL_H
+-
+-// Local includes
+-
+-#include "imagethumbnailmodel.h"
+-#include "digikam_export.h"
+-
+-namespace Digikam
+-{
+-
+-class ImageChangeset;
+-class CollectionImageChangeset;
+-
+-class DIGIKAM_DATABASE_EXPORT ImageListModel : public ImageThumbnailModel
+-{
+- Q_OBJECT
+-
+-public:
+-
+- explicit ImageListModel(QObject* parent = 0);
+- ~ImageListModel();
+-
+- // NOTE: necessary methods to add and remove ImageInfos to the model are inherited from ImageModel
+-
+-Q_SIGNALS:
+-
+- /**
+- * Emitted when images are removed from the model because they are removed in the database
+- */
+- void imageInfosRemoved(const QList<ImageInfo>& infos);
+-
+-protected Q_SLOTS:
+-
+- void slotCollectionImageChange(const CollectionImageChangeset& changeset);
+-};
+-
+-} // namespace Digikam
+-
+-#endif // IMAGELISTMODEL_H
+diff --git a/libs/models/imagemodel.cpp b/libs/models/imagemodel.cpp
+deleted file mode 100644
+index 41b43cf..0000000
+--- a/libs/models/imagemodel.cpp
++++ /dev/null
+@@ -1,1368 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagemodel.h"
+-
+-// Qt includes
+-
+-#include <QHash>
+-#include <QItemSelection>
+-
+-// Local includes
+-
+-#include "digikam_debug.h"
+-#include "coredbchangesets.h"
+-#include "coredbfields.h"
+-#include "coredbwatch.h"
+-#include "imageinfo.h"
+-#include "imageinfolist.h"
+-#include "abstractitemdragdrophandler.h"
+-
+-namespace Digikam
+-{
+-
+-class ImageModel::Private
+-{
+-public:
+-
+- Private()
+- {
+- preprocessor = 0;
+- keepFilePathCache = false;
+- sendRemovalSignals = false;
+- incrementalUpdater = 0;
+- refreshing = false;
+- reAdding = false;
+- incrementalRefreshRequested = false;
+- }
+-
+- ImageInfoList infos;
+- QList<QVariant> extraValues;
+- QHash<qlonglong, int> idHash;
+-
+- bool keepFilePathCache;
+- QHash<QString, qlonglong> filePathHash;
+-
+- bool sendRemovalSignals;
+-
+- QObject* preprocessor;
+- bool refreshing;
+- bool reAdding;
+- bool incrementalRefreshRequested;
+-
+- DatabaseFields::Set watchFlags;
+-
+- class ImageModelIncrementalUpdater* incrementalUpdater;
+-
+- ImageInfoList pendingInfos;
+- QList<QVariant> pendingExtraValues;
+-
+- inline bool isValid(const QModelIndex& index)
+- {
+- if (!index.isValid())
+- {
+- return false;
+- }
+-
+- if (index.row() < 0 || index.row() >= infos.size())
+- {
+- qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index" << index;
+- return false;
+- }
+-
+- return true;
+- }
+- inline bool extraValueValid(const QModelIndex& index)
+- {
+- // we assume isValid() being called before, no duplicate checks
+- if (index.row() >= extraValues.size())
+- {
+- qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid index for extraData" << index;
+- return false;
+- }
+-
+- return true;
+- }
+-};
+-
+-typedef QPair<int, int> IntPair; // to make foreach macro happy
+-typedef QList<IntPair> IntPairList;
+-
+-class ImageModelIncrementalUpdater
+-{
+-public:
+-
+- explicit ImageModelIncrementalUpdater(ImageModel::Private* d);
+-
+- void appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void aboutToBeRemovedInModel(const IntPairList& aboutToBeRemoved);
+- QList<IntPair> oldIndexes();
+-
+- static QList<IntPair> toContiguousPairs(const QList<int>& ids);
+-
+-public:
+-
+- QHash<qlonglong, int> oldIds;
+- QList<QVariant> oldExtraValues;
+- QList<ImageInfo> newInfos;
+- QList<QVariant> newExtraValues;
+- QList<IntPairList> modelRemovals;
+-};
+-
+-ImageModel::ImageModel(QObject* parent)
+- : QAbstractListModel(parent),
+- d(new Private)
+-{
+- connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)),
+- this, SLOT(slotImageChange(ImageChangeset)));
+-
+- connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)),
+- this, SLOT(slotImageTagChange(ImageTagChangeset)));
+-}
+-
+-ImageModel::~ImageModel()
+-{
+- delete d->incrementalUpdater;
+- delete d;
+-}
+-
+-// ------------ Access methods -------------
+-
+-void ImageModel::setKeepsFilePathCache(bool keepCache)
+-{
+- d->keepFilePathCache = keepCache;
+-}
+-
+-bool ImageModel::keepsFilePathCache() const
+-{
+- return d->keepFilePathCache;
+-}
+-
+-bool ImageModel::isEmpty() const
+-{
+- return d->infos.isEmpty();
+-}
+-
+-void ImageModel::setWatchFlags(const DatabaseFields::Set& set)
+-{
+- d->watchFlags = set;
+-}
+-
+-ImageInfo ImageModel::imageInfo(const QModelIndex& index) const
+-{
+- if (!d->isValid(index))
+- {
+- return ImageInfo();
+- }
+-
+- return d->infos.at(index.row());
+-}
+-
+-ImageInfo& ImageModel::imageInfoRef(const QModelIndex& index) const
+-{
+- return d->infos[index.row()];
+-}
+-
+-qlonglong ImageModel::imageId(const QModelIndex& index) const
+-{
+- if (!d->isValid(index))
+- {
+- return 0;
+- }
+-
+- return d->infos.at(index.row()).id();
+-}
+-
+-QList<ImageInfo> ImageModel::imageInfos(const QList<QModelIndex>& indexes) const
+-{
+- QList<ImageInfo> infos;
+-
+- foreach(const QModelIndex& index, indexes)
+- {
+- infos << imageInfo(index);
+- }
+-
+- return infos;
+-}
+-
+-QList<qlonglong> ImageModel::imageIds(const QList<QModelIndex>& indexes) const
+-{
+- QList<qlonglong> ids;
+-
+- foreach(const QModelIndex& index, indexes)
+- {
+- ids << imageId(index);
+- }
+-
+- return ids;
+-}
+-
+-ImageInfo ImageModel::imageInfo(int row) const
+-{
+- if (row >= d->infos.size())
+- {
+- return ImageInfo();
+- }
+-
+- return d->infos.at(row);
+-}
+-
+-ImageInfo& ImageModel::imageInfoRef(int row) const
+-{
+- return d->infos[row];
+-}
+-
+-qlonglong ImageModel::imageId(int row) const
+-{
+- if (row < 0 || row >= d->infos.size())
+- {
+- return -1;
+- }
+-
+- return d->infos.at(row).id();
+-}
+-
+-QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info) const
+-{
+- return indexForImageId(info.id());
+-}
+-
+-QModelIndex ImageModel::indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const
+-{
+- return indexForImageId(info.id(), extraValue);
+-}
+-
+-QList<QModelIndex> ImageModel::indexesForImageInfo(const ImageInfo& info) const
+-{
+- return indexesForImageId(info.id());
+-}
+-
+-QModelIndex ImageModel::indexForImageId(qlonglong id) const
+-{
+- int index = d->idHash.value(id, -1);
+-
+- if (index != -1)
+- {
+- return createIndex(index, 0);
+- }
+-
+- return QModelIndex();
+-}
+-
+-QModelIndex ImageModel::indexForImageId(qlonglong id, const QVariant& extraValue) const
+-{
+- if (d->extraValues.isEmpty())
+- return indexForImageId(id);
+-
+- QHash<qlonglong, int>::const_iterator it;
+-
+- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
+- {
+- if (d->extraValues.at(it.value()) == extraValue)
+- return createIndex(it.value(), 0);
+- }
+-
+- return QModelIndex();
+-}
+-
+-QList<QModelIndex> ImageModel::indexesForImageId(qlonglong id) const
+-{
+- QList<QModelIndex> indexes;
+- QHash<qlonglong, int>::const_iterator it;
+-
+- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
+- {
+- indexes << createIndex(it.value(), 0);
+- }
+-
+- return indexes;
+-}
+-
+-int ImageModel::numberOfIndexesForImageInfo(const ImageInfo& info) const
+-{
+- return numberOfIndexesForImageId(info.id());
+-}
+-
+-int ImageModel::numberOfIndexesForImageId(qlonglong id) const
+-{
+- if (d->extraValues.isEmpty())
+- {
+- return 0;
+- }
+-
+- int count = 0;
+- QHash<qlonglong,int>::const_iterator it;
+-
+- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
+- {
+- ++count;
+- }
+-
+- return count;
+-}
+-
+-// static method
+-ImageInfo ImageModel::retrieveImageInfo(const QModelIndex& index)
+-{
+- if (!index.isValid())
+- {
+- return ImageInfo();
+- }
+-
+- ImageModel* const model = index.data(ImageModelPointerRole).value<ImageModel*>();
+- int row = index.data(ImageModelInternalId).toInt();
+-
+- if (!model)
+- {
+- return ImageInfo();
+- }
+-
+- return model->imageInfo(row);
+-}
+-
+-// static method
+-qlonglong ImageModel::retrieveImageId(const QModelIndex& index)
+-{
+- if (!index.isValid())
+- {
+- return 0;
+- }
+-
+- ImageModel* const model = index.data(ImageModelPointerRole).value<ImageModel*>();
+- int row = index.data(ImageModelInternalId).toInt();
+-
+- if (!model)
+- {
+- return 0;
+- }
+-
+- return model->imageId(row);
+-}
+-
+-QModelIndex ImageModel::indexForPath(const QString& filePath) const
+-{
+- if (d->keepFilePathCache)
+- {
+- return indexForImageId(d->filePathHash.value(filePath));
+- }
+- else
+- {
+- const int size = d->infos.size();
+-
+- for (int i=0; i<size; ++i)
+- {
+- if (d->infos.at(i).filePath() == filePath)
+- {
+- return createIndex(i, 0);
+- }
+- }
+- }
+-
+- return QModelIndex();
+-}
+-
+-QList<QModelIndex> ImageModel::indexesForPath(const QString& filePath) const
+-{
+- if (d->keepFilePathCache)
+- {
+- return indexesForImageId(d->filePathHash.value(filePath));
+- }
+- else
+- {
+- QList<QModelIndex> indexes;
+- const int size = d->infos.size();
+-
+- for (int i=0; i<size; ++i)
+- {
+- if (d->infos.at(i).filePath() == filePath)
+- {
+- indexes << createIndex(i, 0);
+- }
+- }
+-
+- return indexes;
+- }
+-}
+-
+-ImageInfo ImageModel::imageInfo(const QString& filePath) const
+-{
+- if (d->keepFilePathCache)
+- {
+- qlonglong id = d->filePathHash.value(filePath);
+-
+- if (id)
+- {
+- int index = d->idHash.value(id, -1);
+-
+- if (index != -1)
+- {
+- return d->infos.at(index);
+- }
+- }
+- }
+- else
+- {
+- foreach(const ImageInfo& info, d->infos)
+- {
+- if (info.filePath() == filePath)
+- {
+- return info;
+- }
+- }
+- }
+-
+- return ImageInfo();
+-}
+-
+-QList<ImageInfo> ImageModel::imageInfos(const QString& filePath) const
+-{
+- QList<ImageInfo> infos;
+-
+- if (d->keepFilePathCache)
+- {
+- qlonglong id = d->filePathHash.value(filePath);
+-
+- if (id)
+- {
+- foreach(int index, d->idHash.values(id))
+- {
+- infos << d->infos.at(index);
+- }
+- }
+- }
+- else
+- {
+- foreach(const ImageInfo& info, d->infos)
+- {
+- if (info.filePath() == filePath)
+- {
+- infos << info;
+- }
+- }
+- }
+-
+- return infos;
+-}
+-
+-void ImageModel::addImageInfo(const ImageInfo& info)
+-{
+- addImageInfos(QList<ImageInfo>() << info, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfos(const QList<ImageInfo>& infos)
+-{
+- addImageInfos(infos, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (infos.isEmpty())
+- {
+- return;
+- }
+-
+- if (d->incrementalUpdater)
+- {
+- d->incrementalUpdater->appendInfos(infos, extraValues);
+- }
+- else
+- {
+- appendInfos(infos, extraValues);
+- }
+-}
+-
+-void ImageModel::addImageInfoSynchronously(const ImageInfo& info)
+-{
+- addImageInfosSynchronously(QList<ImageInfo>() << info, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos)
+-{
+- addImageInfos(infos, QList<QVariant>());
+-}
+-
+-void ImageModel::addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (infos.isEmpty())
+- {
+- return;
+- }
+-
+- publiciseInfos(infos, extraValues);
+- emit processAdded(infos, extraValues);
+-}
+-
+-void ImageModel::ensureHasImageInfo(const ImageInfo& info)
+-{
+- ensureHasImageInfos(QList<ImageInfo>() << info, QList<QVariant>());
+-}
+-
+-void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos)
+-{
+- ensureHasImageInfos(infos, QList<QVariant>());
+-}
+-
+-void ImageModel::ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (extraValues.isEmpty())
+- {
+- if (!d->pendingExtraValues.isEmpty())
+- {
+- qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
+- return;
+- }
+- }
+- else
+- {
+- if (d->pendingInfos.size() != d->pendingExtraValues.size())
+- {
+- qCDebug(DIGIKAM_GENERAL_LOG) << "ExtraValue / No Extra Value mismatch. Ignoring added infos.";
+- return;
+- }
+- }
+-
+- d->pendingInfos << infos;
+- d->pendingExtraValues << extraValues;
+- cleanSituationChecks();
+-}
+-
+-void ImageModel::clearImageInfos()
+-{
+- d->infos.clear();
+- d->extraValues.clear();
+- d->idHash.clear();
+- d->filePathHash.clear();
+- delete d->incrementalUpdater;
+- d->incrementalUpdater = 0;
+- d->pendingInfos.clear();
+- d->pendingExtraValues.clear();
+- d->refreshing = false;
+- d->reAdding = false;
+- d->incrementalRefreshRequested = false;
+-
+- beginResetModel();
+- endResetModel();
+-
+- imageInfosCleared();
+-}
+-
+-void ImageModel::setImageInfos(const QList<ImageInfo>& infos)
+-{
+- clearImageInfos();
+- addImageInfos(infos);
+-}
+-
+-QList<ImageInfo> ImageModel::imageInfos() const
+-{
+- return d->infos;
+-}
+-
+-QList<qlonglong> ImageModel::imageIds() const
+-{
+- return d->idHash.keys();
+-}
+-
+-bool ImageModel::hasImage(qlonglong id) const
+-{
+- return d->idHash.contains(id);
+-}
+-
+-bool ImageModel::hasImage(const ImageInfo& info) const
+-{
+- return d->idHash.contains(info.id());
+-}
+-
+-bool ImageModel::hasImage(const ImageInfo& info, const QVariant& extraValue) const
+-{
+- return hasImage(info.id(), extraValue);
+-}
+-
+-bool ImageModel::hasImage(qlonglong id, const QVariant& extraValue) const
+-{
+- if (d->extraValues.isEmpty())
+- return hasImage(id);
+-
+- QHash<qlonglong, int>::const_iterator it;
+-
+- for (it = d->idHash.constFind(id); it != d->idHash.constEnd() && it.key() == id; ++it)
+- {
+- if (d->extraValues.at(it.value()) == extraValue)
+- return true;
+- }
+-
+- return false;;
+-}
+-
+-QList<ImageInfo> ImageModel::uniqueImageInfos() const
+-{
+- if (d->extraValues.isEmpty())
+- {
+- return d->infos;
+- }
+-
+- QList<ImageInfo> uniqueInfos;
+- const int size = d->infos.size();
+-
+- for (int i=0; i<size; ++i)
+- {
+- const ImageInfo& info = d->infos.at(i);
+-
+- if (d->idHash.value(info.id()) == i)
+- {
+- uniqueInfos << info;
+- }
+- }
+-
+- return uniqueInfos;
+-}
+-
+-void ImageModel::emitDataChangedForAll()
+-{
+- if (d->infos.isEmpty())
+- {
+- return;
+- }
+-
+- QModelIndex first = createIndex(0, 0);
+- QModelIndex last = createIndex(d->infos.size() - 1, 0);
+- emit dataChanged(first, last);
+-}
+-
+-void ImageModel::emitDataChangedForSelection(const QItemSelection& selection)
+-{
+- if (!selection.isEmpty())
+- {
+- foreach(const QItemSelectionRange& range, selection)
+- {
+- emit dataChanged(range.topLeft(), range.bottomRight());
+- }
+- }
+-}
+-
+-void ImageModel::ensureHasGroupedImages(const ImageInfo& groupLeader)
+-{
+- ensureHasImageInfos(groupLeader.groupedImages());
+-}
+-
+-// ------------ Preprocessing -------------
+-
+-void ImageModel::setPreprocessor(QObject* preprocessor)
+-{
+- unsetPreprocessor(d->preprocessor);
+- d->preprocessor = preprocessor;
+-}
+-
+-void ImageModel::unsetPreprocessor(QObject* preprocessor)
+-{
+- if (preprocessor && d->preprocessor == preprocessor)
+- {
+- disconnect(this, SIGNAL(preprocess(QList<ImageInfo>,QList<QVariant>)), 0, 0);
+- disconnect(d->preprocessor, 0, this, SLOT(reAddImageInfos(QList<ImageInfo>,QList<QVariant>)));
+- disconnect(d->preprocessor, 0, this, SLOT(reAddingFinished()));
+- }
+-}
+-
+-void ImageModel::appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (infos.isEmpty())
+- {
+- return;
+- }
+-
+- if (d->preprocessor)
+- {
+- d->reAdding = true;
+- emit preprocess(infos, extraValues);
+- }
+- else
+- {
+- publiciseInfos(infos, extraValues);
+- }
+-}
+-
+-void ImageModel::appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- // This method does deduplication. It is private because in context of readding or refreshing it is of no use.
+-
+- if (extraValues.isEmpty())
+- {
+- QList<ImageInfo> checkedInfos;
+-
+- foreach (const ImageInfo& info, infos)
+- {
+- if (!hasImage(info))
+- {
+- checkedInfos << info;
+- }
+- }
+-
+- appendInfos(checkedInfos, QList<QVariant>());
+- }
+- else
+- {
+- QList<ImageInfo> checkedInfos;
+- QList<QVariant> checkedExtraValues;
+- const int size = infos.size();
+-
+- for (int i=0; i<size; i++)
+- {
+- if (!hasImage(infos[i], extraValues[i]))
+- {
+- checkedInfos << infos[i];
+- checkedExtraValues << extraValues[i];
+- }
+- }
+-
+- appendInfos(checkedInfos, checkedExtraValues);
+- }
+-}
+-
+-void ImageModel::reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- // addImageInfos -> appendInfos -> preprocessor -> reAddImageInfos
+- publiciseInfos(infos, extraValues);
+-}
+-
+-void ImageModel::reAddingFinished()
+-{
+- d->reAdding = false;
+- cleanSituationChecks();
+-}
+-
+-void ImageModel::startRefresh()
+-{
+- d->refreshing = true;
+-}
+-
+-void ImageModel::finishRefresh()
+-{
+- d->refreshing = false;
+- cleanSituationChecks();
+-}
+-
+-bool ImageModel::isRefreshing() const
+-{
+- return d->refreshing;
+-}
+-
+-void ImageModel::cleanSituationChecks()
+-{
+- // For starting an incremental refresh we want a clear situation:
+- // Any remaining batches from non-incremental refreshing subclasses have been received in appendInfos(),
+- // any batches sent to preprocessor for re-adding have been re-added.
+- if (d->refreshing || d->reAdding)
+- {
+- return;
+- }
+-
+- if (!d->pendingInfos.isEmpty())
+- {
+- appendInfosChecked(d->pendingInfos, d->pendingExtraValues);
+- d->pendingInfos.clear();
+- d->pendingExtraValues.clear();
+- cleanSituationChecks();
+- return;
+- }
+-
+- if (d->incrementalRefreshRequested)
+- {
+- d->incrementalRefreshRequested = false;
+- emit readyForIncrementalRefresh();
+- }
+- else
+- {
+- emit allRefreshingFinished();
+- }
+-}
+-
+-void ImageModel::publiciseInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (infos.isEmpty())
+- {
+- return;
+- }
+-
+- Q_ASSERT(infos.size() == extraValues.size() || (extraValues.isEmpty() && d->extraValues.isEmpty()));
+-
+- emit imageInfosAboutToBeAdded(infos);
+- const int firstNewIndex = d->infos.size();
+- const int lastNewIndex = d->infos.size() + infos.size() - 1;
+- beginInsertRows(QModelIndex(), firstNewIndex, lastNewIndex);
+- d->infos << infos;
+- d->extraValues << extraValues;
+-
+- for (int i=firstNewIndex; i<=lastNewIndex; ++i)
+- {
+- const ImageInfo& info = d->infos.at(i);
+- qlonglong id = info.id();
+- d->idHash.insertMulti(id, i);
+-
+- if (d->keepFilePathCache)
+- {
+- d->filePathHash[info.filePath()] = id;
+- }
+- }
+-
+- endInsertRows();
+- emit imageInfosAdded(infos);
+-}
+-
+-void ImageModel::requestIncrementalRefresh()
+-{
+- if (d->reAdding)
+- {
+- d->incrementalRefreshRequested = true;
+- }
+- else
+- {
+- emit readyForIncrementalRefresh();
+- }
+-}
+-
+-bool ImageModel::hasIncrementalRefreshPending() const
+-{
+- return d->incrementalRefreshRequested;
+-}
+-
+-void ImageModel::startIncrementalRefresh()
+-{
+- delete d->incrementalUpdater;
+-
+- d->incrementalUpdater = new ImageModelIncrementalUpdater(d);
+-}
+-
+-void ImageModel::finishIncrementalRefresh()
+-{
+- if (!d->incrementalUpdater)
+- {
+- return;
+- }
+-
+- // remove old entries
+- QList<QPair<int, int> > pairs = d->incrementalUpdater->oldIndexes();
+- removeRowPairs(pairs);
+-
+- // add new indexes
+- appendInfos(d->incrementalUpdater->newInfos, d->incrementalUpdater->newExtraValues);
+-
+- delete d->incrementalUpdater;
+- d->incrementalUpdater = 0;
+-}
+-
+-void ImageModel::removeIndex(const QModelIndex& index)
+-{
+- removeIndexes(QList<QModelIndex>() << index);
+-}
+-
+-void ImageModel::removeIndexes(const QList<QModelIndex>& indexes)
+-{
+- QList<int> listIndexes;
+-
+- foreach(const QModelIndex& index, indexes)
+- {
+- if (d->isValid(index))
+- {
+- listIndexes << index.row();
+- }
+- }
+-
+- if (listIndexes.isEmpty())
+- {
+- return;
+- }
+-
+- removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
+-}
+-
+-void ImageModel::removeImageInfo(const ImageInfo& info)
+-{
+- removeImageInfos(QList<ImageInfo>() << info);
+-}
+-
+-void ImageModel::removeImageInfos(const QList<ImageInfo>& infos)
+-{
+- QList<int> listIndexes;
+-
+- foreach(const ImageInfo& info, infos)
+- {
+- QModelIndex index = indexForImageId(info.id());
+-
+- if (index.isValid())
+- {
+- listIndexes << index.row();
+- }
+- }
+- removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
+-}
+-
+-void ImageModel::removeImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (extraValues.isEmpty())
+- {
+- removeImageInfos(infos);
+- return;
+- }
+-
+- QList<int> listIndexes;
+-
+- for (int i=0; i<infos.size(); ++i)
+- {
+- QModelIndex index = indexForImageId(infos.at(i).id(), extraValues.at(i));
+-
+- if (index.isValid())
+- {
+- listIndexes << index.row();
+- }
+- }
+-
+- removeRowPairsWithCheck(ImageModelIncrementalUpdater::toContiguousPairs(listIndexes));
+-}
+-
+-void ImageModel::setSendRemovalSignals(bool send)
+-{
+- d->sendRemovalSignals = send;
+-}
+-
+-template <class List, typename T>
+-static bool pairsContain(const List& list, T value)
+-{
+- typename List::const_iterator middle;
+- typename List::const_iterator begin = list.begin();
+- typename List::const_iterator end = list.end();
+- int n = int(end - begin);
+- int half;
+-
+- while (n > 0)
+- {
+- half = n >> 1;
+- middle = begin + half;
+-
+- if (middle->first <= value && middle->second >= value)
+- {
+- return true;
+- }
+- else if (middle->second < value)
+- {
+- begin = middle + 1;
+- n -= half + 1;
+- }
+- else
+- {
+- n = half;
+- }
+- }
+-
+- return false;
+-}
+-
+-void ImageModel::removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove)
+-{
+- if (d->incrementalUpdater)
+- {
+- d->incrementalUpdater->aboutToBeRemovedInModel(toRemove);
+- }
+-
+- removeRowPairs(toRemove);
+-}
+-
+-void ImageModel::removeRowPairs(const QList<QPair<int, int> >& toRemove)
+-{
+- if (toRemove.isEmpty())
+- {
+- return;
+- }
+-
+- // Remove old indexes
+- // Keep in mind that when calling beginRemoveRows all structures announced to be removed
+- // must still be valid, and this includes our hashes as well, which limits what we can optimize
+-
+- int removedRows = 0, offset = 0;
+- typedef QPair<int, int> IntPair; // to make foreach macro happy
+-
+- foreach(const IntPair& pair, toRemove)
+- {
+- const int begin = pair.first - offset;
+- const int end = pair.second - offset; // inclusive
+- removedRows = end - begin + 1;
+-
+- // when removing from the list, all subsequent indexes are affected
+- offset += removedRows;
+-
+- QList<ImageInfo> removedInfos;
+-
+- if (d->sendRemovalSignals)
+- {
+- qCopy(d->infos.begin() + begin, d->infos.begin() + end, removedInfos.begin());
+- emit imageInfosAboutToBeRemoved(removedInfos);
+- }
+-
+- imageInfosAboutToBeRemoved(begin, end);
+- beginRemoveRows(QModelIndex(), begin, end);
+-
+- // update idHash - which points to indexes of d->infos, and these change now!
+- QHash<qlonglong, int>::iterator it;
+-
+- for (it = d->idHash.begin(); it != d->idHash.end(); )
+- {
+- if (it.value() >= begin)
+- {
+- if (it.value() > end)
+- {
+- // after the removed interval: adjust index
+- it.value() -= removedRows;
+- }
+- else
+- {
+- // in the removed interval
+- it = d->idHash.erase(it);
+- continue;
+- }
+- }
+-
+- ++it;
+- }
+-
+- // remove from list
+- d->infos.erase(d->infos.begin() + begin, d->infos.begin() + (end + 1));
+-
+- if (!d->extraValues.isEmpty())
+- {
+- d->extraValues.erase(d->extraValues.begin() + begin, d->extraValues.begin() + (end + 1));
+- }
+-
+- endRemoveRows();
+-
+- if (d->sendRemovalSignals)
+- {
+- emit imageInfosRemoved(removedInfos);
+- }
+- }
+-
+- // tidy up: remove old indexes from file path hash now
+- if (d->keepFilePathCache)
+- {
+- QHash<QString, qlonglong>::iterator it;
+-
+- for (it = d->filePathHash.begin(); it != d->filePathHash.end(); )
+- {
+- if (pairsContain(toRemove, it.value()))
+- {
+- it = d->filePathHash.erase(it);
+- }
+- else
+- {
+- ++it;
+- }
+- }
+- }
+-}
+-
+-ImageModelIncrementalUpdater::ImageModelIncrementalUpdater(ImageModel::Private* d)
+-{
+- oldIds = d->idHash;
+- oldExtraValues = d->extraValues;
+-}
+-
+-void ImageModelIncrementalUpdater::appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues)
+-{
+- if (extraValues.isEmpty())
+- {
+- foreach(const ImageInfo& info, infos)
+- {
+- QHash<qlonglong,int>::iterator it = oldIds.find(info.id());
+-
+- if (it != oldIds.end())
+- {
+- oldIds.erase(it);
+- }
+- else
+- {
+- newInfos << info;
+- }
+- }
+- }
+- else
+- {
+- for (int i=0; i<infos.size(); ++i)
+- {
+- const ImageInfo& info = infos.at(i);
+- bool found = false;
+- QHash<qlonglong,int>::iterator it;
+-
+- for (it = oldIds.find(info.id()); it != oldIds.end() && it.key() == info.id(); ++it)
+- {
+- // first check is for bug #262596. Not sure if needed.
+- if (it.value() < oldExtraValues.size() && extraValues.at(i) == oldExtraValues.at(it.value()))
+- {
+- found = true;
+- break;
+- }
+- }
+-
+- if (found)
+- {
+- oldIds.erase(it);
+- // do not erase from oldExtraValues - oldIds is a hash id -> index.
+- }
+- else
+- {
+- newInfos << info;
+- newExtraValues << extraValues.at(i);
+- }
+- }
+- }
+-}
+-
+-void ImageModelIncrementalUpdater::aboutToBeRemovedInModel(const IntPairList& toRemove)
+-{
+- modelRemovals << toRemove;
+-}
+-
+-QList<QPair<int, int> > ImageModelIncrementalUpdater::oldIndexes()
+-{
+- // first, apply all changes to indexes by direct removal in model
+- // while the updater was active
+- foreach(const IntPairList& list, modelRemovals)
+- {
+- int removedRows = 0, offset = 0;
+-
+- foreach(const IntPair& pair, list)
+- {
+- const int begin = pair.first - offset;
+- const int end = pair.second - offset; // inclusive
+- removedRows = end - begin + 1;
+-
+- // when removing from the list, all subsequent indexes are affected
+- offset += removedRows;
+-
+- // update idHash - which points to indexes of d->infos, and these change now!
+- QHash<qlonglong, int>::iterator it;
+-
+- for (it = oldIds.begin(); it != oldIds.end(); )
+- {
+- if (it.value() >= begin)
+- {
+- if (it.value() > end)
+- {
+- // after the removed interval: adjust index
+- it.value() -= removedRows;
+- }
+- else
+- {
+- // in the removed interval
+- it = oldIds.erase(it);
+- continue;
+- }
+- }
+-
+- ++it;
+- }
+- }
+- }
+-
+- modelRemovals.clear();
+-
+- return toContiguousPairs(oldIds.values());
+-}
+-
+-QList<QPair<int, int> > ImageModelIncrementalUpdater::toContiguousPairs(const QList<int>& unsorted)
+-{
+- // Take the given indices and return them as contiguous pairs [begin, end]
+-
+- QList<QPair<int, int> > pairs;
+-
+- if (unsorted.isEmpty())
+- {
+- return pairs;
+- }
+-
+- QList<int> indices(unsorted);
+- qSort(indices);
+-
+- QPair<int, int> pair(indices.first(), indices.first());
+-
+- for (int i=1; i<indices.size(); ++i)
+- {
+- const int &index = indices.at(i);
+-
+- if (index == pair.second + 1)
+- {
+- pair.second = index;
+- continue;
+- }
+-
+- pairs << pair; // insert last pair
+- pair.first = index;
+- pair.second = index;
+- }
+-
+- pairs << pair;
+-
+- return pairs;
+-}
+-
+-// ------------ QAbstractItemModel implementation -------------
+-
+-QVariant ImageModel::data(const QModelIndex& index, int role) const
+-{
+- if (!d->isValid(index))
+- {
+- return QVariant();
+- }
+-
+- switch (role)
+- {
+- case Qt::DisplayRole:
+- case Qt::ToolTipRole:
+- return d->infos.at(index.row()).name();
+-
+- case ImageModelPointerRole:
+- return QVariant::fromValue(const_cast<ImageModel*>(this));
+-
+- case ImageModelInternalId:
+- return index.row();
+-
+- case CreationDateRole:
+- return d->infos.at(index.row()).dateTime();
+-
+- case ExtraDataRole:
+-
+- if (d->extraValueValid(index))
+- {
+- return d->extraValues.at(index.row());
+- }
+- else
+- {
+- return QVariant();
+- }
+-
+- case ExtraDataDuplicateCount:
+- {
+- qlonglong id = d->infos.at(index.row()).id();
+- return numberOfIndexesForImageId(id);
+- }
+- }
+-
+- return QVariant();
+-}
+-
+-QVariant ImageModel::headerData(int section, Qt::Orientation orientation, int role) const
+-{
+- Q_UNUSED(section)
+- Q_UNUSED(orientation)
+- Q_UNUSED(role)
+- return QVariant();
+-}
+-
+-int ImageModel::rowCount(const QModelIndex& parent) const
+-{
+- if (parent.isValid())
+- {
+- return 0;
+- }
+-
+- return d->infos.size();
+-}
+-
+-Qt::ItemFlags ImageModel::flags(const QModelIndex& index) const
+-{
+- if (!d->isValid(index))
+- {
+- return 0;
+- }
+-
+- Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+-
+- f |= dragDropFlags(index);
+-
+- return f;
+-}
+-
+-QModelIndex ImageModel::index(int row, int column, const QModelIndex& parent) const
+-{
+- if (column != 0 || row < 0 || parent.isValid() || row >= d->infos.size())
+- {
+- return QModelIndex();
+- }
+-
+- return createIndex(row, 0);
+-}
+-
+-// ------------ Database watch -------------
+-
+-void ImageModel::slotImageChange(const ImageChangeset& changeset)
+-{
+- if (d->infos.isEmpty())
+- {
+- return;
+- }
+-
+- if (d->watchFlags & changeset.changes())
+- {
+- QItemSelection items;
+-
+- foreach(const qlonglong& id, changeset.ids())
+- {
+- QModelIndex index = indexForImageId(id);
+-
+- if (index.isValid())
+- {
+- items.select(index, index);
+- }
+- }
+-
+- if (!items.isEmpty())
+- {
+- emitDataChangedForSelection(items);
+- emit imageChange(changeset, items);
+- }
+- }
+-}
+-
+-void ImageModel::slotImageTagChange(const ImageTagChangeset& changeset)
+-{
+- if (d->infos.isEmpty())
+- {
+- return;
+- }
+-
+- QItemSelection items;
+-
+- foreach(const qlonglong& id, changeset.ids())
+- {
+- QModelIndex index = indexForImageId(id);
+-
+- if (index.isValid())
+- {
+- items.select(index, index);
+- }
+- }
+-
+- if (!items.isEmpty())
+- {
+- emitDataChangedForSelection(items);
+- emit imageTagChange(changeset, items);
+- }
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagemodel.h b/libs/models/imagemodel.h
+deleted file mode 100644
+index dcf94c2..0000000
+--- a/libs/models/imagemodel.h
++++ /dev/null
+@@ -1,364 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGEMODEL_H
+-#define IMAGEMODEL_H
+-
+-// Qt includes
+-
+-#include <QAbstractListModel>
+-
+-// Local includes
+-
+-#include "dragdropimplementations.h"
+-#include "imageinfo.h"
+-#include "digikam_export.h"
+-
+-class QItemSelection;
+-
+-namespace Digikam
+-{
+-
+-class ImageChangeset;
+-class ImageTagChangeset;
+-
+-namespace DatabaseFields
+-{
+-class Set;
+-}
+-
+-class DIGIKAM_DATABASE_EXPORT ImageModel : public QAbstractListModel, public DragDropModelImplementation
+-{
+- Q_OBJECT
+-
+-public:
+-
+- enum ImageModelRoles
+- {
+- /// An ImageModel* pointer to this model
+- ImageModelPointerRole = Qt::UserRole,
+- ImageModelInternalId = Qt::UserRole + 1,
+- /// Returns a thumbnail pixmap. May be implemented by subclasses.
+- /// Returns either a valid pixmap or a null QVariant.
+- ThumbnailRole = Qt::UserRole + 2,
+- /// Returns a QDateTime with the creation date
+- CreationDateRole = Qt::UserRole + 3,
+- /// Return (optional) extraData field
+- ExtraDataRole = Qt::UserRole + 5,
+- /// Returns the number of duplicate indexes for the same image id
+- ExtraDataDuplicateCount = Qt::UserRole + 6,
+-
+- // Roles which are defined here but not implemented by ImageModel
+- /// Returns position of item in Left Light Table preview.
+- LTLeftPanelRole = Qt::UserRole + 50,
+- /// Returns position of item in Right Light Table preview.
+- LTRightPanelRole = Qt::UserRole + 51,
+-
+- // For use by subclasses
+- SubclassRoles = Qt::UserRole + 100,
+- // For use by filter models
+- FilterModelRoles = Qt::UserRole + 500
+- };
+-
+-public:
+-
+- explicit ImageModel(QObject* parent = 0);
+- ~ImageModel();
+-
+- /** If a cache is kept, lookup by file path is fast,
+- * without a cache it is O(n). Default is false.
+- */
+- void setKeepsFilePathCache(bool keepCache);
+- bool keepsFilePathCache() const;
+-
+- /** Set a set of database fields to watch.
+- * If either of these is changed, dataChanged() will be emitted.
+- * Default is no flag (no signal will be emitted).
+- */
+- void setWatchFlags(const DatabaseFields::Set& set);
+-
+- /** Returns the ImageInfo object, reference or image id from the underlying data
+- * pointed to by the index.
+- * If the index is not valid, imageInfo will return a null ImageInfo, imageId will
+- * return 0, imageInfoRef must not be called with an invalid index.
+- */
+- ImageInfo imageInfo(const QModelIndex& index) const;
+- ImageInfo& imageInfoRef(const QModelIndex& index) const;
+- qlonglong imageId(const QModelIndex& index) const;
+- QList<ImageInfo> imageInfos(const QList<QModelIndex>& indexes) const;
+- QList<qlonglong> imageIds(const QList<QModelIndex>& indexes) const;
+-
+- /** Returns the ImageInfo object, reference or image id from the underlying data
+- * of the given row (parent is the invalid QModelIndex, column is 0).
+- * Note that imageInfoRef will crash if index is invalid.
+- */
+- ImageInfo imageInfo(int row) const;
+- ImageInfo& imageInfoRef(int row) const;
+- qlonglong imageId(int row) const;
+-
+- /** Return the index for the given ImageInfo or id, if contained in this model.
+- */
+- QModelIndex indexForImageInfo(const ImageInfo& info) const;
+- QModelIndex indexForImageInfo(const ImageInfo& info, const QVariant& extraValue) const;
+- QModelIndex indexForImageId(qlonglong id) const;
+- QModelIndex indexForImageId(qlonglong id, const QVariant& extraValue) const;
+- QList<QModelIndex> indexesForImageInfo(const ImageInfo& info) const;
+- QList<QModelIndex> indexesForImageId(qlonglong id) const;
+-
+- int numberOfIndexesForImageInfo(const ImageInfo& info) const;
+- int numberOfIndexesForImageId(qlonglong id) const;
+-
+- /** Returns the index or ImageInfo object from the underlying data
+- * for the given file path. This is fast if keepsFilePathCache is enabled.
+- * The file path is as returned by ImageInfo.filePath().
+- * In case of multiple occurrences of the same file, the simpler variants return
+- * any one found first, use the QList methods to retrieve all occurrences.
+- */
+- QModelIndex indexForPath(const QString& filePath) const;
+- ImageInfo imageInfo(const QString& filePath) const;
+- QList<QModelIndex> indexesForPath(const QString& filePath) const;
+- QList<ImageInfo> imageInfos(const QString& filePath) const;
+-
+- /** Main entry point for subclasses adding image infos to the model.
+- * If you list entries not unique per image id, you must add an extraValue
+- * so that every entry is unique by imageId and extraValues.
+- * Please note that these methods do not prevent addition of duplicate entries.
+- */
+- void addImageInfo(const ImageInfo& info);
+- void addImageInfos(const QList<ImageInfo>& infos);
+- void addImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+-
+- /** Clears image infos and resets model.
+- */
+- void clearImageInfos();
+-
+- /** Clears and adds the infos.
+- */
+- void setImageInfos(const QList<ImageInfo>& infos);
+-
+- /**
+- * Directly remove the given indexes or infos from the model.
+- */
+- void removeIndex(const QModelIndex& indexes);
+- void removeIndexes(const QList<QModelIndex>& indexes);
+- void removeImageInfo(const ImageInfo& info);
+- void removeImageInfos(const QList<ImageInfo>& infos);
+- void removeImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+-
+- /**
+- * addImageInfo() is asynchronous if a prepocessor is set.
+- * This method first adds the info, synchronously.
+- * Only afterwards, the preprocessor will have the opportunity to process it.
+- * This method also bypasses any incremental updates.
+- * Please note that these methods do not prevent addition of duplicate entries.
+- */
+- void addImageInfoSynchronously(const ImageInfo& info);
+- void addImageInfosSynchronously(const QList<ImageInfo>& infos);
+- void addImageInfosSynchronously(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+-
+- /**
+- * Add the given entries. Method returns immediately, the
+- * addition may happen later asynchronously.
+- * These methods prevent the addition of duplicate entries.
+- */
+- void ensureHasImageInfo(const ImageInfo& info);
+- void ensureHasImageInfos(const QList<ImageInfo>& infos);
+- void ensureHasImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+-
+- /**
+- * Ensure that all images grouped on the given leader are contained in the model.
+- */
+- void ensureHasGroupedImages(const ImageInfo& groupLeader);
+-
+- QList<ImageInfo> imageInfos() const;
+- QList<qlonglong> imageIds() const;
+- QList<ImageInfo> uniqueImageInfos() const;
+-
+- bool hasImage(qlonglong id) const;
+- bool hasImage(const ImageInfo& info) const;
+- bool hasImage(const ImageInfo& info, const QVariant& extraValue) const;
+- bool hasImage(qlonglong id, const QVariant& extraValue) const;
+-
+- bool isEmpty() const;
+-
+- // Drag and Drop
+- DECLARE_MODEL_DRAG_DROP_METHODS
+-
+- /**
+- * Install an object as a preprocessor for ImageInfos added to this model.
+- * For every QList of ImageInfos added to addImageInfo, the signal preprocess()
+- * will be emitted. The preprocessor may process the items and shall then readd
+- * them by calling reAddImageInfos(). It may take some time to process.
+- * It shall discard any held infos when the modelReset() signal is sent.
+- * It shall call readdFinished() when no reset occurred and all infos on the way have been readded.
+- * This means that only after calling this method, you shall make three connections
+- * (preprocess -> your slot, your signal -> reAddImageInfos, your signal -> reAddingFinished)
+- * and make or already hold a connection modelReset() -> your slot.
+- * There is only one preprocessor at a time, a previously set object will be disconnected.
+- */
+- void setPreprocessor(QObject* processor);
+- void unsetPreprocessor(QObject* processor);
+-
+- /**
+- * Returns true if this model is currently refreshing.
+- * For a preprocessor this means that, although the preprocessor may currently have
+- * processed all it got, more batches are to be expected.
+- */
+- bool isRefreshing() const;
+-
+- /**
+- * Enable sending of imageInfosAboutToBeRemoved and imageInfosRemoved signals.
+- * Default: false
+- */
+- void setSendRemovalSignals(bool send);
+-
+- virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+- virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+- virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
+- virtual Qt::ItemFlags flags(const QModelIndex& index) const;
+- virtual QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const;
+-
+- /** Retrieves the imageInfo object from the data() method of the given index.
+- * The index may be from a QSortFilterProxyModel as long as an ImageModel is at the end. */
+- static ImageInfo retrieveImageInfo(const QModelIndex& index);
+- static qlonglong retrieveImageId(const QModelIndex& index);
+-
+-Q_SIGNALS:
+-
+- /** Informs that ImageInfos will be added to the model.
+- * This signal is sent before the model data is changed and views are informed.
+- */
+- void imageInfosAboutToBeAdded(const QList<ImageInfo>& infos);
+-
+- /** Informs that ImageInfos have been added to the model.
+- * This signal is sent after the model data is changed and views are informed.
+- */
+- void imageInfosAdded(const QList<ImageInfo>& infos);
+-
+- /** Informs that ImageInfos will be removed from the model.
+- * This signal is sent before the model data is changed and views are informed.
+- * Note: You need to explicitly enable sending of this signal. It is not sent
+- * in clearImageInfos().
+- */
+- void imageInfosAboutToBeRemoved(const QList<ImageInfo>& infos);
+-
+- /** Informs that ImageInfos have been removed from the model.
+- * This signal is sent after the model data is changed and views are informed. *
+- * Note: You need to explicitly enable sending of this signal. It is not sent
+- * in clearImageInfos().
+- */
+- void imageInfosRemoved(const QList<ImageInfo>& infos);
+-
+- /** Connect to this signal only if you are the current preprocessor.
+- */
+- void preprocess(const QList<ImageInfo>& infos, const QList<QVariant>&);
+- void processAdded(const QList<ImageInfo>& infos, const QList<QVariant>&);
+-
+- /** If an ImageChangeset affected indexes of this model with changes as set in watchFlags(),
+- * this signal contains the changeset and the affected indexes.
+- */
+- void imageChange(const ImageChangeset&, const QItemSelection&);
+-
+- /** If an ImageTagChangeset affected indexes of this model,
+- * this signal contains the changeset and the affected indexes.
+- */
+- void imageTagChange(const ImageTagChangeset&, const QItemSelection&);
+-
+- /** Signals that the model is right now ready to start an incremental refresh.
+- * This is guaranteed only for the scope of emitting this signal.
+- */
+- void readyForIncrementalRefresh();
+-
+- /** Signals that the model has finished currently with all scheduled
+- * refreshing, full or incremental, and all preprocessing.
+- * The model is in polished, clean situation right now.
+- */
+- void allRefreshingFinished();
+-
+-public Q_SLOTS:
+-
+- void reAddImageInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void reAddingFinished();
+-
+-protected:
+-
+- /** Subclasses that add ImageInfos in batches shall call startRefresh()
+- * when they start sending batches and finishRefresh() when they have finished.
+- * No incremental refreshes will be started while listing.
+- * A clearImageInfos() always stops listing, calling finishRefresh() is then not necessary.
+- */
+- void startRefresh();
+- void finishRefresh();
+-
+- /** As soon as the model is ready to start an incremental refresh, the signal
+- * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline
+- * if the model is ready right now.
+- */
+- void requestIncrementalRefresh();
+- bool hasIncrementalRefreshPending() const;
+-
+- /** Starts an incremental refresh operation. You shall only call this method from a slot
+- * connected to readyForIncrementalRefresh(). To initiate an incremental refresh,
+- * call requestIncrementalRefresh().
+- */
+- void startIncrementalRefresh();
+- void finishIncrementalRefresh();
+-
+- void emitDataChangedForAll();
+- void emitDataChangedForSelection(const QItemSelection& selection);
+-
+- // Called when the internal storage is cleared
+- virtual void imageInfosCleared() {};
+-
+- // Called before rowsAboutToBeRemoved
+- virtual void imageInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {};
+-
+-protected Q_SLOTS:
+-
+- virtual void slotImageChange(const ImageChangeset& changeset);
+- virtual void slotImageTagChange(const ImageTagChangeset& changeset);
+-
+-private:
+-
+- void appendInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void appendInfosChecked(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void publiciseInfos(const QList<ImageInfo>& infos, const QList<QVariant>& extraValues);
+- void cleanSituationChecks();
+- void removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove);
+- void removeRowPairs(const QList<QPair<int, int> >& toRemove);
+-
+-public:
+-
+- // Declared public because it's used in ImageModelIncrementalUpdater class
+- class Private;
+-
+-private:
+-
+- Private* const d;
+-};
+-
+-} // namespace Digikam
+-
+-Q_DECLARE_METATYPE(Digikam::ImageModel*)
+-
+-#endif // IMAGEMODEL_H
+diff --git a/libs/models/imagesortsettings.cpp b/libs/models/imagesortsettings.cpp
+deleted file mode 100644
+index 39ee6e1..0000000
+--- a/libs/models/imagesortsettings.cpp
++++ /dev/null
+@@ -1,400 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Filter values for use with ImageFilterModel
+- *
+- * Copyright (C) 2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2014 by Mohamed Anwer <m dot anwer at gmx dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagesortsettings.h"
+-
+-// Qt includes
+-
+-#include <QDateTime>
+-#include <QRectF>
+-
+-// Local includes
+-
+-#include "coredbfields.h"
+-#include "imageinfo.h"
+-
+-namespace Digikam
+-{
+-
+-ImageSortSettings::ImageSortSettings()
+-{
+- categorizationMode = NoCategories;
+- categorizationSortOrder = DefaultOrder;
+- categorizationCaseSensitivity = Qt::CaseSensitive;
+- sortRole = SortByFileName;
+- sortOrder = DefaultOrder;
+- strTypeNatural = true;
+- sortCaseSensitivity = Qt::CaseSensitive;
+- currentCategorizationSortOrder = Qt::AscendingOrder;
+- currentSortOrder = Qt::AscendingOrder;
+-}
+-
+-bool ImageSortSettings::operator==(const ImageSortSettings& other) const
+-{
+- return
+- categorizationMode == other.categorizationMode &&
+- categorizationSortOrder == other.categorizationSortOrder &&
+- categorizationCaseSensitivity == other.categorizationCaseSensitivity &&
+- sortRole == other.sortRole &&
+- sortOrder == other.sortOrder &&
+- sortCaseSensitivity == other.sortCaseSensitivity;
+-}
+-
+-void ImageSortSettings::setCategorizationMode(CategorizationMode mode)
+-{
+- categorizationMode = mode;
+-
+- if (categorizationSortOrder == DefaultOrder)
+- {
+- currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode);
+- }
+-}
+-
+-void ImageSortSettings::setCategorizationSortOrder(SortOrder order)
+-{
+- categorizationSortOrder = order;
+-
+- if (categorizationSortOrder == DefaultOrder)
+- {
+- currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode);
+- }
+- else
+- {
+- currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder;
+- }
+-}
+-
+-void ImageSortSettings::setSortRole(SortRole role)
+-{
+- sortRole = role;
+-
+- if (sortOrder == DefaultOrder)
+- {
+- currentSortOrder = defaultSortOrderForSortRole(sortRole);
+- }
+-}
+-
+-void ImageSortSettings::setSortOrder(SortOrder order)
+-{
+- sortOrder = order;
+-
+- if (sortOrder == DefaultOrder)
+- {
+- currentSortOrder = defaultSortOrderForSortRole(sortRole);
+- }
+- else
+- {
+- currentSortOrder = (Qt::SortOrder)order;
+- }
+-}
+-
+-void ImageSortSettings::setStringTypeNatural(bool natural)
+-{
+- strTypeNatural = natural;
+-}
+-
+-Qt::SortOrder ImageSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode)
+-{
+- switch (mode)
+- {
+- case NoCategories:
+- case OneCategory:
+- case CategoryByAlbum:
+- case CategoryByFormat:
+- default:
+- return Qt::AscendingOrder;
+- }
+-}
+-
+-Qt::SortOrder ImageSortSettings::defaultSortOrderForSortRole(SortRole role)
+-{
+- switch (role)
+- {
+- case SortByFileName:
+- case SortByFilePath:
+- return Qt::AscendingOrder;
+- case SortByFileSize:
+- return Qt::DescendingOrder;
+- case SortByModificationDate:
+- case SortByCreationDate:
+- return Qt::AscendingOrder;
+- case SortByRating:
+- case SortByImageSize:
+- return Qt::DescendingOrder;
+- case SortByAspectRatio:
+- return Qt::DescendingOrder;
+- case SortBySimilarity:
+- return Qt::DescendingOrder;
+- default:
+- return Qt::AscendingOrder;
+- }
+-}
+-
+-int ImageSortSettings::compareCategories(const ImageInfo& left, const ImageInfo& right) const
+-{
+- switch (categorizationMode)
+- {
+- case NoCategories:
+- case OneCategory:
+- return 0;
+- case CategoryByAlbum:
+- {
+- int leftAlbum = left.albumId();
+- int rightAlbum = right.albumId();
+-
+- // return comparation result
+- if (leftAlbum == rightAlbum)
+- {
+- return 0;
+- }
+- else if (lessThanByOrder(leftAlbum, rightAlbum, currentCategorizationSortOrder))
+- {
+- return -1;
+- }
+- else
+- {
+- return 1;
+- }
+- }
+- case CategoryByFormat:
+- {
+- return naturalCompare(left.format(), right.format(),
+- currentCategorizationSortOrder, categorizationCaseSensitivity, strTypeNatural);
+- }
+- default:
+- return 0;
+- }
+-}
+-
+-bool ImageSortSettings::lessThan(const ImageInfo& left, const ImageInfo& right) const
+-{
+- int result = compare(left, right, sortRole);
+-
+- if (result != 0)
+- {
+- return result < 0;
+- }
+-
+- // are they identical?
+- if (left == right)
+- {
+- return false;
+- }
+-
+- // If left and right equal for first sort order, use a hierarchy of all sort orders
+- if ( (result = compare(left, right, SortByFileName)) != 0)
+- {
+- return result < 0;
+- }
+-
+- if ( (result = compare(left, right, SortByCreationDate)) != 0)
+- {
+- return result < 0;
+- }
+-
+- if ( (result = compare(left, right, SortByModificationDate)) != 0)
+- {
+- return result < 0;
+- }
+-
+- if ( (result = compare(left, right, SortByFilePath)) != 0)
+- {
+- return result < 0;
+- }
+-
+- if ( (result = compare(left, right, SortByFileSize)) != 0)
+- {
+- return result < 0;
+- }
+-
+- if ( (result = compare(left, right, SortBySimilarity)) != 0)
+- {
+- return result < 0;
+- }
+-
+- return false;
+-}
+-
+-int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right) const
+-{
+- return compare(left, right, sortRole);
+-}
+-
+-int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right, SortRole role) const
+-{
+- switch (role)
+- {
+- case SortByFileName:
+- {
+- bool versioning = (left.name().contains(QLatin1String("_v"), Qt::CaseInsensitive) ||
+- right.name().contains(QLatin1String("_v"), Qt::CaseInsensitive));
+- return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural, versioning);
+- }
+- case SortByFilePath:
+- return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural);
+- case SortByFileSize:
+- return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder);
+- case SortByModificationDate:
+- return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder);
+- case SortByCreationDate:
+- return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder);
+- case SortByRating:
+- // I have the feeling that inverting the sort order for rating is the natural order
+- return - compareByOrder(left.rating(), right.rating(), currentSortOrder);
+- case SortByImageSize:
+- {
+- QSize leftSize = left.dimensions();
+- QSize rightSize = right.dimensions();
+- int leftPixels = leftSize.width() * leftSize.height();
+- int rightPixels = rightSize.width() * rightSize.height();
+- return compareByOrder(leftPixels, rightPixels, currentSortOrder);
+- }
+- case SortByAspectRatio:
+- {
+- QSize leftSize = left.dimensions();
+- QSize rightSize = right.dimensions();
+- int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000;
+- int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000;
+- return compareByOrder(leftAR, rightAR, currentSortOrder);
+- }
+- case SortBySimilarity:
+- {
+- qlonglong leftReferenceImageId = left.currentReferenceImage();
+- qlonglong rightReferenceImageId = right.currentReferenceImage();
+- // make sure that the original image has always the highest similarity.
+- double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity();
+- double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity();
+- return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder);
+- }
+- default:
+- return 1;
+- }
+-}
+-
+-bool ImageSortSettings::lessThan(const QVariant& left, const QVariant& right) const
+-{
+- if (left.type() != right.type())
+- {
+- return false;
+- }
+-
+- switch (left.type())
+- {
+- case QVariant::Int:
+- return compareByOrder(left.toInt(), right.toInt(), currentSortOrder);
+- case QVariant::UInt:
+- return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder);
+- case QVariant::LongLong:
+- return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder);
+- case QVariant::ULongLong:
+- return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder);
+- case QVariant::Double:
+- return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder);
+- case QVariant::Date:
+- return compareByOrder(left.toDate(), right.toDate(), currentSortOrder);
+- case QVariant::DateTime:
+- return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder);
+- case QVariant::Time:
+- return compareByOrder(left.toTime(), right.toTime(), currentSortOrder);
+- case QVariant::Rect:
+- case QVariant::RectF:
+- {
+- QRectF rectLeft = left.toRectF();
+- QRectF rectRight = right.toRectF();
+- int result;
+-
+- if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0)
+- {
+- return result < 0;
+- }
+-
+- if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0)
+- {
+- return result < 0;
+- }
+-
+- QSizeF sizeLeft = rectLeft.size(), sizeRight = rectRight.size();
+-
+- if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0)
+- {
+- return result < 0;
+- }
+- // FIXME: fall through?? If not, add "break" here
+- }
+- default:
+- return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural);
+- }
+-}
+-
+-DatabaseFields::Set ImageSortSettings::watchFlags() const
+-{
+- DatabaseFields::Set set;
+-
+- switch (sortRole)
+- {
+- case SortByFileName:
+- set |= DatabaseFields::Name;
+- break;
+- case SortByFilePath:
+- set |= DatabaseFields::Name;
+- break;
+- case SortByFileSize:
+- set |= DatabaseFields::FileSize;
+- break;
+- case SortByModificationDate:
+- set |= DatabaseFields::ModificationDate;
+- break;
+- case SortByCreationDate:
+- set |= DatabaseFields::CreationDate;
+- break;
+- case SortByRating:
+- set |= DatabaseFields::Rating;
+- break;
+- case SortByImageSize:
+- set |= DatabaseFields::Width | DatabaseFields::Height;
+- break;
+- case SortByAspectRatio:
+- set |= DatabaseFields::Width | DatabaseFields::Height;
+- break;
+- case SortBySimilarity:
+- // TODO: Not sure what to do here....
+- set |= DatabaseFields::Name;
+- break;
+- }
+-
+- switch (categorizationMode)
+- {
+- case NoCategories:
+- case OneCategory:
+- case CategoryByAlbum:
+- break;
+- case CategoryByFormat:
+- set |= DatabaseFields::Format;
+- break;
+- }
+-
+- return set;
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagesortsettings.h b/libs/models/imagesortsettings.h
+deleted file mode 100644
+index 2a5fd8c..0000000
+--- a/libs/models/imagesortsettings.h
++++ /dev/null
+@@ -1,225 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-05-31
+- * Description : Sort settings for use with ImageFilterModel
+- *
+- * Copyright (C) 2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGESORTSETTINGS_H
+-#define IMAGESORTSETTINGS_H
+-
+-// Qt includes
+-
+-#include <QHash>
+-#include <QList>
+-#include <QMap>
+-#include <QString>
+-#include <QCollator>
+-
+-// Local includes
+-
+-#include "digikam_export.h"
+-
+-namespace Digikam
+-{
+-
+-class ImageInfo;
+-
+-namespace DatabaseFields
+-{
+- class Set;
+-}
+-
+-class DIGIKAM_DATABASE_EXPORT ImageSortSettings
+-{
+-public:
+-
+- ImageSortSettings();
+-
+- bool operator==(const ImageSortSettings& other) const;
+-
+- /** Compares the categories of left and right.
+- * Return -1 if left is less than right, 0 if both fall in the same category,
+- * and 1 if left is greater than right.
+- * Adheres to set categorization mode and current category sort order.
+- */
+- int compareCategories(const ImageInfo& left, const ImageInfo& right) const;
+-
+- /** Returns true if left is less than right.
+- * Adheres to current sort role and sort order.
+- */
+- bool lessThan(const ImageInfo& left, const ImageInfo& right) const;
+-
+- /** Compares the ImageInfos left and right.
+- * Return -1 if left is less than right, 1 if left is greater than right,
+- * and 0 if left equals right comparing the current sort role's value.
+- * Adheres to set sort role and sort order.
+- */
+- int compare(const ImageInfo& left, const ImageInfo& right) const;
+-
+- /** Returns true if left QVariant is less than right.
+- * Adheres to current sort role and sort order.
+- * Use for extraValue, if necessary.
+- */
+- bool lessThan(const QVariant& left, const QVariant& right) const;
+-
+- enum SortOrder
+- {
+- AscendingOrder = Qt::AscendingOrder,
+- DescendingOrder = Qt::DescendingOrder,
+- DefaultOrder /// sort order depends on the chosen sort role
+- };
+-
+- /// --- Categories ---
+-
+- enum CategorizationMode
+- {
+- NoCategories, /// categorization switched off
+- OneCategory, /// all items in one global category
+- CategoryByAlbum,
+- CategoryByFormat
+- };
+-
+- CategorizationMode categorizationMode;
+- SortOrder categorizationSortOrder;
+-
+- void setCategorizationMode(CategorizationMode mode);
+- void setCategorizationSortOrder(SortOrder order);
+-
+- /// Only Ascending or Descending, never DefaultOrder
+- Qt::SortOrder currentCategorizationSortOrder;
+- Qt::CaseSensitivity categorizationCaseSensitivity;
+-
+- bool isCategorized() const { return categorizationMode >= CategoryByAlbum; }
+-
+- /// --- Image Sorting ---
+-
+- enum SortRole
+- {
+- // Note: For legacy reasons, the order of the first five entries must remain unchanged
+- SortByFileName,
+- SortByFilePath,
+- SortByCreationDate,
+- SortByFileSize,
+- SortByRating,
+- SortByModificationDate,
+- SortByImageSize, // pixel number
+- SortByAspectRatio, // width / height * 100000
+- SortBySimilarity
+- };
+-
+- SortRole sortRole;
+- SortOrder sortOrder;
+- bool strTypeNatural;
+-
+- void setSortRole(SortRole role);
+- void setSortOrder(SortOrder order);
+- void setStringTypeNatural(bool natural);
+-
+- Qt::SortOrder currentSortOrder;
+- Qt::CaseSensitivity sortCaseSensitivity;
+-
+- int compare(const ImageInfo& left, const ImageInfo& right, SortRole sortRole) const;
+-
+- // --- ---
+-
+- static Qt::SortOrder defaultSortOrderForCategorizationMode(CategorizationMode mode);
+- static Qt::SortOrder defaultSortOrderForSortRole(SortRole role);
+-
+- /// --- Change notification ---
+-
+- /** Returns database fields a change in which would affect the current sorting.
+- */
+- DatabaseFields::Set watchFlags() const;
+-
+- /// --- Utilities ---
+-
+- /** Returns a < b if sortOrder is Ascending, or b < a if order is descending.
+- */
+- template <typename T>
+- static inline bool lessThanByOrder(const T& a, const T& b, Qt::SortOrder sortOrder)
+- {
+- if (sortOrder == Qt::AscendingOrder)
+- {
+- return a < b;
+- }
+- else
+- {
+- return b < a;
+- }
+- }
+-
+- /** Returns the usual compare result of -1, 0, or 1 for lessThan, equals and greaterThan.
+- */
+- template <typename T>
+- static inline int compareValue(const T& a, const T& b)
+- {
+- if (a == b)
+- {
+- return 0;
+- }
+-
+- if (a < b)
+- {
+- return -1;
+- }
+- else
+- {
+- return 1;
+- }
+- }
+-
+- /** Takes a typical result from a compare method (0 is equal, -1 is less than, 1 is greater than)
+- * and applies the given sort order to it.
+- */
+- static inline int compareByOrder(int compareResult, Qt::SortOrder sortOrder)
+- {
+- if (sortOrder == Qt::AscendingOrder)
+- {
+- return compareResult;
+- }
+- else
+- {
+- return - compareResult;
+- }
+- }
+-
+- template <typename T>
+- static inline int compareByOrder(const T& a, const T& b, Qt::SortOrder sortOrder)
+- {
+- return compareByOrder(compareValue(a, b), sortOrder);
+- }
+-
+- /** Compares the two string by natural comparison and adheres to given sort order
+- */
+- static inline int naturalCompare(const QString& a, const QString& b, Qt::SortOrder sortOrder,
+- Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive,
+- bool natural = true, bool versioning = false)
+- {
+- QCollator collator;
+- collator.setNumericMode(natural);
+- collator.setIgnorePunctuation(versioning);
+- collator.setCaseSensitivity(caseSensitive);
+- return (compareByOrder(collator.compare(a, b), sortOrder));
+- }
+-};
+-
+-} // namespace Digikam
+-
+-#endif // IMAGESORTSETTINGS_H
+diff --git a/libs/models/imagethumbnailmodel.cpp b/libs/models/imagethumbnailmodel.cpp
+deleted file mode 100644
+index b7f5661..0000000
+--- a/libs/models/imagethumbnailmodel.cpp
++++ /dev/null
+@@ -1,323 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries with support for thumbnail loading
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011-2017 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imagethumbnailmodel.h"
+-
+-// Qt includes
+-
+-#include <QHash>
+-
+-// Local includes
+-
+-#include "digikam_debug.h"
+-#include "thumbnailloadthread.h"
+-#include "digikam_export.h"
+-#include "digikam_globals.h"
+-
+-namespace Digikam
+-{
+-
+-class ImageThumbnailModel::ImageThumbnailModelPriv
+-{
+-public:
+-
+- ImageThumbnailModelPriv() :
+- thread(0),
+- preloadThread(0),
+- thumbSize(0),
+- lastGlobalThumbSize(0),
+- preloadThumbSize(0),
+- emitDataChanged(true)
+- {
+- staticListContainingThumbnailRole << ImageModel::ThumbnailRole;
+- }
+-
+- ThumbnailLoadThread* thread;
+- ThumbnailLoadThread* preloadThread;
+- ThumbnailSize thumbSize;
+- ThumbnailSize lastGlobalThumbSize;
+- ThumbnailSize preloadThumbSize;
+- QRect detailRect;
+- QVector<int> staticListContainingThumbnailRole;
+-
+- bool emitDataChanged;
+-
+- int preloadThumbnailSize() const
+- {
+- if (preloadThumbSize.size())
+- {
+- return preloadThumbSize.size();
+- }
+-
+- return thumbSize.size();
+- }
+-};
+-
+-ImageThumbnailModel::ImageThumbnailModel(QObject* parent)
+- : ImageModel(parent), d(new ImageThumbnailModelPriv)
+-{
+- setKeepsFilePathCache(true);
+-}
+-
+-ImageThumbnailModel::~ImageThumbnailModel()
+-{
+- delete d->preloadThread;
+- delete d;
+-}
+-
+-void ImageThumbnailModel::setThumbnailLoadThread(ThumbnailLoadThread* thread)
+-{
+- d->thread = thread;
+-
+- connect(d->thread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)),
+- this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap)));
+-}
+-
+-ThumbnailLoadThread* ImageThumbnailModel::thumbnailLoadThread() const
+-{
+- return d->thread;
+-}
+-
+-ThumbnailSize ImageThumbnailModel::thumbnailSize() const
+-{
+- return d->thumbSize;
+-}
+-
+-void ImageThumbnailModel::setThumbnailSize(const ThumbnailSize& size)
+-{
+- d->lastGlobalThumbSize = size;
+- d->thumbSize = size;
+-}
+-
+-void ImageThumbnailModel::setPreloadThumbnailSize(const ThumbnailSize& size)
+-{
+- d->preloadThumbSize = size;
+-}
+-
+-void ImageThumbnailModel::setEmitDataChanged(bool emitSignal)
+-{
+- d->emitDataChanged = emitSignal;
+-}
+-
+-void ImageThumbnailModel::setPreloadThumbnails(bool preload)
+-{
+- if (preload)
+- {
+- if (!d->preloadThread)
+- {
+- d->preloadThread = new ThumbnailLoadThread;
+- d->preloadThread->setPixmapRequested(false);
+- d->preloadThread->setPriority(QThread::LowestPriority);
+- }
+-
+- connect(this, SIGNAL(allRefreshingFinished()),
+- this, SLOT(preloadAllThumbnails()));
+- }
+- else
+- {
+- delete d->preloadThread;
+- d->preloadThread = 0;
+- disconnect(this, SIGNAL(allRefreshingFinished()),
+- this, SLOT(preloadAllThumbnails()));
+- }
+-}
+-
+-void ImageThumbnailModel::prepareThumbnails(const QList<QModelIndex>& indexesToPrepare)
+-{
+- prepareThumbnails(indexesToPrepare, d->thumbSize);
+-}
+-
+-void ImageThumbnailModel::prepareThumbnails(const QList<QModelIndex>& indexesToPrepare, const ThumbnailSize& thumbSize)
+-{
+- if (!d->thread)
+- {
+- return;
+- }
+-
+- QList<ThumbnailIdentifier> ids;
+- foreach(const QModelIndex& index, indexesToPrepare)
+- {
+- ids << imageInfoRef(index).thumbnailIdentifier();
+- }
+- d->thread->findGroup(ids, thumbSize.size());
+-}
+-
+-void ImageThumbnailModel::preloadThumbnails(const QList<ImageInfo>& infos)
+-{
+- if (!d->preloadThread)
+- {
+- return;
+- }
+-
+- QList<ThumbnailIdentifier> ids;
+- foreach(const ImageInfo& info, infos)
+- {
+- ids << info.thumbnailIdentifier();
+- }
+- d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize());
+-}
+-
+-void ImageThumbnailModel::preloadThumbnails(const QList<QModelIndex>& indexesToPreload)
+-{
+- if (!d->preloadThread)
+- {
+- return;
+- }
+-
+- QList<ThumbnailIdentifier> ids;
+- foreach(const QModelIndex& index, indexesToPreload)
+- {
+- ids << imageInfoRef(index).thumbnailIdentifier();
+- }
+- d->preloadThread->stopAllTasks();
+- d->preloadThread->pregenerateGroup(ids, d->preloadThumbnailSize());
+-}
+-
+-void ImageThumbnailModel::preloadAllThumbnails()
+-{
+- preloadThumbnails(imageInfos());
+-}
+-
+-void ImageThumbnailModel::imageInfosCleared()
+-{
+- if (d->preloadThread)
+- {
+- d->preloadThread->stopAllTasks();
+- }
+-}
+-
+-QVariant ImageThumbnailModel::data(const QModelIndex& index, int role) const
+-{
+- if (role == ThumbnailRole && d->thread && index.isValid())
+- {
+- QPixmap thumbnail;
+- ImageInfo info = imageInfo(index);
+- QString path = info.filePath();
+-
+- if (info.isNull())
+- {
+- return QVariant(QVariant::Pixmap);
+- }
+-
+- if (!d->detailRect.isNull())
+- {
+- if (d->thread->find(info.thumbnailIdentifier(), d->detailRect, thumbnail, d->thumbSize.size()))
+- {
+- return thumbnail;
+- }
+- }
+- else
+- {
+- if (d->thread->find(info.thumbnailIdentifier(), thumbnail, d->thumbSize.size()))
+- {
+- return thumbnail;
+- }
+- }
+-
+- return QVariant(QVariant::Pixmap);
+- }
+-
+- return ImageModel::data(index, role);
+-}
+-
+-bool ImageThumbnailModel::setData(const QModelIndex& index, const QVariant& value, int role)
+-{
+- if (role == ThumbnailRole)
+- {
+- switch (value.type())
+- {
+- case QVariant::Invalid:
+- d->thumbSize = d->lastGlobalThumbSize;
+- d->detailRect = QRect();
+- break;
+-
+- case QVariant::Int:
+-
+- if (value.isNull())
+- {
+- d->thumbSize = d->lastGlobalThumbSize;
+- }
+- else
+- {
+- d->thumbSize = value.toInt();
+- }
+- break;
+-
+- case QVariant::Rect:
+-
+- if (value.isNull())
+- {
+- d->detailRect = QRect();
+- }
+- else
+- {
+- d->detailRect = value.toRect();
+- }
+- break;
+-
+- default:
+- break;
+- }
+- }
+-
+- return ImageModel::setData(index, value, role);
+-}
+-
+-void ImageThumbnailModel::slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb)
+-{
+- if (thumb.isNull())
+- {
+- return;
+- }
+-
+- // In case of multiple occurrence, we currently do not know which thumbnail is this. Signal change on all.
+- QModelIndexList indexes;
+- ThumbnailIdentifier thumbId = loadingDescription.thumbnailIdentifier();
+- if (thumbId.filePath.isEmpty())
+- {
+- indexes = indexesForImageId(thumbId.id);
+- }
+- else
+- {
+- indexes = indexesForPath(thumbId.filePath);
+- }
+- foreach(const QModelIndex& index, indexes)
+- {
+- if (thumb.isNull())
+- {
+- emit thumbnailFailed(index, loadingDescription.previewParameters.size);
+- }
+- else
+- {
+- emit thumbnailAvailable(index, loadingDescription.previewParameters.size);
+-
+- if (d->emitDataChanged)
+- {
+- emit dataChanged(index, index, d->staticListContainingThumbnailRole);
+- }
+- }
+- }
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imagethumbnailmodel.h b/libs/models/imagethumbnailmodel.h
+deleted file mode 100644
+index 366ca65..0000000
+--- a/libs/models/imagethumbnailmodel.h
++++ /dev/null
+@@ -1,140 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2009-03-05
+- * Description : Qt item model for database entries with support for thumbnail loading
+- *
+- * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
+- * Copyright (C) 2011 by Gilles Caulier <caulier dot gilles at gmail dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGETHUMBNAILMODEL_H
+-#define IMAGETHUMBNAILMODEL_H
+-
+-// Local includes
+-
+-#include "imagemodel.h"
+-#include "thumbnailsize.h"
+-#include "digikam_export.h"
+-
+-namespace Digikam
+-{
+-
+-class LoadingDescription;
+-class ThumbnailLoadThread;
+-
+-class DIGIKAM_DATABASE_EXPORT ImageThumbnailModel : public ImageModel
+-{
+- Q_OBJECT
+-
+-public:
+-
+- /**
+- * An ImageModel that supports thumbnail loading.
+- * You need to set a ThumbnailLoadThread to enable thumbnail loading.
+- * Adjust the thumbnail size to your needs.
+- * Note that setKeepsFilePathCache is enabled per default.
+- */
+- explicit ImageThumbnailModel(QObject* parent);
+- ~ImageThumbnailModel();
+-
+- /** Enable thumbnail loading and set the thread that shall be used.
+- * The thumbnail size of this thread will be adjusted.
+- */
+- void setThumbnailLoadThread(ThumbnailLoadThread* thread);
+- ThumbnailLoadThread* thumbnailLoadThread() const;
+-
+- /// Set the thumbnail size to use
+- void setThumbnailSize(const ThumbnailSize& thumbSize);
+-
+- /// If you want to fix a size for preloading, do it here.
+- void setPreloadThumbnailSize(const ThumbnailSize& thumbSize);
+-
+- void setExifRotate(bool rotate);
+-
+- /**
+- * Enable emitting dataChanged() when a thumbnail becomes available.
+- * The thumbnailAvailable() signal will be emitted in any case.
+- * Default is true.
+- */
+- void setEmitDataChanged(bool emitSignal);
+-
+- /**
+- * Enable preloading of thumbnails:
+- * If preloading is enabled, for every entry in the model a thumbnail generation is started.
+- * Default: false.
+- */
+- void setPreloadThumbnails(bool preload);
+-
+- ThumbnailSize thumbnailSize() const;
+-
+- /**
+- * Handles the ThumbnailRole.
+- * If the pixmap is available, returns it in the QVariant.
+- * If it still needs to be loaded, returns a null QVariant and emits
+- * thumbnailAvailable() as soon as it is available.
+- */
+- virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+-
+- /**
+- * You can override the current thumbnail size by giving an integer value for ThumbnailRole.
+- * Set a null QVariant to use the thumbnail size set by setThumbnailSize() again.
+- * The index given here is ignored for this purpose.
+- */
+- virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::DisplayRole);
+-
+-public Q_SLOTS:
+-
+- /** Prepare the thumbnail loading for the given indexes
+- */
+- void prepareThumbnails(const QList<QModelIndex>& indexesToPrepare);
+- void prepareThumbnails(const QList<QModelIndex>& indexesToPrepare, const ThumbnailSize& thumbSize);
+-
+- /**
+- * Preload thumbnail for the given infos resp. indexes.
+- * Note: Use setPreloadThumbnails to automatically preload all entries in the model.
+- * Note: This only ensures thumbnail generation. It is not guaranteed that pixmaps
+- * are stored in the cache. For thumbnails that are expect to be drawn immediately,
+- * include them in prepareThumbnails().
+- * Note: Stops preloading of previously added thumbnails.
+- */
+- void preloadThumbnails(const QList<ImageInfo>&);
+- void preloadThumbnails(const QList<QModelIndex>&);
+- void preloadAllThumbnails();
+-
+-Q_SIGNALS:
+-
+- void thumbnailAvailable(const QModelIndex& index, int requestedSize);
+- void thumbnailFailed(const QModelIndex& index, int requestedSize);
+-
+-protected:
+-
+- virtual void imageInfosCleared();
+-
+-protected Q_SLOTS:
+-
+- void slotThumbnailLoaded(const LoadingDescription& loadingDescription, const QPixmap& thumb);
+-
+-private:
+-
+- class ImageThumbnailModelPriv;
+- ImageThumbnailModelPriv* const d;
+-};
+-
+-} // namespace Digikam
+-
+-#endif /* IMAGETHUMBNAILMODEL_H */
+diff --git a/libs/models/imageversionsmodel.cpp b/libs/models/imageversionsmodel.cpp
+deleted file mode 100644
+index e6ba582..0000000
+--- a/libs/models/imageversionsmodel.cpp
++++ /dev/null
+@@ -1,183 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2010-07-13
+- * Description : Model for image versions
+- *
+- * Copyright (C) 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#include "imageversionsmodel.h"
+-
+-// KDE includes
+-
+-#include <klocalizedstring.h>
+-
+-// Local includes
+-
+-#include "digikam_debug.h"
+-#include "workingwidget.h"
+-
+-namespace Digikam
+-{
+-
+-class ImageVersionsModel::Private
+-{
+-public:
+-
+- Private()
+- {
+- data = 0;
+- paintTree = false;
+- }
+-
+- ///Complete paths with filenames and tree level
+- QList<QPair<QString, int> >* data;
+- ///This is for delegate to paint it as selected
+- QString currentSelectedImage;
+- ///If true, the delegate will paint items as a tree
+- ///if false, it will be painted as a list
+- bool paintTree;
+-};
+-
+-ImageVersionsModel::ImageVersionsModel(QObject* parent)
+- : QAbstractListModel(parent),
+- d(new Private)
+-{
+- d->data = new QList<QPair<QString, int> >;
+-}
+-
+-ImageVersionsModel::~ImageVersionsModel()
+-{
+- //qDeleteAll(d->data);
+- delete d;
+-}
+-
+-Qt::ItemFlags ImageVersionsModel::flags(const QModelIndex& index) const
+-{
+- if (!index.isValid())
+- {
+- return 0;
+- }
+-
+- return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+-}
+-
+-QVariant ImageVersionsModel::data(const QModelIndex& index, int role) const
+-{
+- if (!index.isValid())
+- {
+- return QVariant();
+- }
+-
+- if (role == Qt::DisplayRole && !d->data->isEmpty())
+- {
+- return d->data->at(index.row()).first;
+- }
+- else if (role == Qt::UserRole && !d->data->isEmpty())
+- {
+- return d->data->at(index.row()).second;
+- }
+- else if (role == Qt::DisplayRole && d->data->isEmpty())
+- {
+- //TODO: make this text Italic
+- return QVariant(QString(i18n("No image selected")));
+- }
+-
+- return QVariant();
+-}
+-
+-int ImageVersionsModel::rowCount(const QModelIndex& parent) const
+-{
+- Q_UNUSED(parent)
+- return d->data->count();
+-}
+-
+-void ImageVersionsModel::setupModelData(QList<QPair<QString, int> >& data)
+-{
+- beginResetModel();
+-
+- d->data->clear();
+-
+- if (!data.isEmpty())
+- {
+- d->data->append(data);
+- }
+- else
+- {
+- d->data->append(qMakePair(QString(i18n("This is the original image")), 0));
+- }
+-
+- endResetModel();
+-}
+-
+-void ImageVersionsModel::clearModelData()
+-{
+- beginResetModel();
+-
+- if (!d->data->isEmpty())
+- {
+- d->data->clear();
+- }
+-
+- endResetModel();
+-}
+-
+-void ImageVersionsModel::slotAnimationStep()
+-{
+- emit dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 1));
+-}
+-
+-QString ImageVersionsModel::currentSelectedImage() const
+-{
+- return d->currentSelectedImage;
+-}
+-
+-void ImageVersionsModel::setCurrentSelectedImage(const QString& path)
+-{
+- d->currentSelectedImage = path;
+-}
+-
+-QModelIndex ImageVersionsModel::currentSelectedImageIndex() const
+-{
+- return index(listIndexOf(d->currentSelectedImage), 0);
+-}
+-
+-bool ImageVersionsModel::paintTree() const
+-{
+- return d->paintTree;
+-}
+-
+-void ImageVersionsModel::setPaintTree(bool paint)
+-{
+- d->paintTree = paint;
+-}
+-
+-int ImageVersionsModel::listIndexOf(const QString& item) const
+-{
+- for (int i = 0; i < d->data->size(); ++i)
+- {
+- if (d->data->at(i).first == item)
+- {
+- return i;
+- }
+- }
+-
+- return -1;
+-}
+-
+-} // namespace Digikam
+diff --git a/libs/models/imageversionsmodel.h b/libs/models/imageversionsmodel.h
+deleted file mode 100644
+index ed08529..0000000
+--- a/libs/models/imageversionsmodel.h
++++ /dev/null
+@@ -1,75 +0,0 @@
+-/* ============================================================
+- *
+- * This file is a part of digiKam project
+- * http://www.digikam.org
+- *
+- * Date : 2010-07-13
+- * Description : Model for image versions
+- *
+- * Copyright (C) 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
+- *
+- * This program is free software; you can redistribute it
+- * and/or modify it under the terms of the GNU General
+- * Public License as published by the Free Software Foundation;
+- * either version 2, or (at your option)
+- * any later version.
+- *
+- * This program is distributed in the hope that it will be useful,
+- * but WITHOUT ANY WARRANTY; without even the implied warranty of
+- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+- * GNU General Public License for more details.
+- *
+- * ============================================================ */
+-
+-#ifndef IMAGEVERSIONSMODEL_H
+-#define IMAGEVERSIONSMODEL_H
+-
+-// Qt includes
+-
+-#include <QModelIndex>
+-#include <QPixmap>
+-
+-// Local includes
+-
+-#include "digikam_export.h"
+-
+-namespace Digikam
+-{
+-
+-class DIGIKAM_DATABASE_EXPORT ImageVersionsModel : public QAbstractListModel
+-{
+- Q_OBJECT
+-
+-public:
+-
+- explicit ImageVersionsModel(QObject* parent = 0);
+- ~ImageVersionsModel();
+-
+- Qt::ItemFlags flags(const QModelIndex& index) const;
+- QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+- int rowCount(const QModelIndex& parent = QModelIndex()) const;
+-
+- void setupModelData(QList<QPair<QString, int> >& data);
+- void clearModelData();
+-
+- QString currentSelectedImage() const;
+- void setCurrentSelectedImage(const QString& path);
+- QModelIndex currentSelectedImageIndex() const;
+-
+- bool paintTree() const;
+- int listIndexOf(const QString& item) const;
+-
+-public Q_SLOTS:
+-
+- void slotAnimationStep();
+- void setPaintTree(bool paint);
+-
+-private:
+-
+- class Private;
+- Private* const d;
+-};
+-
+-} // namespace Digikam
+-
+-#endif // IMAGEVERSIONSMODEL_H
+--
+cgit v0.11.2
+
diff --git a/kde/patch/dolphin.patch b/kde/patch/dolphin.patch
new file mode 100644
index 0000000..dfe3a2f
--- /dev/null
+++ b/kde/patch/dolphin.patch
@@ -0,0 +1,3 @@
+# Let the user decide whether she wants to run Dolphin as root:
+cat $CWD/patch/dolphin/dolphin_revert_noroot.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/dolphin/dolphin_revert_noroot.patch b/kde/patch/dolphin/dolphin_revert_noroot.patch
new file mode 100644
index 0000000..46bb541
--- /dev/null
+++ b/kde/patch/dolphin/dolphin_revert_noroot.patch
@@ -0,0 +1,44 @@
+Taken from openSUSE:
+https://build.opensuse.org/package/view_file/KDE:Applications/dolphin/0001-Revert-Disallow-executing-Dolphin-as-root-on-Linux.patch?expand=1
+
+From ba74d639178916221c748b0d5d89f7ac4f5ed669 Mon Sep 17 00:00:00 2001
+From: Fabian Vogt <fabian@ritter-vogt.de>
+Date: Sat, 22 Apr 2017 14:00:33 +0200
+Subject: [PATCH] Revert "Disallow executing Dolphin as root on Linux"
+
+This reverts commit 0bdd8e0b0516555c6233fdc7901e9b417cf89791.
+We ship a desktop file to open dolphin as root and we allow YaST on the
+desktop. So this patch is absolutely pointless for us.
+---
+ src/main.cpp | 13 -------------
+ 1 file changed, 13 deletions(-)
+
+diff --git a/src/main.cpp b/src/main.cpp
+index 789a52996..acba8daed 100644
+--- a/src/main.cpp
++++ b/src/main.cpp
+@@ -35,21 +35,8 @@
+ #include <KLocalizedString>
+ #include <Kdelibs4ConfigMigrator>
+
+-#ifndef Q_OS_WIN
+-#include <unistd.h>
+-#endif
+-#include <iostream>
+-
+ extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv)
+ {
+-#ifndef Q_OS_WIN
+- // Check whether we are running as root
+- if (getuid() == 0) {
+- std::cout << "Executing Dolphin as root is not possible." << std::endl;
+- return EXIT_FAILURE;
+- }
+-#endif
+-
+ QApplication app(argc, argv);
+ app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
+ app.setWindowIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"), app.windowIcon()));
+--
+2.12.0
+
diff --git a/kde/patch/gwenview.patch b/kde/patch/gwenview.patch
new file mode 100644
index 0000000..04dfe50
--- /dev/null
+++ b/kde/patch/gwenview.patch
@@ -0,0 +1,4 @@
+# Prevent dataloss when importing pictures;
+# Will be fixed in 17.04.2:
+#cat $CWD/patch/gwenview/gwenview-17.04.1_dataloss.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/gwenview/gwenview-17.04.1_dataloss.patch b/kde/patch/gwenview/gwenview-17.04.1_dataloss.patch
new file mode 100644
index 0000000..fa93428
--- /dev/null
+++ b/kde/patch/gwenview/gwenview-17.04.1_dataloss.patch
@@ -0,0 +1,131 @@
+From 6ce5d2f8d82f83c5a3d726ee5807ebaf7a6e3c6c Mon Sep 17 00:00:00 2001
+From: Henrik Fehlauer <rkflx@lab12.net>
+Date: Thu, 11 May 2017 22:40:15 +0200
+Subject: Avoid data loss when importing pictures
+
+Summary:
+Fix porting regressions, which left users of Gwenview Importer with:
+- failed import (import destination still empty)
+- additionally (when choosing "Delete" instead of "Keep" after import):
+ pictures also removed from import source, with no way to recover
+
+Correct additional problems remaining after fixing the import failure:
+- hang on duplicate filenames
+- identically named files with different content are never imported
+- error dialog when deleting pictures from import source
+
+BUG: 379615
+
+In detail:
+
+1st problem (introduced in 017b4fe5dc7f4b6e876cfd7b108dcebbf609ae94):
+
+ Initially, pictures are copied to a temporary folder
+ (e.g. "foo/.gwenview_importer-IcQqvo/"), before being moved/renamed
+ to the final destination (e.g. "foo/"), which is determined by
+ calling "cd .." on the temporary folder.
+
+ However, mistakenly this path contains a superfluous '/'
+ (e.g. "foo/.gwenview_importer-IcQqvo//"), resulting in the final
+ destination reading "foo/.gwenview_importer-IcQqvo/" instead of
+ "foo/". On cleanup, the temporary folder is removed, simultaneously
+ deleting all new pictures.
+
+ Fix: Omit '/' where appropriate.
+
+2nd problem (broken by a3262e9b197ee97323ef8bf3a9dca1e13f61a74c):
+
+ When trying to find a unique filename, the while loop "stat"ing the
+ file repeats forever.
+
+ Fix: Call "KIO::stat" and "KJobWidgets::setWindow"
+ also inside the loop.
+
+3rd problem (introduced in 017b4fe5dc7f4b6e876cfd7b108dcebbf609ae94):
+
+ If the destination directory already contains an identically named
+ file, we try to find a different name by appending a number. For
+ this, we need to strip the old filename from the full path.
+ Unfortunately, only calling "path()" is incorrect, giving
+ "foo/pict0001.jpg/pict0001_1.jpg", rather than "foo/pict0001_1.jpg".
+
+ Fix: Use "adjusted(QUrl::RemoveFilename)".
+
+4th problem (broken by a3262e9b197ee97323ef8bf3a9dca1e13f61a74c):
+
+ Although deletion works, we show a warning dialog stating failure.
+
+ Fix: Invert the logic of "job->exec()", as the error handling should
+ be skipped by "break"ing out of the while loop.
+
+Test Plan:
+Steps to reproduce (see https://bugs.kde.org/show_bug.cgi?id=379615) no longer result in data loss.
+
+Autotests for importer (separate review request in D5750) pass. Hopefully, this helps catching any future porting regressions.
+
+Reviewers: ltoscano, sandsmark, gateau
+
+Reviewed By: ltoscano
+
+Differential Revision: https://phabricator.kde.org/D5749
+---
+ importer/fileutils.cpp | 5 ++++-
+ importer/importdialog.cpp | 2 +-
+ importer/importer.cpp | 4 ++--
+ 3 files changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/importer/fileutils.cpp b/importer/fileutils.cpp
+index 5293d56..a51a18c 100644
+--- a/importer/fileutils.cpp
++++ b/importer/fileutils.cpp
+@@ -128,7 +128,10 @@ RenameResult rename(const QUrl& src, const QUrl& dst_, QWidget* authWindow)
+ }
+ result = RenamedUnderNewName;
+
+- dst.setPath(dst.path() + '/' + prefix + QString::number(count) + suffix);
++ dst.setPath(dst.adjusted(QUrl::RemoveFilename).path() + prefix + QString::number(count) + suffix);
++ statJob = KIO::stat(dst);
++ KJobWidgets::setWindow(statJob, authWindow);
++
+ ++count;
+ }
+
+diff --git a/importer/importdialog.cpp b/importer/importdialog.cpp
+index ee6f7cd..5e9e847 100644
+--- a/importer/importdialog.cpp
++++ b/importer/importdialog.cpp
+@@ -121,7 +121,7 @@ public:
+ QList<QUrl> urls = importedUrls + skippedUrls;
+ while (true) {
+ KIO::Job* job = KIO::del(urls);
+- if (!job->exec()) {
++ if (job->exec()) {
+ break;
+ }
+ // Deleting failed
+diff --git a/importer/importer.cpp b/importer/importer.cpp
+index 51c4b96..a7e1d4e 100644
+--- a/importer/importer.cpp
++++ b/importer/importer.cpp
+@@ -98,7 +98,7 @@ struct ImporterPrivate
+ }
+ mCurrentUrl = mUrlList.takeFirst();
+ QUrl dst = mTempImportDirUrl;
+- dst.setPath(dst.path() + '/' + mCurrentUrl.fileName());
++ dst.setPath(dst.path() + mCurrentUrl.fileName());
+ KIO::Job* job = KIO::copy(mCurrentUrl, dst, KIO::HideProgressInfo);
+ KJobWidgets::setWindow(job, mAuthWindow);
+ QObject::connect(job, SIGNAL(result(KJob*)),
+@@ -122,7 +122,7 @@ struct ImporterPrivate
+ } else {
+ fileName = src.fileName();
+ }
+- dst.setPath(dst.path() + '/' + fileName);
++ dst.setPath(dst.path() + fileName);
+
+ FileUtils::RenameResult result = FileUtils::rename(src, dst, mAuthWindow);
+ switch (result) {
+--
+cgit v0.11.2
+
+
diff --git a/kde/patch/kalzium.patch b/kde/patch/kalzium.patch
new file mode 100644
index 0000000..6593e9d
--- /dev/null
+++ b/kde/patch/kalzium.patch
@@ -0,0 +1,3 @@
+# Fix build against KF 5.31.0 (repaired in 16.12.3):
+#cat $CWD/patch/kalzium/kalzium_kf_5.31.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kalzium/kalzium_kf_5.31.patch b/kde/patch/kalzium/kalzium_kf_5.31.patch
new file mode 100644
index 0000000..a3d4e00
--- /dev/null
+++ b/kde/patch/kalzium/kalzium_kf_5.31.patch
@@ -0,0 +1,156 @@
+Patch taken from:
+https://gitweb.gentoo.org/repo/gentoo.git/plain/kde-apps/kalzium/files/kalzium-16.12.2-kf-5.31.patch
+
+From f233d458959548ab371e3faeca7313f746625afc Mon Sep 17 00:00:00 2001
+From: Heiko Becker <heirecka@exherbo.org>
+Date: Sun, 22 Jan 2017 14:46:24 +0100
+Subject: Fix build with extra-cmake-modules > 5.30
+
+Since a5f3a76e14799c68b5e8f74e375baa5f6f6ab4dc in
+extra-cmake-modules.git -fno-operator-names is passed to the build
+(when supported), causing a build error for kalzium.
+
+REVIEW: 129873
+---
+ src/calculator/titrationCalculator.cpp | 39 +++++++++++++++-------------------
+ 1 file changed, 17 insertions(+), 22 deletions(-)
+
+diff --git a/src/calculator/titrationCalculator.cpp b/src/calculator/titrationCalculator.cpp
+index 44ea152..6ea9ac9 100644
+--- a/src/calculator/titrationCalculator.cpp
++++ b/src/calculator/titrationCalculator.cpp
+@@ -41,11 +41,6 @@
+
+ using namespace std;
+
+-#ifdef _MSC_VER
+-#define and &&
+-#define or ||
+-#endif
+-
+ titrationCalculator::titrationCalculator(QWidget * parent) : QWidget(parent)
+ {
+ xmin = 0;
+@@ -112,7 +107,7 @@ void titrationCalculator::plot()
+ }
+ QString mreporto;
+ int iter = 0;
+- if (uid.xaxis->text() == "" or uid.xaxis->text() == " ") {
++ if (uid.xaxis->text() == "" || uid.xaxis->text() == " ") {
+ uid.xaxis->setText(i18n("nothing"));
+ }
+ if (tmpy == 0) {
+@@ -121,11 +116,11 @@ void titrationCalculator::plot()
+ //now we have to solve the system of equations NOTE:yvalue contains the equation of Y-axis variable
+ //we iterates the process until you have an equation in one only unknown variable or a numeric expression
+ mreporto = solve(yvalue);
+- while (end == 0 or lettere == 1) {
++ while (end == 0 || lettere == 1) {
+ QByteArray ba = mreporto.toLatin1();
+ char *tmreport = ba.data();
+ ++iter;
+- if (end == 1 or lettere == 0) {
++ if (end == 1 || lettere == 0) {
+ break;
+ }
+ if (iter > 100) {
+@@ -273,13 +268,13 @@ QString titrationCalculator::solve(char *yvalue)
+ QString tempyval;
+ QString ptem;
+ for (int i = 0; strlen(yvalue) + 1; ++i) {
+- if (!(yvalue[i]=='q' or yvalue[i]=='w' or yvalue[i]=='e' or yvalue[i]=='r' or yvalue[i]=='t' or yvalue[i]=='y' or yvalue[i]=='u' or yvalue[i]=='i' or yvalue[i]=='o' or yvalue[i]=='p' or yvalue[i]=='a' or yvalue[i]=='s' or yvalue[i]=='d' or yvalue[i]=='f' or yvalue[i]=='g' or yvalue[i]=='h' or yvalue[i]=='j' or yvalue[i]=='k' or yvalue[i]=='l' or yvalue[i]=='z' or yvalue[i]=='x' or yvalue[i]=='c' or yvalue[i]=='v' or yvalue[i]=='b' or yvalue[i]=='n' or yvalue[i]=='m' or yvalue[i]=='+' or yvalue[i]=='-' or yvalue[i]=='^' or yvalue[i]=='*' or yvalue[i]=='/' or yvalue[i]=='(' or yvalue[i]==')' or yvalue[i]=='Q' or yvalue[i]=='W' or yvalue[i]=='E' or yvalue[i]=='R' or yvalue[i]=='T' or yvalue[i]=='Y' or yvalue[i]=='U' or yvalue[i]=='I' or yvalue[i]=='O' or yvalue[i]=='P' or yvalue[i]=='A' or yvalue[i]=='S' or yvalue[i]=='D' or yvalue[i]=='F' or yvalue[i]=='G' or yvalue[i]=='H' or yvalue[i]=='J' or yvalue[i]=='K' or yvalue[i]=='L' or yvalue[i]=='Z' or yvalue[i]=='X' or yvalue[i]=='C' or yvalue[i]=='V' or yvalue[i]=='B' or yvalue[i]=='N' or yvalue[i]=='M' or yvalue[i]=='1' or yvalue[i]=='2' or yvalue[i]=='3' or yvalue[i]=='4' or yvalue[i]=='5' or yvalue[i]=='6' or yvalue[i]=='7' or yvalue[i]=='8' or yvalue[i]=='9' or yvalue[i]=='0' or yvalue[i]=='.' or yvalue[i]==',')) {
++ if (!(yvalue[i]=='q' || yvalue[i]=='w' || yvalue[i]=='e' || yvalue[i]=='r' || yvalue[i]=='t' || yvalue[i]=='y' || yvalue[i]=='u' || yvalue[i]=='i' || yvalue[i]=='o' || yvalue[i]=='p' || yvalue[i]=='a' || yvalue[i]=='s' || yvalue[i]=='d' || yvalue[i]=='f' || yvalue[i]=='g' || yvalue[i]=='h' || yvalue[i]=='j' || yvalue[i]=='k' || yvalue[i]=='l' || yvalue[i]=='z' || yvalue[i]=='x' || yvalue[i]=='c' || yvalue[i]=='v' || yvalue[i]=='b' || yvalue[i]=='n' || yvalue[i]=='m' || yvalue[i]=='+' || yvalue[i]=='-' || yvalue[i]=='^' || yvalue[i]=='*' || yvalue[i]=='/' || yvalue[i]=='(' || yvalue[i]==')' || yvalue[i]=='Q' || yvalue[i]=='W' || yvalue[i]=='E' || yvalue[i]=='R' || yvalue[i]=='T' || yvalue[i]=='Y' || yvalue[i]=='U' || yvalue[i]=='I' || yvalue[i]=='O' || yvalue[i]=='P' || yvalue[i]=='A' || yvalue[i]=='S' || yvalue[i]=='D' || yvalue[i]=='F' || yvalue[i]=='G' || yvalue[i]=='H' || yvalue[i]=='J' || yvalue[i]=='K' || yvalue[i]=='L' || yvalue[i]=='Z' || yvalue[i]=='X' || yvalue[i]=='C' || yvalue[i]=='V' || yvalue[i]=='B' || yvalue[i]=='N' || yvalue[i]=='M' || yvalue[i]=='1' || yvalue[i]=='2' || yvalue[i]=='3' || yvalue[i]=='4' || yvalue[i]=='5' || yvalue[i]=='6' || yvalue[i]=='7' || yvalue[i]=='8' || yvalue[i]=='9' || yvalue[i]=='0' || yvalue[i]=='.' || yvalue[i]==',')) {
+ break; //if current value is not a permitted value, this means that something is wrong
+ }
+- if (yvalue[i]=='q' or yvalue[i]=='w' or yvalue[i]=='e' or yvalue[i]=='r' or yvalue[i]=='t' or yvalue[i]=='y' or yvalue[i]=='u' or yvalue[i]=='i' or yvalue[i]=='o' or yvalue[i]=='p' or yvalue[i]=='a' or yvalue[i]=='s' or yvalue[i]=='d' or yvalue[i]=='f' or yvalue[i]=='g' or yvalue[i]=='h' or yvalue[i]=='j' or yvalue[i]=='k' or yvalue[i]=='l' or yvalue[i]=='z' or yvalue[i]=='x' or yvalue[i]=='c' or yvalue[i]=='v' or yvalue[i]=='b' or yvalue[i]=='n' or yvalue[i]=='m' or yvalue[i]=='Q' or yvalue[i]=='W' or yvalue[i]=='E' or yvalue[i]=='R' or yvalue[i]=='T' or yvalue[i]=='Y' or yvalue[i]=='U' or yvalue[i]=='I' or yvalue[i]=='O' or yvalue[i]=='P' or yvalue[i]=='A' or yvalue[i]=='S' or yvalue[i]=='D' or yvalue[i]=='F' or yvalue[i]=='G' or yvalue[i]=='H' or yvalue[i]=='J' or yvalue[i]=='K' or yvalue[i]=='L' or yvalue[i]=='Z' or yvalue[i]=='X' or yvalue[i]=='C' or yvalue[i]=='V' or yvalue[i]=='B' or yvalue[i]=='N' or yvalue[i]=='M' or yvalue[i]=='.' or yvalue[i]==',') {
++ if (yvalue[i]=='q' || yvalue[i]=='w' || yvalue[i]=='e' || yvalue[i]=='r' || yvalue[i]=='t' || yvalue[i]=='y' || yvalue[i]=='u' || yvalue[i]=='i' || yvalue[i]=='o' || yvalue[i]=='p' || yvalue[i]=='a' || yvalue[i]=='s' || yvalue[i]=='d' || yvalue[i]=='f' || yvalue[i]=='g' || yvalue[i]=='h' || yvalue[i]=='j' || yvalue[i]=='k' || yvalue[i]=='l' || yvalue[i]=='z' || yvalue[i]=='x' || yvalue[i]=='c' || yvalue[i]=='v' || yvalue[i]=='b' || yvalue[i]=='n' || yvalue[i]=='m' || yvalue[i]=='Q' || yvalue[i]=='W' || yvalue[i]=='E' || yvalue[i]=='R' || yvalue[i]=='T' || yvalue[i]=='Y' || yvalue[i]=='U' || yvalue[i]=='I' || yvalue[i]=='O' || yvalue[i]=='P' || yvalue[i]=='A' || yvalue[i]=='S' || yvalue[i]=='D' || yvalue[i]=='F' || yvalue[i]=='G' || yvalue[i]=='H' || yvalue[i]=='J' || yvalue[i]=='K' || yvalue[i]=='L' || yvalue[i]=='Z' || yvalue[i]=='X' || yvalue[i]=='C' || yvalue[i]=='V' || yvalue[i]=='B' || yvalue[i]=='N' || yvalue[i]=='M' || yvalue[i]=='.' || yvalue[i]==',') {
+ lettere = 1; //if lettere == 0 then the equation contains only mnumbers
+ }
+- if (yvalue[i]=='+' or yvalue[i]=='-' or yvalue[i]=='^' or yvalue[i]=='*' or yvalue[i]=='/' or yvalue[i]=='(' or yvalue[i]==')' or yvalue[i]=='1' or yvalue[i]=='2' or yvalue[i]=='3' or yvalue[i]=='4' or yvalue[i]=='5' or yvalue[i]=='6' or yvalue[i]=='7' or yvalue[i]=='8' or yvalue[i]=='9' or yvalue[i]=='0' or yvalue[i]=='.' or yvalue[i]==',') {
++ if (yvalue[i]=='+' || yvalue[i]=='-' || yvalue[i]=='^' || yvalue[i]=='*' || yvalue[i]=='/' || yvalue[i]=='(' || yvalue[i]==')' || yvalue[i]=='1' || yvalue[i]=='2' || yvalue[i]=='3' || yvalue[i]=='4' || yvalue[i]=='5' || yvalue[i]=='6' || yvalue[i]=='7' || yvalue[i]=='8' || yvalue[i]=='9' || yvalue[i]=='0' || yvalue[i]=='.' || yvalue[i]==',') {
+ tempyval = tempyval + QString(yvalue[i]);
+ } else {
+ tempy = tempy + QString(yvalue[i]);
+@@ -302,7 +297,7 @@ QString titrationCalculator::solve(char *yvalue)
+ end = 1;
+ }
+ if (tempy!=uid.xaxis->text()) {
+- if (yvalue[i]=='+' or yvalue[i]=='-' or yvalue[i]=='^' or yvalue[i]=='*' or yvalue[i]=='/' or yvalue[i]=='(' or yvalue[i]==')' or yvalue[i]=='1' or yvalue[i]=='2' or yvalue[i]=='3' or yvalue[i]=='4' or yvalue[i]=='5' or yvalue[i]=='6' or yvalue[i]=='7' or yvalue[i]=='8' or yvalue[i]=='9' or yvalue[i]=='0' or yvalue[i]=='.' or yvalue[i]==',') {
++ if (yvalue[i]=='+' || yvalue[i]=='-' || yvalue[i]=='^' || yvalue[i]=='*' || yvalue[i]=='/' || yvalue[i]=='(' || yvalue[i]==')' || yvalue[i]=='1' || yvalue[i]=='2' || yvalue[i]=='3' || yvalue[i]=='4' || yvalue[i]=='5' || yvalue[i]=='6' || yvalue[i]=='7' || yvalue[i]=='8' || yvalue[i]=='9' || yvalue[i]=='0' || yvalue[i]=='.' || yvalue[i]==',') {
+ //actually nothing
+ } else {
+ end = 0;
+@@ -335,13 +330,13 @@ QString titrationCalculator::solvex(char *yvalue, QString dnum) {
+ QString tempyval;
+ tempy = "";
+ for (int i = 0; strlen(yvalue) + 1; ++i) {
+- if (!(yvalue[i]=='q' or yvalue[i]=='w' or yvalue[i]=='e' or yvalue[i]=='r' or yvalue[i]=='t' or yvalue[i]=='y' or yvalue[i]=='u' or yvalue[i]=='i' or yvalue[i]=='o' or yvalue[i]=='p' or yvalue[i]=='a' or yvalue[i]=='s' or yvalue[i]=='d' or yvalue[i]=='f' or yvalue[i]=='g' or yvalue[i]=='h' or yvalue[i]=='j' or yvalue[i]=='k' or yvalue[i]=='l' or yvalue[i]=='z' or yvalue[i]=='x' or yvalue[i]=='c' or yvalue[i]=='v' or yvalue[i]=='b' or yvalue[i]=='n' or yvalue[i]=='m' or yvalue[i]=='+' or yvalue[i]=='-' or yvalue[i]=='^' or yvalue[i]=='*' or yvalue[i]=='/' or yvalue[i]=='(' or yvalue[i]==')' or yvalue[i]=='Q' or yvalue[i]=='W' or yvalue[i]=='E' or yvalue[i]=='R' or yvalue[i]=='T' or yvalue[i]=='Y' or yvalue[i]=='U' or yvalue[i]=='I' or yvalue[i]=='O' or yvalue[i]=='P' or yvalue[i]=='A' or yvalue[i]=='S' or yvalue[i]=='D' or yvalue[i]=='F' or yvalue[i]=='G' or yvalue[i]=='H' or yvalue[i]=='J' or yvalue[i]=='K' or yvalue[i]=='L' or yvalue[i]=='Z' or yvalue[i]=='X' or yvalue[i]=='C' or yvalue[i]=='V' or yvalue[i]=='B' or yvalue[i]=='N' or yvalue[i]=='M' or yvalue[i]=='1' or yvalue[i]=='2' or yvalue[i]=='3' or yvalue[i]=='4' or yvalue[i]=='5' or yvalue[i]=='6' or yvalue[i]=='7' or yvalue[i]=='8' or yvalue[i]=='9' or yvalue[i]=='0' or yvalue[i]=='.' or yvalue[i]==',')) {
++ if (!(yvalue[i]=='q' || yvalue[i]=='w' || yvalue[i]=='e' || yvalue[i]=='r' || yvalue[i]=='t' || yvalue[i]=='y' || yvalue[i]=='u' || yvalue[i]=='i' || yvalue[i]=='o' || yvalue[i]=='p' || yvalue[i]=='a' || yvalue[i]=='s' || yvalue[i]=='d' || yvalue[i]=='f' || yvalue[i]=='g' || yvalue[i]=='h' || yvalue[i]=='j' || yvalue[i]=='k' || yvalue[i]=='l' || yvalue[i]=='z' || yvalue[i]=='x' || yvalue[i]=='c' || yvalue[i]=='v' || yvalue[i]=='b' || yvalue[i]=='n' || yvalue[i]=='m' || yvalue[i]=='+' || yvalue[i]=='-' || yvalue[i]=='^' || yvalue[i]=='*' || yvalue[i]=='/' || yvalue[i]=='(' || yvalue[i]==')' || yvalue[i]=='Q' || yvalue[i]=='W' || yvalue[i]=='E' || yvalue[i]=='R' || yvalue[i]=='T' || yvalue[i]=='Y' || yvalue[i]=='U' || yvalue[i]=='I' || yvalue[i]=='O' || yvalue[i]=='P' || yvalue[i]=='A' || yvalue[i]=='S' || yvalue[i]=='D' || yvalue[i]=='F' || yvalue[i]=='G' || yvalue[i]=='H' || yvalue[i]=='J' || yvalue[i]=='K' || yvalue[i]=='L' || yvalue[i]=='Z' || yvalue[i]=='X' || yvalue[i]=='C' || yvalue[i]=='V' || yvalue[i]=='B' || yvalue[i]=='N' || yvalue[i]=='M' || yvalue[i]=='1' || yvalue[i]=='2' || yvalue[i]=='3' || yvalue[i]=='4' || yvalue[i]=='5' || yvalue[i]=='6' || yvalue[i]=='7' || yvalue[i]=='8' || yvalue[i]=='9' || yvalue[i]=='0' || yvalue[i]=='.' || yvalue[i]==',')) {
+ break; //if current value is not a permitted value, this means that something is wrong
+ }
+- if (yvalue[i]=='q' or yvalue[i]=='w' or yvalue[i]=='e' or yvalue[i]=='r' or yvalue[i]=='t' or yvalue[i]=='y' or yvalue[i]=='u' or yvalue[i]=='i' or yvalue[i]=='o' or yvalue[i]=='p' or yvalue[i]=='a' or yvalue[i]=='s' or yvalue[i]=='d' or yvalue[i]=='f' or yvalue[i]=='g' or yvalue[i]=='h' or yvalue[i]=='j' or yvalue[i]=='k' or yvalue[i]=='l' or yvalue[i]=='z' or yvalue[i]=='x' or yvalue[i]=='c' or yvalue[i]=='v' or yvalue[i]=='b' or yvalue[i]=='n' or yvalue[i]=='m' or yvalue[i]=='Q' or yvalue[i]=='W' or yvalue[i]=='E' or yvalue[i]=='R' or yvalue[i]=='T' or yvalue[i]=='Y' or yvalue[i]=='U' or yvalue[i]=='I' or yvalue[i]=='O' or yvalue[i]=='P' or yvalue[i]=='A' or yvalue[i]=='S' or yvalue[i]=='D' or yvalue[i]=='F' or yvalue[i]=='G' or yvalue[i]=='H' or yvalue[i]=='J' or yvalue[i]=='K' or yvalue[i]=='L' or yvalue[i]=='Z' or yvalue[i]=='X' or yvalue[i]=='C' or yvalue[i]=='V' or yvalue[i]=='B' or yvalue[i]=='N' or yvalue[i]=='M' or yvalue[i]=='.' or yvalue[i]==',') {
++ if (yvalue[i]=='q' || yvalue[i]=='w' || yvalue[i]=='e' || yvalue[i]=='r' || yvalue[i]=='t' || yvalue[i]=='y' || yvalue[i]=='u' || yvalue[i]=='i' || yvalue[i]=='o' || yvalue[i]=='p' || yvalue[i]=='a' || yvalue[i]=='s' || yvalue[i]=='d' || yvalue[i]=='f' || yvalue[i]=='g' || yvalue[i]=='h' || yvalue[i]=='j' || yvalue[i]=='k' || yvalue[i]=='l' || yvalue[i]=='z' || yvalue[i]=='x' || yvalue[i]=='c' || yvalue[i]=='v' || yvalue[i]=='b' || yvalue[i]=='n' || yvalue[i]=='m' || yvalue[i]=='Q' || yvalue[i]=='W' || yvalue[i]=='E' || yvalue[i]=='R' || yvalue[i]=='T' || yvalue[i]=='Y' || yvalue[i]=='U' || yvalue[i]=='I' || yvalue[i]=='O' || yvalue[i]=='P' || yvalue[i]=='A' || yvalue[i]=='S' || yvalue[i]=='D' || yvalue[i]=='F' || yvalue[i]=='G' || yvalue[i]=='H' || yvalue[i]=='J' || yvalue[i]=='K' || yvalue[i]=='L' || yvalue[i]=='Z' || yvalue[i]=='X' || yvalue[i]=='C' || yvalue[i]=='V' || yvalue[i]=='B' || yvalue[i]=='N' || yvalue[i]=='M' || yvalue[i]=='.' || yvalue[i]==',') {
+ tempy = tempy + yvalue[i]; //if lettere == 0 then the equation contains only mnumbers
+ }
+- if (yvalue[i]=='+' or yvalue[i]=='-' or yvalue[i]=='^' or yvalue[i]=='*' or yvalue[i]=='/' or yvalue[i]=='(' or yvalue[i]==')' or yvalue[i]=='1' or yvalue[i]=='2' or yvalue[i]=='3' or yvalue[i]=='4' or yvalue[i]=='5' or yvalue[i]=='6' or yvalue[i]=='7' or yvalue[i]=='8' or yvalue[i]=='9' or yvalue[i]=='0' or yvalue[i]=='.' or yvalue[i]==',') {
++ if (yvalue[i]=='+' || yvalue[i]=='-' || yvalue[i]=='^' || yvalue[i]=='*' || yvalue[i]=='/' || yvalue[i]=='(' || yvalue[i]==')' || yvalue[i]=='1' || yvalue[i]=='2' || yvalue[i]=='3' || yvalue[i]=='4' || yvalue[i]=='5' || yvalue[i]=='6' || yvalue[i]=='7' || yvalue[i]=='8' || yvalue[i]=='9' || yvalue[i]=='0' || yvalue[i]=='.' || yvalue[i]==',') {
+ if (!tempyolda.isEmpty()) {
+ tempy = tempy + yvalue[i];
+ if (tempyolda == uid.xaxis->text()) {
+@@ -359,7 +354,7 @@ QString titrationCalculator::solvex(char *yvalue, QString dnum) {
+ tempyolda = tempyold;
+ } else {
+ tempyold = "";
+- if (((olda != 1) and (yvalue[i + 1] != '^')) or (yvalue[i] == '+' or yvalue[i] == '-' or yvalue[i] == '^' or yvalue[i] == '*' or yvalue[i] == '/' or yvalue[i] == '(' or yvalue[i] == ')')) {
++ if (((olda != 1) && (yvalue[i + 1] != '^')) || (yvalue[i] == '+' || yvalue[i] == '-' || yvalue[i] == '^' || yvalue[i] == '*' || yvalue[i] == '/' || yvalue[i] == '(' || yvalue[i] == ')')) {
+ tempyval = tempyval + QString(yvalue[i]);
+ }
+ }
+@@ -374,7 +369,7 @@ QString titrationCalculator::solvex(char *yvalue, QString dnum) {
+ tempyold = "";
+ olda = 1;
+ }
+- if ((tempy==uid.xaxis->text()) and (!tempyolda.isEmpty())) {
++ if ((tempy==uid.xaxis->text()) && (!tempyolda.isEmpty())) {
+ if (yvalue[i + 1] != '^') {
+ tempyval = tempyval + dnum;
+ }
+@@ -611,7 +606,7 @@ void titrationCalculator::on_actionOpen_triggered()
+ if (tmpchr != '|') {
+ tempyval = tempyval + tmpchr;
+ } else {
+- if ((tablea == 1) and (tempyval != QString("table1")) and (tempyval != QString("table2")) and (tempyval != QString("xaxis")) and (tempyval != QString("yaxis")) and (tempyval != QString("note"))) {
++ if ((tablea == 1) && (tempyval != QString("table1")) && (tempyval != QString("table2")) && (tempyval != QString("xaxis")) && (tempyval != QString("yaxis")) && (tempyval != QString("note"))) {
+ if ((i % 2) != 0) {
+ QTableWidgetItem *titemo = uid.tableWidget->item((i - 1) / 2, 1);
+ if (titemo) {
+@@ -626,7 +621,7 @@ void titrationCalculator::on_actionOpen_triggered()
+ ++i;
+ }
+
+- if ((tableb == 1) and (tempyval != QString("table1")) and (tempyval != QString("table2")) and (tempyval != QString("xaxis")) and (tempyval != QString("yaxis")) and (tempyval != QString("note"))) {
++ if ((tableb == 1) && (tempyval != QString("table1")) && (tempyval != QString("table2")) && (tempyval != QString("xaxis")) && (tempyval != QString("yaxis")) && (tempyval != QString("note"))) {
+ if ((i % 2) != 0) {
+ QTableWidgetItem *titemo = uid.tableWidget_2->item((i - 1) / 2, 1);
+ if (titemo) {
+@@ -641,13 +636,13 @@ void titrationCalculator::on_actionOpen_triggered()
+ }
+ ++i;
+ }
+- if ((xax == 1) and (tempyval != QString("table1")) and (tempyval != QString("table2")) and (tempyval != QString("xaxis")) and (tempyval != QString("yaxis")) and (tempyval != QString("note"))) {
++ if ((xax == 1) && (tempyval != QString("table1")) && (tempyval != QString("table2")) && (tempyval != QString("xaxis")) && (tempyval != QString("yaxis")) && (tempyval != QString("note"))) {
+ uid.xaxis->setText(tempyval);
+ }
+- if ((yax == 1) and (tempyval != QString("table1")) and (tempyval != QString("table2")) and (tempyval != QString("xaxis")) and (tempyval != QString("yaxis")) and (tempyval != QString("note"))) {
++ if ((yax == 1) && (tempyval != QString("table1")) && (tempyval != QString("table2")) && (tempyval != QString("xaxis")) && (tempyval != QString("yaxis")) && (tempyval != QString("note"))) {
+ uid.yaxis->setText(tempyval);
+ }
+- if ((notea == 1) and (tempyval != QString("table1")) and (tempyval != QString("table2")) and (tempyval != QString("xaxis")) and (tempyval != QString("yaxis")) and (tempyval != QString("note"))) {
++ if ((notea == 1) && (tempyval != QString("table1")) && (tempyval != QString("table2")) && (tempyval != QString("xaxis")) && (tempyval != QString("yaxis")) && (tempyval != QString("note"))) {
+ uid.note->setText(tempyval);
+ }
+
+--
+cgit v0.11.2
+
+
diff --git a/kde/patch/kate.patch b/kde/patch/kate.patch
index d8cd42f..45c2f53 100644
--- a/kde/patch/kate.patch
+++ b/kde/patch/kate.patch
@@ -2,3 +2,7 @@
# Should be fixed after 15.12.0.
#cat $CWD/patch/kate/kate-15.12.0.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+# Allow Kate to be started by the root user; disallowing this is not
+# a decision that a developer should make for the user, it is patronizing:
+cat $CWD/patch/kate/kate_runasroot.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kate/kate_runasroot.patch b/kde/patch/kate/kate_runasroot.patch
new file mode 100644
index 0000000..88dbbe9
--- /dev/null
+++ b/kde/patch/kate/kate_runasroot.patch
@@ -0,0 +1,48 @@
+From 435ed5853b9451ab8fdfff722545c57a8f154625 Mon Sep 17 00:00:00 2001
+From: Fabian Vogt <fabian@ritter-vogt.de>
+Date: Sat, 18 Feb 2017 13:49:14 +0100
+Subject: [PATCH] Defuse root block
+
+While the main point is correct as any application running in the same
+X session (not sandboxed) can use kate's capability to open a console,
+we allow (even encourage) running YaST on X11 as root.
+That way it's only an impact on usability.
+---
+ kate/main.cpp | 3 +--
+ kwrite/main.cpp | 3 +--
+ 2 files changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/kate/main.cpp b/kate/main.cpp
+index 342cd5db3..4845646aa 100644
+--- a/kate/main.cpp
++++ b/kate/main.cpp
+@@ -64,9 +64,8 @@ int main(int argc, char **argv)
+ * Check whether we are running as root
+ **/
+ if (getuid() == 0) {
+- std::cout << "Executing Kate as root is not possible. To edit files as root use:" << std::endl;
++ std::cout << "THIS IS POTENTIALLY INSECURE!\nTo edit files as root please use:" << std::endl;
+ std::cout << "SUDO_EDITOR=kate sudoedit <file>" << std::endl;
+- return 0;
+ }
+ #endif
+ /**
+diff --git a/kwrite/main.cpp b/kwrite/main.cpp
+index 68a055edb..4937f72d3 100644
+--- a/kwrite/main.cpp
++++ b/kwrite/main.cpp
+@@ -54,9 +54,8 @@ extern "C" Q_DECL_EXPORT int main(int argc, char **argv)
+ * Check whether we are running as root
+ **/
+ if (getuid() == 0) {
+- std::cout << "Executing KWrite as root is not possible. To edit files as root use:" << std::endl;
++ std::cout << "THIS IS POTENTIALLY INSECURE!\nTo edit files as root please use:" << std::endl;
+ std::cout << "SUDO_EDITOR=kwrite sudoedit <file>" << std::endl;
+- return 0;
+ }
+ #endif
+ /**
+--
+2.12.2
+
+
diff --git a/kde/patch/kcalcore.patch b/kde/patch/kcalcore.patch
new file mode 100644
index 0000000..051150d
--- /dev/null
+++ b/kde/patch/kcalcore.patch
@@ -0,0 +1,4 @@
+# Fix compile error against new libical 3:
+# Fixed in Applications 17.12.0:
+#cat $CWD/patch/kcalcore/kcalcore_libical3.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kcalcore/kcalcore_libical3.patch b/kde/patch/kcalcore/kcalcore_libical3.patch
new file mode 100644
index 0000000..0a5155f
--- /dev/null
+++ b/kde/patch/kcalcore/kcalcore_libical3.patch
@@ -0,0 +1,109 @@
+From 27eaa211b23a6bb0bcba5a91cf7cadfc1e888e21 Mon Sep 17 00:00:00 2001
+From: Allen Winter <winter@kde.org>
+Date: Fri, 6 Oct 2017 10:39:20 -0400
+Subject: icalformat_p.cpp, icaltimezones.cpp - follow API changes in libical3
+
+---
+ src/icalformat_p.cpp | 11 ++++++-----
+ src/icaltimezones.cpp | 10 ++++------
+ 2 files changed, 10 insertions(+), 11 deletions(-)
+
+diff --git a/src/icalformat_p.cpp b/src/icalformat_p.cpp
+index bd1d8a3..c2e4548 100644
+--- a/src/icalformat_p.cpp
++++ b/src/icalformat_p.cpp
+@@ -2355,7 +2355,6 @@ icaltimetype ICalFormatImpl::writeICalDate(const QDate &date)
+ t.second = 0;
+
+ t.is_date = 1;
+- t.is_utc = 0;
+ t.zone = nullptr;
+
+ return t;
+@@ -2377,7 +2376,9 @@ icaltimetype ICalFormatImpl::writeICalDateTime(const QDateTime &datetime, bool d
+ t.second = datetime.time().second();
+ }
+ t.zone = nullptr; // zone is NOT set
+- t.is_utc = datetime.isUtc() ? 1 : 0;
++ if ( datetime.isUtc() ) {
++ t = icaltime_convert_to_zone(t, icaltimezone_get_utc_timezone());
++ }
+
+ // _dumpIcaltime( t );
+
+@@ -2450,7 +2452,7 @@ icalproperty *ICalFormatImpl::writeICalDateTimeProperty(const icalproperty_kind
+ }
+
+ KTimeZone ktz;
+- if (!t.is_utc) {
++ if (!icaltime_is_utc(t)) {
+ ktz = dt.timeZone();
+ }
+
+@@ -2483,7 +2484,7 @@ QDateTime ICalFormatImpl::readICalDateTime(icalproperty *p, const icaltimetype &
+ // _dumpIcaltime( t );
+
+ KDateTime::Spec timeSpec;
+- if (t.is_utc || t.zone == icaltimezone_get_utc_timezone()) {
++ if (icaltime_is_utc(t) || t.zone == icaltimezone_get_utc_timezone()) {
+ timeSpec = KDateTime::UTC; // the time zone is UTC
+ utc = false; // no need to convert to UTC
+ } else {
+diff --git a/src/icaltimezones.cpp b/src/icaltimezones.cpp
+index 2f6d42f..f8f8d5d 100644
+--- a/src/icaltimezones.cpp
++++ b/src/icaltimezones.cpp
+@@ -54,7 +54,7 @@ static QDateTime toQDateTime(const icaltimetype &t)
+ {
+ return QDateTime(QDate(t.year, t.month, t.day),
+ QTime(t.hour, t.minute, t.second),
+- (t.is_utc ? Qt::UTC : Qt::LocalTime));
++ (icaltime_is_utc(t) ? Qt::UTC : Qt::LocalTime));
+ }
+
+ // Maximum date for time zone data.
+@@ -81,7 +81,6 @@ static icaltimetype writeLocalICalDateTime(const QDateTime &utc, int offset)
+ t.second = local.time().second();
+ t.is_date = 0;
+ t.zone = nullptr;
+- t.is_utc = 0;
+ return t;
+ }
+
+@@ -888,7 +887,7 @@
+ }
+ case ICAL_LASTMODIFIED_PROPERTY: {
+ const icaltimetype t = icalproperty_get_lastmodified(p);
+- if (t.is_utc) {
++ if (icaltime_is_utc(t)) {
+ data->d->lastModified = toQDateTime(t);
+ } else {
+ qCDebug(KCALCORE_LOG) << "LAST-MODIFIED not UTC";
+@@ -1261,7 +1260,7 @@ bool ICalTimeZoneParser::parsePhase(icalcomponent *c, ICalTimeZonePhase &phase)
+ // Convert DTSTART to QDateTime, and from local time to UTC
+ const QDateTime localStart = toQDateTime(dtstart); // local time
+ dtstart.second -= prevOffset;
+- dtstart.is_utc = 1;
++ dtstart = icaltime_convert_to_zone(dtstart, icaltimezone_get_utc_timezone());
+ const QDateTime utcStart = toQDateTime(icaltime_normalize(dtstart)); // UTC
+
+ transitions += utcStart;
+@@ -1286,13 +1285,12 @@ bool ICalTimeZoneParser::parsePhase(icalcomponent *c, ICalTimeZonePhase &phase)
+ t.minute = dtstart.minute;
+ t.second = dtstart.second;
+ t.is_date = 0;
+- t.is_utc = 0; // dtstart is in local time
+ }
+ // RFC2445 states that RDATE must be in local time,
+ // but we support UTC as well to be safe.
+- if (!t.is_utc) {
++ if (!icaltime_is_utc(t)) {
+ t.second -= prevOffset; // convert to UTC
+- t.is_utc = 1;
++ t = icaltime_convert_to_zone(t, icaltimezone_get_utc_timezone());
+ t = icaltime_normalize(t);
+ }
+ transitions += toQDateTime(t);
+--
+cgit v0.11.2
+
diff --git a/kde/patch/kde-gtk-config.patch b/kde/patch/kde-gtk-config.patch
new file mode 100644
index 0000000..a62e649
--- /dev/null
+++ b/kde/patch/kde-gtk-config.patch
@@ -0,0 +1,4 @@
+# Make the kde-gtk-config load the current config first:
+# Fixed in 5.11.2
+#cat $CWD/patch/kde-gtk-config/kde-gtk-config_loadcurrentsettings.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kde-gtk-config/kde-gtk-config_loadcurrentsettings.patch b/kde/patch/kde-gtk-config/kde-gtk-config_loadcurrentsettings.patch
new file mode 100644
index 0000000..b5b074d
--- /dev/null
+++ b/kde/patch/kde-gtk-config/kde-gtk-config_loadcurrentsettings.patch
@@ -0,0 +1,622 @@
+From 0d0f812a1704c62c014bc87162b1280224b64f93 Mon Sep 17 00:00:00 2001
+From: Fabian Vogt <fabian@ritter-vogt.de>
+Date: Tue, 24 Oct 2017 13:25:32 +0200
+Subject: Revert "Make the kde-gtk-config kcm better at checking global gtk
+ settings"
+
+Summary:
+This reverts commit 34357f74ee2d98128ff423b0ec6ddcbf4232c475.
+
+Reverting this fixes loading of the actually used GTK settings.
+
+BUG: 382291
+
+Test Plan:
+Opened kcmshell5 kde-gtk-config with and without this revert.
+Without, it shows Adwaita as theme, with it shows breeze.
+GTK uses breeze, so the behaviour with the revert is correct.
+
+Reviewers: #plasma, apol
+
+Subscribers: plasma-devel
+
+Tags: #plasma
+
+Differential Revision: https://phabricator.kde.org/D8443
+---
+ CMakeLists.txt | 2 +-
+ src/abstractappearance.h | 5 +-
+ src/appearancegtk2.cpp | 103 +++++++++++++---------------------
+ src/appearancegtk2.h | 11 +---
+ src/appearancegtk3.cpp | 143 +++++++++++++++++++++--------------------------
+ src/appearancegtk3.h | 10 +---
+ src/appearencegtk.cpp | 4 +-
+ tests/CMakeLists.txt | 2 +-
+ tests/configsavetest.cpp | 75 ++++++++++---------------
+ tests/configsavetest.h | 8 +--
+ 10 files changed, 144 insertions(+), 219 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 181cfc9..bf1ba29 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -51,7 +51,7 @@ ki18n_wrap_ui(kcm_SRCS
+ )
+ add_library(kcm_kdegtkconfig MODULE ${kcm_SRCS})
+ target_compile_definitions(kcm_kdegtkconfig PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
+-target_link_libraries(kcm_kdegtkconfig ${X11_Xcursor_LIB} KF5::ConfigCore KF5::I18n KF5::KIOWidgets KF5::NewStuff KF5::Archive KF5::NewStuff KF5::ConfigWidgets KF5::IconThemes)
++target_link_libraries(kcm_kdegtkconfig ${X11_Xcursor_LIB} KF5::I18n KF5::KIOWidgets KF5::NewStuff KF5::Archive KF5::NewStuff KF5::ConfigWidgets KF5::IconThemes)
+
+ kcoreaddons_desktop_to_json(kcm_kdegtkconfig kde-gtk-config.desktop)
+
+diff --git a/src/abstractappearance.h b/src/abstractappearance.h
+index 208342e..2961a09 100644
+--- a/src/abstractappearance.h
++++ b/src/abstractappearance.h
+@@ -30,11 +30,10 @@ class AbstractAppearance
+ {
+ public:
+ virtual ~AbstractAppearance() {}
+- virtual bool loadSettings() = 0;
+- virtual bool saveSettings() const = 0;
++ virtual QString defaultConfigFile() const = 0;
+ virtual bool loadSettings(const QString& path) = 0;
+ virtual bool saveSettings(const QString& path) const = 0;
+-
++
+ /** @returns the installed themes' paths*/
+ virtual QStringList installedThemes() const = 0;
+
+diff --git a/src/appearancegtk2.cpp b/src/appearancegtk2.cpp
+index 92cbee3..44a2239 100644
+--- a/src/appearancegtk2.cpp
++++ b/src/appearancegtk2.cpp
+@@ -30,38 +30,48 @@
+ #include <QStandardPaths>
+ #include <config.h>
+
+-bool AppearanceGTK2::loadSettingsPrivate(const QString& path)
++bool AppearanceGTK2::loadSettings(const QString& path)
+ {
+ QFile configFile(path);
+
+- if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text))
+- return false;
++ bool canRead = configFile.open(QIODevice::ReadOnly | QIODevice::Text);
+
+- const QMap<QString, QString> foundSettings = readSettingsTuples(&configFile);
+-
+- for(auto it = foundSettings.constBegin(), itEnd = foundSettings.constEnd(); it!=itEnd; ++it) {
+- if (it.key() == "gtk-theme-name")
+- m_settings["theme"] = *it;
+- else if (it.key() == "gtk-icon-theme-name")
+- m_settings["icon"] = *it;
+- else if (it.key() == "gtk-fallback-icon-theme")
+- m_settings["icon_fallback"] = *it;
+- else if (it.key() == "gtk-cursor-theme-name")
+- m_settings["cursor"] = *it;
+- else if (it.key() == "gtk-font-name")
+- m_settings["font"] = *it;
+- else if (it.key() == "gtk-toolbar-style")
+- m_settings["toolbar_style"] = *it;
+- else if (it.key() == "gtk-button-images")
+- m_settings["show_icons_buttons"] = *it;
+- else if(it.key() == "gtk-menu-images")
+- m_settings["show_icons_menus"] = *it;
+- else if (it.key() == "gtk-primary-button-warps-slider")
+- m_settings["primary_button_warps_slider"] = *it;
+- else
+- qWarning() << "unknown field" << it.key();
++ if(canRead) {
++// qDebug() << "The gtk2 config file exists...";
++ const QMap<QString, QString> foundSettings = readSettingsTuples(&configFile);
++ m_settings = QMap<QString, QString> {
++ {"toolbar_style", "GTK_TOOLBAR_ICONS"},
++ {"show_icons_buttons", "0"},
++ {"show_icons_menus", "0"},
++ {"primary_button_warps_slider", "false"}
++ };
++
++ for(auto it = foundSettings.constBegin(), itEnd = foundSettings.constEnd(); it!=itEnd; ++it) {
++ if (it.key() == "gtk-theme-name")
++ m_settings["theme"] = *it;
++ else if (it.key() == "gtk-icon-theme-name")
++ m_settings["icon"] = *it;
++ else if (it.key() == "gtk-fallback-icon-theme")
++ m_settings["icon_fallback"] = *it;
++ else if (it.key() == "gtk-cursor-theme-name")
++ m_settings["cursor"] = *it;
++ else if (it.key() == "gtk-font-name")
++ m_settings["font"] = *it;
++ else if (it.key() == "gtk-toolbar-style")
++ m_settings["toolbar_style"] = *it;
++ else if (it.key() == "gtk-button-images")
++ m_settings["show_icons_buttons"] = *it;
++ else if(it.key() == "gtk-menu-images")
++ m_settings["show_icons_menus"] = *it;
++ else if (it.key() == "gtk-primary-button-warps-slider")
++ m_settings["primary_button_warps_slider"] = *it;
++ else
++ qWarning() << "unknown field" << it.key();
++ }
++
+ }
+- return true;
++
++ return canRead;
+ }
+
+ QString AppearanceGTK2::themesGtkrcFile(const QString& themeName) const
+@@ -82,7 +92,7 @@ QString AppearanceGTK2::themesGtkrcFile(const QString& themeName) const
+ return QString();
+ }
+
+-bool AppearanceGTK2::saveSettingsPrivate(const QString& gtkrcFile) const
++bool AppearanceGTK2::saveSettings(const QString& gtkrcFile) const
+ {
+ QFile gtkrc(gtkrcFile);
+ gtkrc.remove();
+@@ -107,14 +117,14 @@ bool AppearanceGTK2::saveSettingsPrivate(const QString& gtkrcFile) const
+ flow << "include \"/etc/gtk-2.0/gtkrc\"\n"; //We include the /etc's config file
+
+ int nameEnd = m_settings["font"].lastIndexOf(QRegExp(" ([0-9]+|bold|italic)"));
+- const auto fontFamily = m_settings["font"].leftRef(nameEnd);
++ QString fontFamily = m_settings["font"].left(nameEnd);
+
+ //TODO: is this really needed?
+ flow << "style \"user-font\" \n"
+ << "{\n"
+ << "\tfont_name=\""<< fontFamily << "\"\n"
+ << "}\n";
+-
++
+ flow << "widget_class \"*\" style \"user-font\"\n";
+ flow << "gtk-font-name=\"" << m_settings["font"] << "\"\n";
+ flow << "gtk-theme-name=\"" << m_settings["theme"] << "\"\n";
+@@ -144,16 +154,6 @@ bool AppearanceGTK2::saveSettingsPrivate(const QString& gtkrcFile) const
+ return true;
+ }
+
+-void AppearanceGTK2::reset()
+-{
+- m_settings = QMap<QString, QString> {
+- {"toolbar_style", "GTK_TOOLBAR_ICONS"},
+- {"show_icons_buttons", "0"},
+- {"show_icons_menus", "0"},
+- {"primary_button_warps_slider", "false"}
+- };
+-}
+-
+ QString AppearanceGTK2::defaultConfigFile() const
+ {
+ return QDir::homePath()+"/.gtkrc-2.0";
+@@ -183,26 +183,3 @@ QStringList AppearanceGTK2::installedThemes() const
+
+ return paths;
+ }
+-
+-bool AppearanceGTK2::loadSettings()
+-{
+- reset();
+-
+- return loadSettingsPrivate("/etc/gtk-2.0/gtkrc") && loadSettingsPrivate(defaultConfigFile());
+-}
+-
+-bool AppearanceGTK2::saveSettings() const
+-{
+- return saveSettings(defaultConfigFile());
+-}
+-
+-bool AppearanceGTK2::loadSettings(const QString& gtkrcFile)
+-{
+- reset();
+- return loadSettingsPrivate(gtkrcFile);
+-}
+-
+-bool AppearanceGTK2::saveSettings(const QString& gtkrcFile) const
+-{
+- return saveSettingsPrivate(gtkrcFile);
+-}
+diff --git a/src/appearancegtk2.h b/src/appearancegtk2.h
+index 7df49bf..8bc28ee 100644
+--- a/src/appearancegtk2.h
++++ b/src/appearancegtk2.h
+@@ -29,17 +29,10 @@ class AppearanceGTK2 : public AbstractAppearance
+ {
+ bool loadSettings(const QString& path) override;
+ bool saveSettings(const QString& path) const override;
+- bool loadSettings() override;
+- bool saveSettings() const override;
++ QString defaultConfigFile() const override;
+ QStringList installedThemes() const override;
+-
++
+ QString themesGtkrcFile(const QString& themeName) const;
+-
+-private:
+- void reset();
+- QString defaultConfigFile() const;
+- bool loadSettingsPrivate(const QString& path);
+- bool saveSettingsPrivate(const QString& path) const;
+ };
+
+ #endif // APPEARANCEGTK2_H
+diff --git a/src/appearancegtk3.cpp b/src/appearancegtk3.cpp
+index 7df48c3..fa1bde5 100644
+--- a/src/appearancegtk3.cpp
++++ b/src/appearancegtk3.cpp
+@@ -25,8 +25,6 @@
+ #include <QDir>
+ #include <QDebug>
+ #include <QStandardPaths>
+-#include <KSharedConfig>
+-#include <KConfigGroup>
+
+ QStringList AppearanceGTK3::installedThemes() const
+ {
+@@ -53,65 +51,76 @@ QStringList AppearanceGTK3::installedThemes() const
+ return themes;
+ }
+
+-bool AppearanceGTK3::saveSettings(const KSharedConfig::Ptr& file) const
+-{
+- KConfigGroup group(file, "Settings");
+-
+- group.writeEntry("gtk-font-name", m_settings["font"]);
+- group.writeEntry("gtk-theme-name", m_settings["theme"]);
+- group.writeEntry("gtk-icon-theme-name", m_settings["icon"]);
+- group.writeEntry("gtk-fallback-icon-theme", m_settings["icon_fallback"]);
+- group.writeEntry("gtk-cursor-theme-name", m_settings["cursor"]);
+- group.writeEntry("gtk-toolbar-style", m_settings["toolbar_style"]);
+- group.writeEntry("gtk-menu-images", m_settings["show_icons_menus"]);
+- group.writeEntry("gtk-button-images", m_settings["show_icons_buttons"]);
+- group.writeEntry("gtk-primary-button-warps-slider", m_settings["primary_button_warps_slider"]);
+- group.writeEntry("gtk-application-prefer-dark-theme", m_settings["application_prefer_dark_theme"]);
+-
+- const bool sync = group.sync();
+- Q_ASSERT(sync);
+- return true;
+-}
+-
+-bool AppearanceGTK3::loadSettings(const KSharedConfig::Ptr& file)
++bool AppearanceGTK3::saveSettings(const QString& file) const
+ {
+- KConfigGroup group(file, "Settings");
+-
+- if (!file || !group.isValid()) {
+- qWarning() << "Cannot open the GTK3 config file" << file;
++ //Opening GTK3 config file $ENV{XDG_CONFIG_HOME}/gtk-3.0/m_settings.ini
++ QDir::home().mkpath(file.left(file.lastIndexOf('/'))); //we make sure the path exists
++ QFile file_gtk3(file);
++
++ if(!file_gtk3.open(QIODevice::WriteOnly | QIODevice::Text)) {
++ qWarning() << "Couldn't open GTK3 config file for writing at:" << file_gtk3.fileName();
+ return false;
+ }
++ QTextStream flow3(&file_gtk3);
++ flow3 << "[Settings]\n";
++ flow3 << "gtk-font-name=" << m_settings["font"] << "\n";
++ flow3 << "gtk-theme-name=" << m_settings["theme"] << "\n";
++ flow3 << "gtk-icon-theme-name="<< m_settings["icon"] << "\n";
++ flow3 << "gtk-fallback-icon-theme=" << m_settings["icon_fallback"] << "\n";
++ flow3 << "gtk-cursor-theme-name=" << m_settings["cursor"] << "\n";
++ flow3 << "gtk-toolbar-style=" << m_settings["toolbar_style"] << "\n";
++ flow3 << "gtk-menu-images=" << m_settings["show_icons_menus"] << "\n";
++ flow3 << "gtk-button-images=" << m_settings["show_icons_buttons"] << "\n";
++ flow3 << "gtk-primary-button-warps-slider=" << m_settings["primary_button_warps_slider"] << "\n";
++ flow3 << "gtk-application-prefer-dark-theme=" << m_settings["application_prefer_dark_theme"] << "\n";
+
+- m_settings = QMap<QString, QString> {
+- {"toolbar_style", "GTK_TOOLBAR_ICONS"},
+- {"show_icons_buttons", "0"},
+- {"show_icons_menus", "0"},
+- {"primary_button_warps_slider", "false"},
+- {"application_prefer_dark_theme", "false"}
+- };
+-
+- m_settings["theme"] = group.readEntry("gtk-theme-name");
+- m_settings["icon"] = group.readEntry("gtk-icon-theme-name");
+- m_settings["icon_fallback"] = group.readEntry("gtk-fallback-icon-theme");
+- m_settings["cursor"] = group.readEntry("gtk-cursor-theme-name");
+- m_settings["font"] = group.readEntry("gtk-font-name");
+- m_settings["toolbar_style"] = group.readEntry("gtk-toolbar-style");
+- m_settings["show_icons_buttons"] = group.readEntry("gtk-button-images");
+- m_settings["show_icons_menus"] = group.readEntry("gtk-menu-images");
+- m_settings["primary_button_warps_slider"] = group.readEntry("gtk-primary-button-warps-slider");
+- m_settings["application_prefer_dark_theme"] = group.readEntry("gtk-application-prefer-dark-theme");
+- for(auto it = m_settings.begin(); it != m_settings.end(); ) {
+- if (it.value().isEmpty())
+- it = m_settings.erase(it);
+- else
+- ++it;
+- }
+ return true;
+ }
+
+-QString AppearanceGTK3::configFileName() const
++bool AppearanceGTK3::loadSettings(const QString& path)
+ {
+- return QStringLiteral("gtk-3.0/settings.ini");
++ QFile fileGtk3(path);
++ bool canRead=fileGtk3.open(QIODevice::ReadOnly | QIODevice::Text);
++
++ if(canRead) {
++ const QMap<QString, QString> foundSettings = readSettingsTuples(&fileGtk3);
++
++ m_settings = QMap<QString, QString> {
++ {"toolbar_style", "GTK_TOOLBAR_ICONS"},
++ {"show_icons_buttons", "0"},
++ {"show_icons_menus", "0"},
++ {"primary_button_warps_slider", "false"},
++ {"application_prefer_dark_theme", "false"}
++ };
++
++ for(auto it = foundSettings.constBegin(), itEnd = foundSettings.constEnd(); it!=itEnd; ++it) {
++ if (it.key() == "gtk-theme-name")
++ m_settings["theme"] = *it;
++ else if (it.key() == "gtk-icon-theme-name")
++ m_settings["icon"] = *it;
++ else if (it.key() == "gtk-fallback-icon-theme")
++ m_settings["icon_fallback"] = *it;
++ else if (it.key() == "gtk-cursor-theme-name")
++ m_settings["cursor"] = *it;
++ else if (it.key() == "gtk-font-name")
++ m_settings["font"] = *it;
++ else if (it.key() == "gtk-toolbar-style")
++ m_settings["toolbar_style"] = *it;
++ else if (it.key() == "gtk-button-images")
++ m_settings["show_icons_buttons"] = *it;
++ else if (it.key() == "gtk-menu-images")
++ m_settings["show_icons_menus"] = *it;
++ else if (it.key() == "gtk-primary-button-warps-slider")
++ m_settings["primary_button_warps_slider"] = *it;
++ else if (it.key() == "gtk-application-prefer-dark-theme")
++ m_settings["application_prefer_dark_theme"] = *it;
++ else
++ qWarning() << "unknown field" << it.key();
++ }
++ } else
++ qWarning() << "Cannot open the GTK3 config file" << path;
++
++ return canRead;
+ }
+
+ QString AppearanceGTK3::defaultConfigFile() const
+@@ -120,7 +129,7 @@ QString AppearanceGTK3::defaultConfigFile() const
+ if(root.isEmpty())
+ root = QFileInfo(QDir::home(), ".config").absoluteFilePath();
+
+- return root + '/' + configFileName();
++ return root+"/gtk-3.0/settings.ini";
+ }
+
+ bool AppearanceGTK3::getApplicationPreferDarkTheme() const
+@@ -132,29 +141,3 @@ void AppearanceGTK3::setApplicationPreferDarkTheme(const bool& enable)
+ {
+ m_settings["application_prefer_dark_theme"] = enable ? "true" : "false";
+ }
+-
+-bool AppearanceGTK3::saveSettings(const QString& file) const
+-{
+- auto cfg = KSharedConfig::openConfig(file);
+- return saveSettings(cfg);
+-}
+-
+-bool AppearanceGTK3::loadSettings(const QString& path)
+-{
+- auto cfg = KSharedConfig::openConfig(path);
+- return loadSettings(cfg);
+-}
+-
+-bool AppearanceGTK3::loadSettings()
+-{
+- auto cfg = KSharedConfig::openConfig(configFileName());
+- cfg->setReadDefaults(true);
+- return loadSettings(cfg);
+-}
+-
+-bool AppearanceGTK3::saveSettings() const
+-{
+- auto cfg = KSharedConfig::openConfig(configFileName());
+- cfg->setReadDefaults(true);
+- return saveSettings(cfg);
+-}
+diff --git a/src/appearancegtk3.h b/src/appearancegtk3.h
+index 3ce5a05..d4562b1 100644
+--- a/src/appearancegtk3.h
++++ b/src/appearancegtk3.h
+@@ -23,7 +23,6 @@
+ #ifndef APPEARANCEGTK3_H
+ #define APPEARANCEGTK3_H
+
+-#include <KSharedConfig>
+ #include "abstractappearance.h"
+
+ class AppearanceGTK3 : public AbstractAppearance
+@@ -31,18 +30,11 @@ class AppearanceGTK3 : public AbstractAppearance
+
+ public:
+ QStringList installedThemes() const override;
+- bool saveSettings() const override;
+- bool loadSettings() override;
+ bool saveSettings(const QString& file) const override;
+ bool loadSettings(const QString& path) override;
++ QString defaultConfigFile() const override;
+ bool getApplicationPreferDarkTheme() const;
+ void setApplicationPreferDarkTheme(const bool& enable);
+-
+-private:
+- QString defaultConfigFile() const;
+- QString configFileName() const;
+- bool saveSettings(const KSharedConfig::Ptr& file) const;
+- bool loadSettings(const KSharedConfig::Ptr& file);
+ };
+
+ #endif // APPEARANCEGTK3_H
+diff --git a/src/appearencegtk.cpp b/src/appearencegtk.cpp
+index 95a6604..2e26a5a 100644
+--- a/src/appearencegtk.cpp
++++ b/src/appearencegtk.cpp
+@@ -64,7 +64,7 @@ bool AppearenceGTK::loadFileConfig()
+ {
+ bool correct = false;
+ foreach(AbstractAppearance* app, m_app) {
+- bool c = app->loadSettings();
++ bool c = app->loadSettings(app->defaultConfigFile());
+ correct = correct || c;
+ }
+ // qDebug() << "loading..." << correct;
+@@ -75,7 +75,7 @@ bool AppearenceGTK::saveFileConfig()
+ {
+ bool correct = true;
+ foreach(AbstractAppearance* app, m_app) {
+- bool c = app->saveSettings();
++ bool c = app->saveSettings(app->defaultConfigFile());
+ correct = correct && c;
+ }
+ // qDebug() << "saving..." << correct;
+diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
+index 151725d..05bf8f1 100644
+--- a/tests/CMakeLists.txt
++++ b/tests/CMakeLists.txt
+@@ -2,7 +2,7 @@ macro(add_kgc_test name)
+ add_executable(${name} ${name}.cpp ${ARGV})
+ add_test(${name} ${name})
+ ecm_mark_as_test(${name})
+- target_link_libraries(${name} Qt5::Core Qt5::Gui Qt5::Test KF5::ConfigCore)
++ target_link_libraries(${name} Qt5::Core Qt5::Gui Qt5::Test)
+ target_include_directories(${name} PRIVATE ${CMAKE_BINARY_DIR})
+ endmacro(add_kgc_test)
+
+diff --git a/tests/configsavetest.cpp b/tests/configsavetest.cpp
+index 1fe8f4f..d5d8460 100644
+--- a/tests/configsavetest.cpp
++++ b/tests/configsavetest.cpp
+@@ -9,40 +9,33 @@
+
+ QTEST_GUILESS_MAIN(ConfigSaveTest);
+
+-ConfigSaveTest::ConfigSaveTest()
+-{
+- QStandardPaths::setTestModeEnabled(true);
+-}
+-
+-static void fillValues(QScopedPointer<AbstractAppearance>& a)
++void ConfigSaveTest::fillValues(AbstractAppearance* a)
+ {
+ a->setFont("a");
+ a->setIcon("a");
+ a->setTheme("a");
+ a->setToolbarStyle("a");
+ a->setIconFallback("a");
+- a->setCursor("a");
+ a->setShowIconsInButtons(true);
+ a->setShowIconsInMenus(true);
+ a->setPrimaryButtonWarpsSlider(true);
+
+- auto a3 = dynamic_cast<AppearanceGTK3*>(a.data());
++ auto a3 = dynamic_cast<AppearanceGTK3*>(a);
+ if (a3) {
+ a3->setApplicationPreferDarkTheme(false);
+ }
+ }
+
+-void compareAppearances(QScopedPointer<AbstractAppearance>& reloaded, QScopedPointer<AbstractAppearance>& instance)
++bool compareAppearances(AbstractAppearance* a, AbstractAppearance* b)
+ {
+- QCOMPARE(reloaded->getFont(), instance->getFont());
+- QCOMPARE(reloaded->getIcon(), instance->getIcon());
+- QCOMPARE(reloaded->getTheme(), instance->getTheme());
+- QCOMPARE(reloaded->getCursor(), instance->getCursor());
+- QCOMPARE(reloaded->getToolbarStyle(), instance->getToolbarStyle());
+- QCOMPARE(reloaded->getIconFallback(), instance->getIconFallback());
+- QCOMPARE(reloaded->getShowIconsInButtons(), instance->getShowIconsInButtons());
+- QCOMPARE(reloaded->getShowIconsInMenus(), instance->getShowIconsInMenus());
+- QCOMPARE(reloaded->getPrimaryButtonWarpsSlider(), instance->getPrimaryButtonWarpsSlider());
++ return a->getFont() == b->getFont()
++ && a->getIcon() == b->getIcon()
++ && a->getTheme() == b->getTheme()
++ && a->getToolbarStyle() == b->getToolbarStyle()
++ && a->getIconFallback() == b->getIconFallback()
++ && a->getShowIconsInButtons() == b->getShowIconsInButtons()
++ && a->getShowIconsInMenus() == b->getShowIconsInMenus()
++ && a->getPrimaryButtonWarpsSlider() == b->getPrimaryButtonWarpsSlider();
+ }
+
+ QByteArray readFile(const QString& path)
+@@ -53,35 +46,23 @@ QByteArray readFile(const QString& path)
+ return f.readAll();
+ }
+
+-void ConfigSaveTest::testGtk2()
++void ConfigSaveTest::testOpen()
+ {
+- const QString pathA = QDir::current().absoluteFilePath("test-gtk2")
+- , pathB = QDir::current().absoluteFilePath("testB-gtk2");
+-
+- QScopedPointer<AbstractAppearance> instance(new AppearanceGTK2);
+- fillValues(instance);
+- QVERIFY(instance->saveSettings(pathA));
+-
+- QScopedPointer<AbstractAppearance> reloaded(new AppearanceGTK2);
+- QVERIFY(reloaded->loadSettings(pathA));
+- compareAppearances(reloaded, instance);
+- QVERIFY(reloaded->saveSettings(pathB));
+- QCOMPARE(readFile(pathA), readFile(pathB));
+-}
+-
+-void ConfigSaveTest::testGtk3()
+-{
+- QScopedPointer<AbstractAppearance> instance(new AppearanceGTK3);
+- fillValues(instance);
+- const QString pathA = QDir::current().absoluteFilePath("test-gtk3")
+- , pathB = QDir::current().absoluteFilePath("testB-gtk3");
+- QVERIFY(instance->saveSettings(pathA));
+-
+- QScopedPointer<AbstractAppearance> reloaded(new AppearanceGTK3);
+- QVERIFY(QFile::exists(pathA));
+- QVERIFY(reloaded->loadSettings(pathA));
+- compareAppearances(reloaded, instance);
+- QVERIFY(reloaded->saveSettings(pathB));
++ QVector<AbstractAppearance*> instances;
++ instances << new AppearanceGTK2 << new AppearanceGTK3;
++ fillValues(instances[0]);
++ fillValues(instances[1]);
++ QVERIFY(instances[0]->saveSettings("test-gtk2"));
++ QVERIFY(instances[1]->saveSettings("test-gtk3"));
+
+- QCOMPARE(readFile(pathA), readFile(pathB));
++ QVector<AbstractAppearance*> reloaded;
++ reloaded << new AppearanceGTK2 << new AppearanceGTK3;
++ QVERIFY(reloaded[0]->loadSettings("test-gtk2"));
++ QVERIFY(reloaded[1]->loadSettings("test-gtk3"));
++ QVERIFY(compareAppearances(reloaded[0], instances[0]));
++ QVERIFY(compareAppearances(reloaded[1], instances[1]));
++ QVERIFY(reloaded[0]->saveSettings("testB-gtk2"));
++ QVERIFY(reloaded[1]->saveSettings("testB-gtk3"));
++ QCOMPARE(readFile("test-gtk2"), readFile("testB-gtk2"));
++ QCOMPARE(readFile("test-gtk3"), readFile("testB-gtk3"));
+ }
+diff --git a/tests/configsavetest.h b/tests/configsavetest.h
+index 342b408..39fb4c2 100644
+--- a/tests/configsavetest.h
++++ b/tests/configsavetest.h
+@@ -11,11 +11,11 @@ class AbstractAppearance;
+ class ConfigSaveTest : public QObject
+ {
+ Q_OBJECT
+-public:
+- ConfigSaveTest();
+ private slots:
+- void testGtk2();
+- void testGtk3();
++ void testOpen();
++
++private:
++ void fillValues(AbstractAppearance* a);
+ };
+
+ #endif // CONFIGSAVETEST_H
+--
+cgit v0.11.2
+
diff --git a/kde/patch/kde-runtime.patch b/kde/patch/kde-runtime.patch
index d95ef54..c3b6101 100644
--- a/kde/patch/kde-runtime.patch
+++ b/kde/patch/kde-runtime.patch
@@ -5,3 +5,6 @@
# Fix compilation against NetworkManager 1.0.6:
cat $CWD/patch/kde-runtime/kde-runtime_networkmanager.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+## Fix compilation against gpgme 1.7+:
+#cat $CWD/patch/kde-runtime/kde-runtime_gpgme.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kde-runtime/kde-runtime_gpgme.patch b/kde/patch/kde-runtime/kde-runtime_gpgme.patch
new file mode 100644
index 0000000..b1703ed
--- /dev/null
+++ b/kde/patch/kde-runtime/kde-runtime_gpgme.patch
@@ -0,0 +1,133 @@
+commit 1b80d1d0b961f8e28186928ede2b87af292c3de4
+Author: Antonio Rojas <arojas@archlinux.org>
+Date: Thu Nov 10 16:58:10 2016 +0100
+
+ Allow building kwalletd against gpgme++ from gpgme 1.7
+
+ REVIEW: 129339
+
+diff --git a/kwalletd/CMakeLists.txt b/kwalletd/CMakeLists.txt
+index 73aec82..ae8c745 100644
+--- a/kwalletd/CMakeLists.txt
++++ b/kwalletd/CMakeLists.txt
+@@ -5,11 +5,18 @@ find_package(Gpgme) # Called by FindQGpgme, but since we call some gpgme
+ # functions ourselves we need to link against it directly.
+ find_package(QGpgme) # provided by kdepimlibs
+
+-if (GPGME_FOUND AND QGPGME_FOUND)
++if (NOT QGPGME_FOUND)
++find_package(Gpgmepp) # provided by gpgme 1.7
++endif (NOT QGPGME_FOUND)
++
++if ((GPGME_FOUND AND QGPGME_FOUND) OR Gpgmepp_FOUND)
+ add_definitions(-DHAVE_QGPGME)
+- include_directories(${GPGME_INCLUDES} ${QGPGME_INCLUDE_DIR})
++ include_directories(${GPGME_INCLUDES})
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
+-endif(GPGME_FOUND AND QGPGME_FOUND)
++endif((GPGME_FOUND AND QGPGME_FOUND) OR Gpgmepp_FOUND)
++if (GPGME_FOUND AND QGPGME_FOUND)
++ include_directories(${QGPGME_INCLUDE_DIR})
++endif (GPGME_FOUND AND QGPGME_FOUND)
+
+ ########### build backends #########
+ add_subdirectory(backend)
+@@ -37,7 +44,7 @@ kde4_add_ui_files(kwalletd_KDEINIT_SRCS
+ kwalletwizardpagepassword.ui
+ )
+
+-if (GPGME_FOUND AND QGPGME_FOUND)
++if ((GPGME_FOUND AND QGPGME_FOUND) OR Gpgmepp_FOUND)
+ set(kwalletd_KDEINIT_SRCS
+ ${kwalletd_KDEINIT_SRCS}
+ knewwalletdialog.cpp
+@@ -48,7 +55,7 @@ if (GPGME_FOUND AND QGPGME_FOUND)
+ knewwalletdialogintro.ui
+ knewwalletdialoggpg.ui
+ )
+-endif(GPGME_FOUND AND QGPGME_FOUND)
++endif((GPGME_FOUND AND QGPGME_FOUND) OR Gpgmepp_FOUND)
+
+ find_file(kwallet_xml org.kde.KWallet.xml HINTS ${KDE4_DBUS_INTERFACES_DIR} )
+
+@@ -57,8 +64,12 @@ qt4_add_dbus_adaptor( kwalletd_KDEINIT_SRCS ${kwallet_xml} kwalletd.h KWalletD )
+ kde4_add_kdeinit_executable( kwalletd NOGUI ${kwalletd_KDEINIT_SRCS} )
+
+ target_link_libraries(kdeinit_kwalletd ${KDE4_KDEUI_LIBS} kwalletbackend )
+-if (GPGME_FOUND AND QGPGME_FOUND)
+- target_link_libraries(kdeinit_kwalletd ${QGPGME_LIBRARIES} )
++if(GPGME_FOUND AND QGPGME_FOUND)
++target_link_libraries(kdeinit_kwalletd ${QGPGME_LIBRARIES} )
++else(GPGME_FOUND AND QGPGME_FOUND)
++if(Gpgmepp_FOUND)
++target_link_libraries(kdeinit_kwalletd Gpgmepp)
++endif(Gpgmepp_FOUND)
+ endif(GPGME_FOUND AND QGPGME_FOUND)
+
+ install(TARGETS kdeinit_kwalletd ${INSTALL_TARGETS_DEFAULT_ARGS})
+@@ -73,4 +84,4 @@ install( FILES kwalletd.notifyrc DESTINATION ${DATA_INSTALL_DIR}/kwalletd )
+ install( FILES kwallet-4.13.upd DESTINATION ${DATA_INSTALL_DIR}/kconf_update)
+
+ add_subdirectory(tests)
+-add_subdirectory(autotests)
+\ No newline at end of file
++add_subdirectory(autotests)
+diff --git a/kwalletd/backend/CMakeLists.txt b/kwalletd/backend/CMakeLists.txt
+index 4db348f..7347b12 100644
+--- a/kwalletd/backend/CMakeLists.txt
++++ b/kwalletd/backend/CMakeLists.txt
+@@ -22,6 +22,10 @@ kde4_add_library(kwalletbackend SHARED ${kwalletbackend_LIB_SRCS})
+ target_link_libraries(kwalletbackend ${KDE4_KDEUI_LIBS} ${LIBGCRYPT_LIBRARIES})
+ if(QGPGME_FOUND)
+ target_link_libraries(kwalletbackend ${QGPGME_LIBRARIES} )
++else(QGPGME_FOUND)
++if(Gpgmepp_FOUND)
++target_link_libraries(kwalletbackend Gpgmepp)
++endif(Gpgmepp_FOUND)
+ endif(QGPGME_FOUND)
+
+ # link with advapi32 on windows
+diff --git a/kwalletd/backend/backendpersisthandler.cpp b/kwalletd/backend/backendpersisthandler.cpp
+index b7f63f8..9608af0 100644
+--- a/kwalletd/backend/backendpersisthandler.cpp
++++ b/kwalletd/backend/backendpersisthandler.cpp
+@@ -33,6 +33,7 @@
+ #include <gpgme++/data.h>
+ #include <gpgme++/encryptionresult.h>
+ #include <gpgme++/decryptionresult.h>
++#include <boost/shared_ptr.hpp>
+ #endif
+ #include "backendpersisthandler.h"
+ #include "kwalletbackend.h"
+diff --git a/kwalletd/kwalletwizard.cpp b/kwalletd/kwalletwizard.cpp
+index 78de78d..821b666 100644
+--- a/kwalletd/kwalletwizard.cpp
++++ b/kwalletd/kwalletwizard.cpp
+@@ -40,6 +40,7 @@
+ #include <kdebug.h>
+ #include <kmessagebox.h>
+ #include <gpgme.h>
++#include <boost/shared_ptr.hpp>
+ #endif
+
+ class PageIntro : public QWizardPage
+commit cf28801cd34730da07a2c01704ca3114630f4fe7
+Author: Antonio Rojas <arojas@archlinux.org>
+Date: Thu Nov 10 18:54:41 2016 +0100
+
+ Compiling against gmgpe 1.7 requires c++11
+
+diff --git a/kwalletd/CMakeLists.txt b/kwalletd/CMakeLists.txt
+index ae8c745..88d944e 100644
+--- a/kwalletd/CMakeLists.txt
++++ b/kwalletd/CMakeLists.txt
+@@ -17,6 +17,9 @@ endif((GPGME_FOUND AND QGPGME_FOUND) OR Gpgmepp_FOUND)
+ if (GPGME_FOUND AND QGPGME_FOUND)
+ include_directories(${QGPGME_INCLUDE_DIR})
+ endif (GPGME_FOUND AND QGPGME_FOUND)
++if (Gpgmepp_FOUND)
++ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
++endif (Gpgmepp_FOUND)
+
+ ########### build backends #########
+ add_subdirectory(backend)
diff --git a/kde/patch/kdenlive.patch b/kde/patch/kdenlive.patch
new file mode 100644
index 0000000..9065c4d
--- /dev/null
+++ b/kde/patch/kdenlive.patch
@@ -0,0 +1,4 @@
+# Fix compilation with gcc7.
+# Should have been fixed in 17.04.2...
+#cat $CWD/patch/kdenlive/kdenlive_gcc7.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kdenlive/kdenlive_gcc7.patch b/kde/patch/kdenlive/kdenlive_gcc7.patch
new file mode 100644
index 0000000..a7ddb90
--- /dev/null
+++ b/kde/patch/kdenlive/kdenlive_gcc7.patch
@@ -0,0 +1,32 @@
+# Make kdenlive compile with gcc7
+
+--- kdenlive-17.04.1/src/profiles/tree/profiletreemodel.cpp.orig 2017-05-08 19:52:35.000000000 +0200
++++ kdenlive-17.04.1/src/profiles/tree/profiletreemodel.cpp 2017-05-19 08:09:04.986909338 +0200
+@@ -27,6 +27,7 @@
+ #include <QVector>
+ #include <array>
+ #include <KLocalizedString>
++#include <functional>
+
+
+ class ProfileItem
+--- kdenlive-17.04.1/src/scopes/audioscopes/spectrogram.cpp.orig 2017-05-08 19:52:35.000000000 +0200
++++ kdenlive-17.04.1/src/scopes/audioscopes/spectrogram.cpp 2017-05-19 08:09:04.993910503 +0200
+@@ -241,7 +241,7 @@
+ x = leftDist + (m_innerScopeRect.width() - 1) * ((float)hz) / m_freqMax;
+
+ // Hide text if it would overlap with the text drawn at the mouse position
+- hideText = m_aTrackMouse->isChecked() && m_mouseWithinWidget && abs(x - (leftDist + mouseX + 20)) < (int) minDistX + 16
++ hideText = m_aTrackMouse->isChecked() && m_mouseWithinWidget && abs(x - ((int)leftDist + mouseX + 20)) < (int) minDistX + 16
+ && mouseX < m_innerScopeRect.width() && mouseX >= 0;
+
+ if (x <= rightBorder) {
+@@ -268,7 +268,7 @@
+ }
+ // Draw the line at the very right (maximum frequency)
+ x = leftDist + m_innerScopeRect.width() - 1;
+- hideText = m_aTrackMouse->isChecked() && m_mouseWithinWidget && abs(x - (leftDist + mouseX + 30)) < (int) minDistX
++ hideText = m_aTrackMouse->isChecked() && m_mouseWithinWidget && abs(x - ((int)leftDist + mouseX + 30)) < (int) minDistX
+ && mouseX < m_innerScopeRect.width() && mouseX >= 0;
+ davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height() + 6);
+ if (!hideText) {
diff --git a/kde/patch/kdepimlibs4.patch b/kde/patch/kdepimlibs4.patch
new file mode 100644
index 0000000..91a7d15
--- /dev/null
+++ b/kde/patch/kdepimlibs4.patch
@@ -0,0 +1,3 @@
+# Fix a compilation issue with new libical:
+cat $CWD/patch/kdepimlibs4/kdepimlibs.libical3.diff | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kdepimlibs4/kdepimlibs.libical3.diff b/kde/patch/kdepimlibs4/kdepimlibs.libical3.diff
new file mode 100644
index 0000000..c3b8caf
--- /dev/null
+++ b/kde/patch/kdepimlibs4/kdepimlibs.libical3.diff
@@ -0,0 +1,184 @@
+--- ./kcalcore/icalformat_p.cpp.orig 2015-06-24 07:43:14.000000000 -0500
++++ ./kcalcore/icalformat_p.cpp 2017-12-05 23:03:44.742261940 -0600
+@@ -2301,7 +2301,6 @@
+ t.second = 0;
+
+ t.is_date = 1;
+- t.is_utc = 0;
+ t.zone = 0;
+
+ return t;
+@@ -2322,8 +2321,7 @@
+ t.minute = datetime.time().minute();
+ t.second = datetime.time().second();
+ }
+- t.zone = 0; // zone is NOT set
+- t.is_utc = datetime.isUtc() ? 1 : 0;
++ t.zone = datetime.isUtc() ? icaltimezone_get_utc_timezone() : 0; // zone is NOT set
+
+ // _dumpIcaltime( t );
+
+@@ -2398,7 +2396,7 @@
+ }
+
+ KTimeZone ktz;
+- if (!t.is_utc) {
++ if (!icaltime_is_utc( t )) {
+ ktz = dt.timeZone();
+ }
+
+@@ -2431,7 +2429,7 @@
+ // _dumpIcaltime( t );
+
+ KDateTime::Spec timeSpec;
+- if (t.is_utc || t.zone == icaltimezone_get_utc_timezone()) {
++ if (icaltime_is_utc( t ) || t.zone == icaltimezone_get_utc_timezone()) {
+ timeSpec = KDateTime::UTC; // the time zone is UTC
+ utc = false; // no need to convert to UTC
+ } else {
+--- ./kcalcore/icaltimezones.cpp.orig 2015-06-24 07:43:14.000000000 -0500
++++ ./kcalcore/icaltimezones.cpp 2017-12-05 23:03:55.482262829 -0600
+@@ -54,7 +54,7 @@
+ {
+ return QDateTime(QDate(t.year, t.month, t.day),
+ QTime(t.hour, t.minute, t.second),
+- (t.is_utc ? Qt::UTC : Qt::LocalTime));
++ (icaltime_is_utc( t ) ? Qt::UTC : Qt::LocalTime));
+ }
+
+ // Maximum date for time zone data.
+@@ -81,7 +81,6 @@
+ t.second = local.time().second();
+ t.is_date = 0;
+ t.zone = 0;
+- t.is_utc = 0;
+ return t;
+ }
+
+@@ -886,7 +885,7 @@
+ case ICAL_LASTMODIFIED_PROPERTY:
+ {
+ const icaltimetype t = icalproperty_get_lastmodified(p);
+- if (t.is_utc) {
++ if (icaltime_is_utc( t )) {
+ data->d->lastModified = toQDateTime(t);
+ } else {
+ kDebug() << "LAST-MODIFIED not UTC";
+@@ -1259,7 +1258,7 @@
+ // Convert DTSTART to QDateTime, and from local time to UTC
+ const QDateTime localStart = toQDateTime(dtstart); // local time
+ dtstart.second -= prevOffset;
+- dtstart.is_utc = 1;
++ dtstart.zone = icaltimezone_get_utc_timezone();
+ const QDateTime utcStart = toQDateTime(icaltime_normalize(dtstart)); // UTC
+
+ transitions += utcStart;
+@@ -1286,13 +1285,13 @@
+ t.minute = dtstart.minute;
+ t.second = dtstart.second;
+ t.is_date = 0;
+- t.is_utc = 0; // dtstart is in local time
++ t.zone = 0; // dtstart is in local time
+ }
+ // RFC2445 states that RDATE must be in local time,
+ // but we support UTC as well to be safe.
+- if (!t.is_utc) {
++ if (!icaltime_is_utc( t )) {
+ t.second -= prevOffset; // convert to UTC
+- t.is_utc = 1;
++ t.zone = icaltimezone_get_utc_timezone();
+ t = icaltime_normalize(t);
+ }
+ transitions += toQDateTime(t);
+--- ./kcal/icalformat_p.cpp.orig 2015-06-24 07:43:14.000000000 -0500
++++ ./kcal/icalformat_p.cpp 2017-12-05 23:04:01.670263342 -0600
+@@ -2087,7 +2087,6 @@
+ t.second = 0;
+
+ t.is_date = 1;
+- t.is_utc = 0;
+ t.zone = 0;
+
+ return t;
+@@ -2106,8 +2105,7 @@
+ t.second = datetime.time().second();
+
+ t.is_date = 0;
+- t.zone = 0; // zone is NOT set
+- t.is_utc = datetime.isUtc() ? 1 : 0;
++ t.zone = datetime.isUtc() ? icaltimezone_get_utc_timezone() : 0;
+
+ // _dumpIcaltime( t );
+
+@@ -2174,7 +2172,7 @@
+ }
+
+ KTimeZone ktz;
+- if ( !t.is_utc ) {
++ if ( !icaltime_is_utc( t ) ) {
+ ktz = dt.timeZone();
+ }
+
+@@ -2207,7 +2205,7 @@
+ // _dumpIcaltime( t );
+
+ KDateTime::Spec timeSpec;
+- if ( t.is_utc || t.zone == icaltimezone_get_utc_timezone() ) {
++ if ( icaltime_is_utc( t ) || t.zone == icaltimezone_get_utc_timezone() ) {
+ timeSpec = KDateTime::UTC; // the time zone is UTC
+ utc = false; // no need to convert to UTC
+ } else {
+--- ./kcal/icaltimezones.cpp.orig 2015-06-24 07:43:14.000000000 -0500
++++ ./kcal/icaltimezones.cpp 2017-12-05 23:04:07.385263815 -0600
+@@ -50,7 +50,7 @@
+ {
+ return QDateTime( QDate( t.year, t.month, t.day ),
+ QTime( t.hour, t.minute, t.second ),
+- ( t.is_utc ? Qt::UTC : Qt::LocalTime ) );
++ ( icaltime_is_utc( t ) ? Qt::UTC : Qt::LocalTime ) );
+ }
+
+ // Maximum date for time zone data.
+@@ -77,7 +77,6 @@
+ t.second = local.time().second();
+ t.is_date = 0;
+ t.zone = 0;
+- t.is_utc = 0;
+ return t;
+ }
+
+@@ -787,7 +786,7 @@
+ case ICAL_LASTMODIFIED_PROPERTY:
+ {
+ icaltimetype t = icalproperty_get_lastmodified(p);
+- if ( t.is_utc ) {
++ if ( icaltime_is_utc( t ) ) {
+ data->d->lastModified = toQDateTime( t );
+ } else {
+ kDebug() << "LAST-MODIFIED not UTC";
+@@ -972,7 +971,7 @@
+ // Convert DTSTART to QDateTime, and from local time to UTC
+ QDateTime localStart = toQDateTime( dtstart ); // local time
+ dtstart.second -= prevOffset;
+- dtstart.is_utc = 1;
++ dtstart.zone = icaltimezone_get_utc_timezone();
+ QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) ); // UTC
+
+ transitions += utcStart;
+@@ -999,13 +998,13 @@
+ t.minute = dtstart.minute;
+ t.second = dtstart.second;
+ t.is_date = 0;
+- t.is_utc = 0; // dtstart is in local time
++ t.zone = 0; // dtstart is in local time
+ }
+ // RFC2445 states that RDATE must be in local time,
+ // but we support UTC as well to be safe.
+- if ( !t.is_utc ) {
++ if ( !icaltime_is_utc( t ) ) {
+ t.second -= prevOffset; // convert to UTC
+- t.is_utc = 1;
++ t.zone = icaltimezone_get_utc_timezone();
+ t = icaltime_normalize( t );
+ }
+ transitions += toQDateTime( t );
diff --git a/kde/patch/kdesdk-kioslaves.patch b/kde/patch/kdesdk-kioslaves.patch
index dd4f9e2..da2f885 100644
--- a/kde/patch/kdesdk-kioslaves.patch
+++ b/kde/patch/kdesdk-kioslaves.patch
@@ -1,3 +1,3 @@
# Fix compilation against svn > 1.8.
-cat $CWD/patch/kdesdk-kioslaves/svn19.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+#cat $CWD/patch/kdesdk-kioslaves/svn19.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
diff --git a/kde/patch/kholidays.patch b/kde/patch/kholidays.patch
index f1ed4b5..ec8ad80 100644
--- a/kde/patch/kholidays.patch
+++ b/kde/patch/kholidays.patch
@@ -2,3 +2,7 @@
# Fixed post Plasma 5.5.5.
#cat $CWD/patch/kholidays/kholidays_isnan.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+# Revert depfreeze breaking merges (KF5_VERSION, cmake, PIM_VERSION).
+# Should be fixed in Applications > 16.12.3.
+#cat $CWD/patch/kholidays/kholidays_depfreeze_revert.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kholidays/kholidays_depfreeze_revert.patch b/kde/patch/kholidays/kholidays_depfreeze_revert.patch
new file mode 100644
index 0000000..316403a
--- /dev/null
+++ b/kde/patch/kholidays/kholidays_depfreeze_revert.patch
@@ -0,0 +1,61 @@
+From 506bd08a6faf61c776beecb05f1acbe04223827a Mon Sep 17 00:00:00 2001
+From: Andreas Sturmlechner <andreas.sturmlechner@gmail.com>
+Date: Fri, 10 Mar 2017 13:33:10 +0100
+Subject: Revert depfreeze breaking merges (KF5_VERSION, cmake, PIM_VERSION)
+
+---
+ CMakeLists.txt | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 05164db..e0b67b8 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -1,28 +1,28 @@
+-cmake_minimum_required(VERSION 3.0)
+-set(PIM_VERSION "5.4.40")
++cmake_minimum_required(VERSION 2.8.12)
+
+-project(KHolidays VERSION ${PIM_VERSION})
++project(KHolidays)
+
+ # ECM setup
+-set(KF5_VERSION "5.31.0")
++set(KF5_VERSION "5.28.0")
+ find_package(ECM ${KF5_VERSION} CONFIG REQUIRED)
+ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
+
+ include(GenerateExportHeader)
+ include(ECMGenerateHeaders)
+ include(ECMGeneratePriFile)
+-include(CMakePackageConfigHelpers)
++include(ECMPackageConfigHelpers)
+ include(ECMSetupVersion)
+ include(ECMPoQmTools)
+ include(FeatureSummary)
+ include(KDEInstallDirs)
+ include(KDECMakeSettings)
+ include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
+-include(ECMCoverageOption)
++
++set(PIM_VERSION "5.4.3")
+
+ set(KHOLIDAYS_LIB_VERSION ${PIM_VERSION})
+
+-ecm_setup_version(PROJECT VARIABLE_PREFIX KHOLIDAYS
++ecm_setup_version(${KHOLIDAYS_LIB_VERSION} VARIABLE_PREFIX KHOLIDAYS
+ VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kholidays_version.h"
+ PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5HolidaysConfigVersion.cmake"
+ SOVERSION 5
+@@ -50,7 +50,7 @@ endif()
+ ########### CMake Config Files ###########
+ set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Holidays")
+
+-configure_package_config_file(
++ecm_configure_package_config_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/KF5HolidaysConfig.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/KF5HolidaysConfig.cmake"
+ INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
+--
+cgit v0.11.2
+
diff --git a/kde/patch/kio.patch b/kde/patch/kio.patch
index 5d87cba..1caed00 100644
--- a/kde/patch/kio.patch
+++ b/kde/patch/kio.patch
@@ -7,3 +7,8 @@
# to kio (kf5 based) to make service menus visible in dolphin (kf5 based):
#cat $CWD/patch/kio/kio_dolphin_servicemenus.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+# Qt >= 5.9.3 breaks creation of folders in kio, which affects e.g.
+# Dolphin and Plasma Folder View.
+# Fixed in KIO 5.41:
+#cat $CWD/patch/kio/kio_fix_url_setpath.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kio/kio_fix_url_setpath.patch b/kde/patch/kio/kio_fix_url_setpath.patch
new file mode 100644
index 0000000..d9cf740
--- /dev/null
+++ b/kde/patch/kio/kio_fix_url_setpath.patch
@@ -0,0 +1,65 @@
+From 2353119aae8f03565bc7779ed1d597d266f5afda Mon Sep 17 00:00:00 2001
+From: Elvis Angelaccio <elvis.angelaccio@kde.org>
+Date: Thu, 16 Nov 2017 10:41:19 +0100
+Subject: Fix KIO::mkpath with qtbase 5.10 beta 4
+
+Summary:
+The latest Qt 5.10 beta includes [1] which breaks a bunch of unit tests,
+since `url.setPath("//foo")` will now result in an invalid (empty) QUrl.
+
+This patch fixes the KIO::mkpath() case.
+
+[1]: http://code.qt.io/cgit/qt/qtbase.git/commit/?id=f62768d046528636789f901ac79e2cfa1843a7b7
+
+Test Plan:
+
+* I can now create folders from dolphin and plasma.
+* fileundomanagertest and mkpathjobtest no longer fail
+
+Reviewers: #frameworks, dfaure
+
+Tags: #frameworks
+
+Differential Revision: https://phabricator.kde.org/D8836
+---
+ src/core/mkpathjob.cpp | 17 ++++++++++++++---
+ 1 file changed, 14 insertions(+), 3 deletions(-)
+
+diff --git a/src/core/mkpathjob.cpp b/src/core/mkpathjob.cpp
+index bff46ca..a177805 100644
+--- a/src/core/mkpathjob.cpp
++++ b/src/core/mkpathjob.cpp
+@@ -43,8 +43,13 @@ public:
+ m_url.setPath(QStringLiteral("/"));
+ int i = 0;
+ for (; i < basePathComponents.count() && i < m_pathComponents.count(); ++i) {
+- if (m_pathComponents.at(i) == basePathComponents.at(i)) {
+- m_url.setPath(m_url.path() + '/' + m_pathComponents.at(i));
++ const QString pathComponent = m_pathComponents.at(i);
++ if (pathComponent == basePathComponents.at(i)) {
++ if (m_url.path() == QLatin1Char('/')) {
++ m_url.setPath(m_url.path() + pathComponent);
++ } else {
++ m_url.setPath(m_url.path() + '/' + pathComponent);
++ }
+ } else {
+ break;
+ }
+@@ -57,7 +62,13 @@ public:
+ if (m_url.isLocalFile()) {
+ i = 0;
+ for (; i < m_pathComponents.count(); ++i) {
+- QString testDir = m_url.toLocalFile() + '/' + m_pathComponents.at(i);
++ const QString localFile = m_url.toLocalFile();
++ QString testDir;
++ if (localFile == QLatin1Char('/')) {
++ testDir = localFile + m_pathComponents.at(i);
++ } else {
++ testDir = localFile + '/' + m_pathComponents.at(i);
++ }
+ if (QFileInfo(testDir).isDir()) {
+ m_url.setPath(testDir);
+ } else {
+--
+cgit v0.11.2
+
diff --git a/kde/patch/konsole.patch b/kde/patch/konsole.patch
new file mode 100644
index 0000000..ce82120
--- /dev/null
+++ b/kde/patch/konsole.patch
@@ -0,0 +1,5 @@
+# Set TERM to 'konsole' instead of the default 'xterm-256color'
+# to prevent garbled text under certain conditions:
+# Reverted to default behaviour in 18.04.0 after Slackware did the same:
+#cat $CWD/patch/konsole/konsole.term.is.konsole.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/konsole/konsole.term.is.konsole.patch b/kde/patch/konsole/konsole.term.is.konsole.patch
new file mode 100644
index 0000000..443b9f1
--- /dev/null
+++ b/kde/patch/konsole/konsole.term.is.konsole.patch
@@ -0,0 +1,24 @@
+diff -uar konsole-17.12.3.orig/src/Profile.cpp konsole-17.12.3/src/Profile.cpp
+--- konsole-17.12.3.orig/src/Profile.cpp 2018-03-01 23:54:01.000000000 +0100
++++ konsole-17.12.3/src/Profile.cpp 2018-04-03 21:17:11.897873304 +0200
+@@ -157,7 +157,7 @@
+ // See Pty.cpp on why Arguments is populated
+ setProperty(Arguments, QStringList() << QString::fromUtf8(qgetenv("SHELL")));
+ setProperty(Icon, QStringLiteral("utilities-terminal"));
+- setProperty(Environment, QStringList() << QStringLiteral("TERM=xterm-256color") << QStringLiteral("COLORTERM=truecolor"));
++ setProperty(Environment, QStringList() << QStringLiteral("TERM=konsole") << QStringLiteral("COLORTERM=truecolor"));
+ setProperty(LocalTabTitleFormat, QStringLiteral("%d : %n"));
+ setProperty(RemoteTabTitleFormat, QStringLiteral("(%u) %H"));
+ setProperty(ShowTerminalSizeHint, true);
+diff -uar konsole-17.12.3.orig/src/Pty.cpp konsole-17.12.3/src/Pty.cpp
+--- konsole-17.12.3.orig/src/Pty.cpp 2018-03-01 23:54:01.000000000 +0100
++++ konsole-17.12.3/src/Pty.cpp 2018-04-03 21:18:18.898007801 +0200
+@@ -229,7 +229,7 @@
+
+ // extra safeguard to make sure $TERM is always set
+ if (!isTermEnvAdded) {
+- setEnv(QStringLiteral("TERM"), QStringLiteral("xterm-256color"));
++ setEnv(QStringLiteral("TERM"), QStringLiteral("konsole"));
+ }
+ }
+
diff --git a/kde/patch/kopete.patch b/kde/patch/kopete.patch
new file mode 100644
index 0000000..64e8238
--- /dev/null
+++ b/kde/patch/kopete.patch
@@ -0,0 +1,9 @@
+# Fix for jabber protocol vulnerability in Kopete: CVE-2017-5593
+# (User Impersonation Vulnerability)
+# Fixed in 16.12.3.
+# cat $CWD/patch/kopete/kopete_kdebug376348.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Make 18.04.0 compile.
+# Fixed in 18.04.1.
+#cat $CWD/patch/kopete/kopete_kdebug393372.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kopete/kopete_kdebug376348.patch b/kde/patch/kopete/kopete_kdebug376348.patch
new file mode 100644
index 0000000..d9bb057
--- /dev/null
+++ b/kde/patch/kopete/kopete_kdebug376348.patch
@@ -0,0 +1,127 @@
+From 6243764c4fd0985320d4a10b48051cc418d584ad Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali.rohar@gmail.com>
+Date: Sat, 11 Feb 2017 13:24:59 +0100
+Subject: Fix CVE 2017-5593 (User Impersonation Vulnerability) in jabber
+ protocol
+
+BUG: 376348
+FIXED-IN: 16.12.3
+---
+ .../jabber/libiris/patches/01_cve_2017-5593.patch | 52 ++++++++++++++++++++++
+ .../jabber/libiris/src/xmpp/xmpp-im/xmpp_tasks.cpp | 14 +++---
+ 2 files changed, 61 insertions(+), 5 deletions(-)
+ create mode 100644 protocols/jabber/libiris/patches/01_cve_2017-5593.patch
+
+diff --git a/protocols/jabber/libiris/patches/01_cve_2017-5593.patch b/protocols/jabber/libiris/patches/01_cve_2017-5593.patch
+new file mode 100644
+index 0000000..573ca66
+--- /dev/null
++++ b/protocols/jabber/libiris/patches/01_cve_2017-5593.patch
+@@ -0,0 +1,52 @@
++diff --git a/src/xmpp/xmpp-im/xmpp_tasks.cpp b/src/xmpp/xmpp-im/xmpp_tasks.cpp
++index 0e74b71..0837548 100644
++--- a/src/xmpp/xmpp-im/xmpp_tasks.cpp
+++++ b/src/xmpp/xmpp-im/xmpp_tasks.cpp
++@@ -888,14 +888,18 @@ bool JT_PushMessage::take(const QDomElement &e)
++ QDomElement forward;
++ Message::CarbonDir cd = Message::NoCarbon;
++
+++ Jid fromJid = Jid(e1.attribute(QLatin1String("from")));
++ // Check for Carbon
++ QDomNodeList list = e1.childNodes();
++ for (int i = 0; i < list.size(); ++i) {
++ QDomElement el = list.at(i).toElement();
++
++- if (el.attribute("xmlns") == QLatin1String("urn:xmpp:carbons:2") && (el.tagName() == QLatin1String("received") || el.tagName() == QLatin1String("sent"))) {
+++ if (el.attribute("xmlns") == QLatin1String("urn:xmpp:carbons:2")
+++ && (el.tagName() == QLatin1String("received") || el.tagName() == QLatin1String("sent"))
+++ && fromJid.compare(Jid(e1.attribute(QLatin1String("to"))), false)) {
++ QDomElement el1 = el.firstChildElement();
++- if (el1.tagName() == QLatin1String("forwarded") && el1.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
+++ if (el1.tagName() == QLatin1String("forwarded")
+++ && el1.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
++ QDomElement el2 = el1.firstChildElement(QLatin1String("message"));
++ if (!el2.isNull()) {
++ forward = el2;
++@@ -904,7 +908,8 @@ bool JT_PushMessage::take(const QDomElement &e)
++ }
++ }
++ }
++- else if (el.tagName() == QLatin1String("forwarded") && el.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
+++ else if (el.tagName() == QLatin1String("forwarded")
+++ && el.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
++ forward = el.firstChildElement(QLatin1String("message")); // currently only messages are supportted
++ // TODO <delay> element support
++ if (!forward.isNull()) {
++@@ -913,7 +918,6 @@ bool JT_PushMessage::take(const QDomElement &e)
++ }
++ }
++
++- QString from = e1.attribute(QLatin1String("from"));
++ Stanza s = client()->stream().createStanza(addCorrectNS(forward.isNull()? e1 : forward));
++ if(s.isNull()) {
++ //printf("take: bad stanza??\n");
++@@ -926,7 +930,7 @@ bool JT_PushMessage::take(const QDomElement &e)
++ return false;
++ }
++ if (!forward.isNull()) {
++- m.setForwardedFrom(Jid(from));
+++ m.setForwardedFrom(fromJid);
++ m.setCarbonDirection(cd);
++ }
++
+diff --git a/protocols/jabber/libiris/src/xmpp/xmpp-im/xmpp_tasks.cpp b/protocols/jabber/libiris/src/xmpp/xmpp-im/xmpp_tasks.cpp
+index 0e74b71..0837548 100644
+--- a/protocols/jabber/libiris/src/xmpp/xmpp-im/xmpp_tasks.cpp
++++ b/protocols/jabber/libiris/src/xmpp/xmpp-im/xmpp_tasks.cpp
+@@ -888,14 +888,18 @@ bool JT_PushMessage::take(const QDomElement &e)
+ QDomElement forward;
+ Message::CarbonDir cd = Message::NoCarbon;
+
++ Jid fromJid = Jid(e1.attribute(QLatin1String("from")));
+ // Check for Carbon
+ QDomNodeList list = e1.childNodes();
+ for (int i = 0; i < list.size(); ++i) {
+ QDomElement el = list.at(i).toElement();
+
+- if (el.attribute("xmlns") == QLatin1String("urn:xmpp:carbons:2") && (el.tagName() == QLatin1String("received") || el.tagName() == QLatin1String("sent"))) {
++ if (el.attribute("xmlns") == QLatin1String("urn:xmpp:carbons:2")
++ && (el.tagName() == QLatin1String("received") || el.tagName() == QLatin1String("sent"))
++ && fromJid.compare(Jid(e1.attribute(QLatin1String("to"))), false)) {
+ QDomElement el1 = el.firstChildElement();
+- if (el1.tagName() == QLatin1String("forwarded") && el1.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
++ if (el1.tagName() == QLatin1String("forwarded")
++ && el1.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
+ QDomElement el2 = el1.firstChildElement(QLatin1String("message"));
+ if (!el2.isNull()) {
+ forward = el2;
+@@ -904,7 +908,8 @@ bool JT_PushMessage::take(const QDomElement &e)
+ }
+ }
+ }
+- else if (el.tagName() == QLatin1String("forwarded") && el.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
++ else if (el.tagName() == QLatin1String("forwarded")
++ && el.attribute(QLatin1String("xmlns")) == QLatin1String("urn:xmpp:forward:0")) {
+ forward = el.firstChildElement(QLatin1String("message")); // currently only messages are supportted
+ // TODO <delay> element support
+ if (!forward.isNull()) {
+@@ -913,7 +918,6 @@ bool JT_PushMessage::take(const QDomElement &e)
+ }
+ }
+
+- QString from = e1.attribute(QLatin1String("from"));
+ Stanza s = client()->stream().createStanza(addCorrectNS(forward.isNull()? e1 : forward));
+ if(s.isNull()) {
+ //printf("take: bad stanza??\n");
+@@ -926,7 +930,7 @@ bool JT_PushMessage::take(const QDomElement &e)
+ return false;
+ }
+ if (!forward.isNull()) {
+- m.setForwardedFrom(Jid(from));
++ m.setForwardedFrom(fromJid);
+ m.setCarbonDirection(cd);
+ }
+
+--
+cgit v0.11.2
+
diff --git a/kde/patch/kopete/kopete_kdebug393372.patch b/kde/patch/kopete/kopete_kdebug393372.patch
new file mode 100644
index 0000000..2d1b1f0
--- /dev/null
+++ b/kde/patch/kopete/kopete_kdebug393372.patch
@@ -0,0 +1,30 @@
+From b1f4fa1401cba2e359e5a4b3ea2bafd119fca62b Mon Sep 17 00:00:00 2001
+From: Pino Toscano <pino@kde.org>
+Date: Tue, 24 Apr 2018 06:30:19 +0200
+Subject: oscar: include buffer.h
+
+This header uses Buffer as by-value parameter, so make sure it knows
+about it.
+
+BUG: 393372
+FIXED-IN: 18.04.1
+---
+ protocols/oscar/liboscar/tasks/messagereceivertask.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/protocols/oscar/liboscar/tasks/messagereceivertask.h b/protocols/oscar/liboscar/tasks/messagereceivertask.h
+index 8f52cd7..908e903 100644
+--- a/protocols/oscar/liboscar/tasks/messagereceivertask.h
++++ b/protocols/oscar/liboscar/tasks/messagereceivertask.h
+@@ -21,6 +21,7 @@
+ #include <QByteArray>
+ #include "oscarmessage.h"
+ #include "oscartypeclasses.h"
++#include "buffer.h"
+
+ class QTextCodec;
+
+--
+cgit v0.11.2
+
+
diff --git a/kde/patch/kpat.patch b/kde/patch/kpat.patch
new file mode 100644
index 0000000..dfc1651
--- /dev/null
+++ b/kde/patch/kpat.patch
@@ -0,0 +1,5 @@
+# Commit https://cgit.kde.org/kpat.git/patch/?id=fc1d54ced6a727382599d767e55879b6843c3456
+# Introduces a hard dependency on fc-solver which in turn has new dependencies
+# So, we revert this commit to avoid dropping kpat altogether
+cat $CWD/patch/kpat/kpat_no_freecell_solver_dep.patch | patch -p1 --reverse --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kpat/kpat_no_freecell_solver_dep.patch b/kde/patch/kpat/kpat_no_freecell_solver_dep.patch
new file mode 100644
index 0000000..c2a0e66
--- /dev/null
+++ b/kde/patch/kpat/kpat_no_freecell_solver_dep.patch
@@ -0,0 +1,1475 @@
+Commit https://cgit.kde.org/kpat.git/patch/?id=fc1d54ced6a727382599d767e55879b6843c3456
+Introduces a hard dependency on fc-solver which in turn has new dependencies
+So, we revert this commit to avoid dropping kpat altogether
+
+From ed0e53e0888da7123f4a0d2097f8da7fb105ca18 Mon Sep 17 00:00:00 2001
+From: Fabian Kosmale <0inkane@googlemail.com>
+Date: Sun, 13 May 2018 15:14:53 +0200
+Subject: Use Freecell Solver for FreeCell and Simple Simon
+
+Summary: This uses http://fc-solve.shlomifish.org/ and prevents the looping in the existing solvers.
+
+Test Plan: Test that the solvers are working.
+
+Reviewers: #kde_games, fabiank
+
+Subscribers: kde-games-devel, aacid, #kde_games
+
+Tags: #kde_games
+
+Differential Revision: https://phabricator.kde.org/D12415
+---
+ CMakeLists.txt | 6 +-
+ dealer.cpp | 4 +
+ freecell.cpp | 35 ++++
+ freecell.h | 1 +
+ patsolve/abstract_fc_solve_solver.cpp | 239 ++++++++++++++++++++++++++
+ patsolve/abstract_fc_solve_solver.h | 52 ++++++
+ patsolve/freecellsolver.cpp | 310 +++++++++++++++++-----------------
+ patsolve/freecellsolver.h | 23 ++-
+ patsolve/patsolve.h | 12 +-
+ patsolve/simonsolver.cpp | 129 +++++++++++++-
+ patsolve/simonsolver.h | 20 ++-
+ patsolve/solverinterface.h | 2 +
+ pileutils.cpp | 61 +++++++
+ pileutils.h | 4 +
+ simon.cpp | 56 +++++-
+ simon.h | 1 +
+ 16 files changed, 779 insertions(+), 176 deletions(-)
+ create mode 100644 patsolve/abstract_fc_solve_solver.cpp
+ create mode 100644 patsolve/abstract_fc_solve_solver.h
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 8f738bf..c043c45 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -4,6 +4,8 @@ cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR)
+ set (QT_MIN_VERSION "5.7.0")
+ set (KF5_MIN_VERSION "5.30.0")
+
++include(FindPkgConfig)
++pkg_check_modules(FC_SOLVE REQUIRED libfreecell-solver)
+ find_package(ECM ${KF5_MIN_VERSION} REQUIRED CONFIG)
+ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
+
+@@ -45,7 +47,7 @@ add_subdirectory(sounds)
+ add_subdirectory(themes)
+ add_subdirectory(doc)
+
+-set(kpat_SRCS
++set(kpat_SRCS ${libfcs_SRCS}
+ main.cpp
+ dealer.cpp
+ dealerinfo.cpp
+@@ -59,6 +61,7 @@ set(kpat_SRCS
+ soundengine.cpp
+ statisticsdialog.cpp
+ view.cpp
++ patsolve/abstract_fc_solve_solver.cpp
+ patsolve/memory.cpp
+ patsolve/patsolve.cpp
+
+@@ -101,6 +104,7 @@ target_link_libraries(kpat
+ KF5::KIOCore
+ KF5KDEGames
+ kcardgame
++ ${FC_SOLVE_LIBRARIES}
+ )
+
+ install(TARGETS kpat ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
+diff --git a/dealer.cpp b/dealer.cpp
+index 7c03ebf..a2558fc 100644
+--- a/dealer.cpp
++++ b/dealer.cpp
+@@ -1724,6 +1724,10 @@ void DealerScene::startSolver()
+
+ bool DealerScene::isGameLost() const
+ {
++ if (! m_winningMoves.isEmpty())
++ {
++ return false;
++ }
+ if ( solver() )
+ {
+ if ( m_solverThread && m_solverThread->isRunning() )
+diff --git a/freecell.cpp b/freecell.cpp
+index f870cdb..9a7c278 100644
+--- a/freecell.cpp
++++ b/freecell.cpp
+@@ -111,6 +111,41 @@ void Freecell::restart( const QList<KCard*> & cards )
+ }
+
+
++QString Freecell::solverFormat() const
++{
++ QString output;
++ QString tmp;
++ for (int i = 0; i < 4 ; i++) {
++ if (target[i]->isEmpty())
++ continue;
++ tmp += suitToString(target[i]->topCard()->suit()) + '-' + rankToString(target[i]->topCard()->rank()) + ' ';
++ }
++ if (!tmp.isEmpty())
++ output += QString::fromLatin1("Foundations: %1\n").arg(tmp);
++
++ tmp.truncate(0);
++ for (int i = 0; i < 4 ; i++) {
++ if (freecell[i]->isEmpty())
++ tmp += "- ";
++ else
++ tmp += rankToString(freecell[i]->topCard()->rank()) + suitToString(freecell[i]->topCard()->suit()) + ' ';
++ }
++ if (!tmp.isEmpty())
++ {
++ QString a = QString::fromLatin1("Freecells: %1\n");
++ output += a.arg(tmp);
++ }
++
++ for (int i = 0; i < 8 ; i++)
++ {
++ QList<KCard*> cards = store[i]->cards();
++ for (QList<KCard*>::ConstIterator it = cards.begin(); it != cards.end(); ++it)
++ output += rankToString((*it)->rank()) + suitToString((*it)->suit()) + ' ';
++ output += '\n';
++ }
++ return output;
++}
++
+ void Freecell::cardsDroppedOnPile( const QList<KCard*> & cards, KCardPile * pile )
+ {
+ if ( cards.size() <= 1 )
+diff --git a/freecell.h b/freecell.h
+index 7b0b2cb..9f7d84b 100644
+--- a/freecell.h
++++ b/freecell.h
+@@ -62,6 +62,7 @@ protected slots:
+ private:
+ bool canPutStore( const KCardPile * pile, const QList<KCard*> & cards ) const;
+
++ virtual QString solverFormat() const;
+ PatPile* store[8];
+ PatPile* freecell[4];
+ PatPile* target[4];
+diff --git a/patsolve/abstract_fc_solve_solver.cpp b/patsolve/abstract_fc_solve_solver.cpp
+new file mode 100644
+index 0000000..11e5baa
+--- /dev/null
++++ b/patsolve/abstract_fc_solve_solver.cpp
+@@ -0,0 +1,239 @@
++/*
++ * Copyright (C) 2006-2009 Stephan Kulow <coolo@kde.org>
++ *
++ * 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 of
++ * the License, 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.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include <stdlib.h>
++#include <string.h>
++
++#include "freecell-solver/fcs_user.h"
++#include "freecell-solver/fcs_cl.h"
++
++#include "abstract_fc_solve_solver.h"
++
++const int CHUNKSIZE = 100;
++const long int MAX_ITERS_LIMIT = 200000;
++
++#define PRINT 0
++
++/* These two routines make and unmake moves. */
++
++void FcSolveSolver::make_move(MOVE *)
++{
++ return;
++}
++
++void FcSolveSolver::undo_move(MOVE *)
++{
++ return;
++}
++
++/* Get the possible moves from a position, and store them in Possible[]. */
++SolverInterface::ExitStatus FcSolveSolver::patsolve( int _max_positions )
++{
++ int current_iters_count;
++ max_positions = (_max_positions < 0) ? MAX_ITERS_LIMIT : _max_positions;
++
++ init();
++
++ if (!solver_instance)
++ {
++ {
++ solver_instance = freecell_solver_user_alloc();
++
++ solver_ret = FCS_STATE_NOT_BEGAN_YET;
++
++ char * error_string;
++ int error_arg;
++ const char * known_parameters[1] = {NULL};
++ /* A "char *" copy instead of "const char *". */
++
++ int parse_args_ret_code = freecell_solver_user_cmd_line_parse_args(
++ solver_instance,
++ get_cmd_line_arg_count() ,
++ get_cmd_line_args(),
++ 0,
++ known_parameters,
++ NULL,
++ NULL,
++ &error_string,
++ &error_arg
++ );
++
++ Q_ASSERT(!parse_args_ret_code);
++ }
++
++ /* Not needed for Simple Simon because it's already specified in
++ * freecell_solver_cmd_line_args. TODO : abstract .
++ *
++ * Shlomi Fish
++ * */
++ setFcSolverGameParams();
++
++ current_iters_count = CHUNKSIZE;
++ freecell_solver_user_limit_iterations(solver_instance, current_iters_count);
++ }
++
++ if (solver_instance)
++ {
++ bool continue_loop = true;
++ while (continue_loop &&
++ ( (solver_ret == FCS_STATE_NOT_BEGAN_YET)
++ || (solver_ret == FCS_STATE_SUSPEND_PROCESS))
++ &&
++ (current_iters_count < MAX_ITERS_LIMIT)
++ )
++ {
++ current_iters_count += CHUNKSIZE;
++ freecell_solver_user_limit_iterations(solver_instance, current_iters_count);
++
++ if (solver_ret == FCS_STATE_NOT_BEGAN_YET)
++ {
++ solver_ret =
++ freecell_solver_user_solve_board(
++ solver_instance,
++ board_as_string
++ );
++ }
++ else
++ {
++ solver_ret = freecell_solver_user_resume_solution(solver_instance);
++ }
++ {
++ // QMutexLocker lock( &endMutex );
++ if ( m_shouldEnd )
++ {
++ continue_loop = false;
++ }
++ }
++ }
++ }
++
++ switch (solver_ret)
++ {
++ case FCS_STATE_IS_NOT_SOLVEABLE:
++ if (solver_instance)
++ {
++ freecell_solver_user_free(solver_instance);
++ solver_instance = NULL;
++ }
++ return Solver::NoSolutionExists;
++
++ case FCS_STATE_WAS_SOLVED:
++ {
++ if (solver_instance)
++ {
++ m_winMoves.clear();
++ while (freecell_solver_user_get_moves_left(solver_instance))
++ {
++ fcs_move_t move;
++ MOVE new_move;
++ const int verdict = !freecell_solver_user_get_next_move(
++ solver_instance, &move)
++ ;
++
++ Q_ASSERT (verdict);
++
++ new_move.fcs = move;
++
++ m_winMoves.append( new_move );
++ }
++
++ freecell_solver_user_free(solver_instance);
++ solver_instance = NULL;
++ }
++ return Solver::SolutionExists;
++ }
++
++ case FCS_STATE_SUSPEND_PROCESS:
++ return Solver::UnableToDetermineSolvability;
++
++ default:
++ if (solver_instance)
++ {
++ freecell_solver_user_free(solver_instance);
++ solver_instance = NULL;
++ }
++ return Solver::NoSolutionExists;
++ }
++}
++
++/* Get the possible moves from a position, and store them in Possible[]. */
++
++int FcSolveSolver::get_possible_moves(int *, int *)
++{
++ return 0;
++}
++
++bool FcSolveSolver::isWon()
++{
++ return true;
++}
++
++int FcSolveSolver::getOuts()
++{
++ return 0;
++}
++
++FcSolveSolver::FcSolveSolver()
++ : Solver()
++ , solver_instance(NULL)
++ , solver_ret(FCS_STATE_NOT_BEGAN_YET)
++ , board_as_string("")
++{
++}
++
++unsigned int FcSolveSolver::getClusterNumber()
++{
++ return 0;
++}
++
++void FcSolveSolver::print_layout()
++{
++#if 0
++ int i, w, o;
++
++ fprintf(stderr, "print-layout-begin\n");
++ for (w = 0; w < 10; ++w) {
++ Q_ASSERT( Wp[w] == &W[w][Wlen[w]-1] );
++ fprintf( stderr, "Play%d: ", w );
++ for (i = 0; i < Wlen[w]; ++i) {
++ printcard(W[w][i], stderr);
++ }
++ fputc('\n', stderr);
++ }
++ fprintf( stderr, "Off: " );
++ for (o = 0; o < 4; ++o) {
++ if ( O[o] != -1 )
++ printcard( O[o] + PS_KING, stderr);
++ }
++ fprintf(stderr, "\nprint-layout-end\n");
++#endif
++}
++
++void FcSolveSolver::unpack_cluster( unsigned int)
++{
++ return;
++}
++
++FcSolveSolver::~FcSolveSolver()
++{
++ if (solver_instance)
++ {
++ freecell_solver_user_free(solver_instance);
++ solver_instance = NULL;
++ }
++}
++
+diff --git a/patsolve/abstract_fc_solve_solver.h b/patsolve/abstract_fc_solve_solver.h
+new file mode 100644
+index 0000000..d2d072d
+--- /dev/null
++++ b/patsolve/abstract_fc_solve_solver.h
+@@ -0,0 +1,52 @@
++/*
++ * Copyright (C) 2006-2009 Stephan Kulow <coolo@kde.org>
++ *
++ * 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 of
++ * the License, 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.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef ABSTRACT_FC_SOLVE_SOLVER_H
++#define ABSTRACT_FC_SOLVE_SOLVER_H
++
++#include "patsolve.h"
++
++struct FcSolveSolver : public Solver<10>
++{
++public:
++ FcSolveSolver();
++ virtual ~FcSolveSolver();
++ virtual int get_possible_moves(int *a, int *numout);
++ virtual bool isWon();
++ virtual void make_move(MOVE *m);
++ virtual void undo_move(MOVE *m);
++ virtual int getOuts();
++ virtual unsigned int getClusterNumber();
++ virtual void translate_layout() = 0;
++ virtual void unpack_cluster( unsigned int k );
++ virtual MoveHint translateMove(const MOVE &m) = 0;
++ virtual SolverInterface::ExitStatus patsolve( int _max_positions = -1);
++ virtual void setFcSolverGameParams() = 0;
++
++ virtual void print_layout();
++
++ virtual int get_cmd_line_arg_count() = 0;
++ virtual const char * * get_cmd_line_args() = 0;
++/* Names of the cards. The ordering is defined in pat.h. */
++
++ void * solver_instance;
++ int solver_ret;
++ // More than enough space for two decks.
++ char board_as_string[4 * 13 * 2 * 4 * 3];
++};
++
++#endif // ABSTRACT_FC_SOLVE_SOLVER_H
+diff --git a/patsolve/freecellsolver.cpp b/patsolve/freecellsolver.cpp
+index 39eff50..e92000f 100644
+--- a/patsolve/freecellsolver.cpp
++++ b/patsolve/freecellsolver.cpp
+@@ -16,12 +16,18 @@
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
++#include <stdlib.h>
++#include <string.h>
++
++#include "freecell-solver/fcs_user.h"
++#include "freecell-solver/fcs_cl.h"
++
+ #include "freecellsolver.h"
+
+ #include "../freecell.h"
+
+-
+-/* Some macros used in get_possible_moves(). */
++const int CHUNKSIZE = 100;
++const long int MAX_ITERS_LIMIT = 200000;
+
+ /* The following function implements
+ (Same_suit ? (suit(a) == suit(b)) : (color(a) != color(b)))
+@@ -32,10 +38,13 @@ namespace {
+
+ /* Statistics. */
+
++#if 0
+ int FreecellSolver::Xparam[] = { 4, 1, 8, -1, 7, 11, 4, 2, 2, 1, 2 };
++#endif
+
+ /* These two routines make and unmake moves. */
+
++#if 0
+ void FreecellSolver::make_move(MOVE *m)
+ {
+ int from, to;
+@@ -85,7 +94,9 @@ void FreecellSolver::undo_move(MOVE *m)
+ Wlen[from]++;
+ hashpile(from);
+ }
++#endif
+
++#if 0
+ /* Move prioritization. Given legal, pruned moves, there are still some
+ that are a waste of time, especially in the endgame where there are lots of
+ possible moves, but few productive ones. Note that we also prioritize
+@@ -178,9 +189,11 @@ void FreecellSolver::prioritize(MOVE *mp0, int n)
+ }
+ }
+ }
++#endif
+
+ /* Automove logic. Freecell games must avoid certain types of automoves. */
+
++#if 0
+ int FreecellSolver::good_automove(int o, int r)
+ {
+ int i;
+@@ -220,148 +233,43 @@ int FreecellSolver::good_automove(int o, int r)
+
+ return true;
+ }
++#endif
+
+-/* Get the possible moves from a position, and store them in Possible[]. */
++#define CMD_LINE_ARGS_NUM 2
+
+-int FreecellSolver::get_possible_moves(int *a, int *numout)
++static const char * freecell_solver_cmd_line_args[CMD_LINE_ARGS_NUM] =
+ {
+- int i, n, t, w, o, empty, emptyw;
+- card_t card;
+- MOVE *mp;
+-
+- /* Check for moves from W to O. */
+-
+- n = 0;
+- mp = Possible;
+- for (w = 0; w < Nwpiles + Ntpiles; ++w) {
+- if (Wlen[w] > 0) {
+- card = *Wp[w];
+- o = SUIT(card);
+- empty = (O[o] == NONE);
+- if ((empty && (RANK(card) == PS_ACE)) ||
+- (!empty && (RANK(card) == O[o] + 1))) {
+- mp->card_index = 0;
+- mp->from = w;
+- mp->to = o;
+- mp->totype = O_Type;
+- mp->turn_index = -1;
+- mp->pri = 0; /* unused */
+- n++;
+- mp++;
+-
+- /* If it's an automove, just do it. */
+-
+- if (good_automove(o, RANK(card))) {
+- *a = true;
+- mp[-1].pri = 127;
+- if (n != 1) {
+- Possible[0] = mp[-1];
+- return 1;
+- }
+- return n;
+- }
+- }
+- }
+- }
+-
+- /* No more automoves, but remember if there were any moves out. */
+-
+- *a = false;
+- *numout = n;
+-
+- /* Check for moves from non-singleton W cells to one of any
+- empty W cells. */
++ "--load-config", "video-editing"
++};
+
+- emptyw = -1;
+- for (w = 0; w < Nwpiles; ++w) {
+- if (Wlen[w] == 0) {
+- emptyw = w;
+- break;
+- }
+- }
+- if (emptyw >= 0) {
+- for (i = 0; i < Nwpiles + Ntpiles; ++i) {
+- if (i == emptyw || Wlen[i] == 0) {
+- continue;
+- }
+- bool allowed = false;
+- if ( i < Nwpiles)
+- allowed = true;
+- if ( i >= Nwpiles )
+- allowed = true;
+- if ( allowed ) {
+- card = *Wp[i];
+- mp->card_index = 0;
+- mp->from = i;
+- mp->to = emptyw;
+- mp->totype = W_Type;
+- mp->turn_index = -1;
+- if ( i >= Nwpiles )
+- mp->pri = Xparam[6];
+- else
+- mp->pri = Xparam[3];
+- n++;
+- mp++;
+- }
+- }
+- }
+-
+- /* Check for moves from W to non-empty W cells. */
+-
+- for (i = 0; i < Nwpiles + Ntpiles; ++i) {
+- if (Wlen[i] > 0) {
+- card = *Wp[i];
+- for (w = 0; w < Nwpiles; ++w) {
+- if (i == w) {
+- continue;
+- }
+- if (Wlen[w] > 0 &&
+- (RANK(card) == RANK(*Wp[w]) - 1 &&
+- suitable(card, *Wp[w]))) {
+- mp->card_index = 0;
+- mp->from = i;
+- mp->to = w;
+- mp->totype = W_Type;
+- mp->turn_index = -1;
+- if ( i >= Nwpiles )
+- mp->pri = Xparam[5];
+- else
+- mp->pri = Xparam[4];
+- n++;
+- mp++;
+- }
+- }
+- }
+- }
+-
+- /* Check for moves from W to one of any empty T cells. */
+-
+- for (t = 0; t < Ntpiles; ++t) {
+- if (!Wlen[t+Nwpiles]) {
+- break;
+- }
+- }
++int FreecellSolver::get_cmd_line_arg_count()
++{
++ return CMD_LINE_ARGS_NUM;
++}
+
+- if (t < Ntpiles) {
+- for (w = 0; w < Nwpiles; ++w) {
+- if (Wlen[w] > 0) {
+- card = *Wp[w];
+- mp->card_index = 0;
+- mp->from = w;
+- mp->turn_index = -1;
+- mp->to = t+Nwpiles;
+- mp->totype = W_Type;
+- mp->pri = Xparam[7];
+- n++;
+- mp++;
+- }
+- }
+- }
++const char * * FreecellSolver::get_cmd_line_args()
++{
++ return freecell_solver_cmd_line_args;
++}
+
+
+- return n;
++void FreecellSolver::setFcSolverGameParams()
++{
++ /*
++ * I'm using the more standard interface instead of the depracated
++ * user_set_game one. I'd like that each function will have its
++ * own dedicated purpose.
++ *
++ * Shlomi Fish
++ * */
++ freecell_solver_user_set_num_freecells(solver_instance,4);
++ freecell_solver_user_set_num_stacks(solver_instance,8);
++ freecell_solver_user_set_num_decks(solver_instance,1);
++ freecell_solver_user_set_sequences_are_built_by_type(solver_instance, FCS_SEQ_BUILT_BY_ALTERNATE_COLOR);
++ freecell_solver_user_set_sequence_move(solver_instance, 0);
++ freecell_solver_user_set_empty_stacks_filled_by(solver_instance, FCS_ES_FILLED_BY_ANY_CARD);
+ }
+-
++#if 0
+ void FreecellSolver::unpack_cluster( unsigned int k )
+ {
+ /* Get the Out cells from the cluster number. */
+@@ -373,27 +281,13 @@ void FreecellSolver::unpack_cluster( unsigned int k )
+ k >>= 4;
+ O[3] = k & 0xF;
+ }
++#endif
+
+-bool FreecellSolver::isWon()
+-{
+- // maybe won?
+- for (int o = 0; o < 4; ++o) {
+- if (O[o] != PS_KING) {
+- return false;
+- }
+- }
+-
+- return true;
+-}
+-
+-int FreecellSolver::getOuts()
+-{
+- return O[0] + O[1] + O[2] + O[3];
+-}
+
+ FreecellSolver::FreecellSolver(const Freecell *dealer)
+- : Solver()
++ : FcSolveSolver()
+ {
++#if 0
+ Osuit[0] = PS_DIAMOND;
+ Osuit[1] = PS_CLUB;
+ Osuit[2] = PS_HEART;
+@@ -402,12 +296,15 @@ FreecellSolver::FreecellSolver(const Freecell *dealer)
+ Nwpiles = 8;
+ Ntpiles = 4;
+
++#endif
++
+ deal = dealer;
+ }
+
+ /* Read a layout file. Format is one pile per line, bottom to top (visible
+ card). Temp cells and Out on the last two lines, if any. */
+
++#if 0
+ void FreecellSolver::translate_layout()
+ {
+ /* Read the workspace. */
+@@ -447,9 +344,78 @@ void FreecellSolver::translate_layout()
+ }
+ }
+ }
++#endif
+
+ MoveHint FreecellSolver::translateMove( const MOVE &m )
+ {
++ fcs_move_t move = m.fcs;
++ int cards = fcs_move_get_num_cards_in_seq(move);
++ PatPile *from = 0;
++ PatPile *to = 0;
++
++ switch(fcs_move_get_type(move))
++ {
++ case FCS_MOVE_TYPE_STACK_TO_STACK:
++ from = deal->store[fcs_move_get_src_stack(move)];
++ to = deal->store[fcs_move_get_dest_stack(move)];
++ break;
++
++ case FCS_MOVE_TYPE_FREECELL_TO_STACK:
++ from = deal->freecell[fcs_move_get_src_freecell(move)];
++ to = deal->store[fcs_move_get_dest_stack(move)];
++ cards = 1;
++ break;
++
++ case FCS_MOVE_TYPE_FREECELL_TO_FREECELL:
++ from = deal->freecell[fcs_move_get_src_freecell(move)];
++ to = deal->freecell[fcs_move_get_dest_freecell(move)];
++ cards = 1;
++ break;
++
++ case FCS_MOVE_TYPE_STACK_TO_FREECELL:
++ from = deal->store[fcs_move_get_src_stack(move)];
++ to = deal->freecell[fcs_move_get_dest_freecell(move)];
++ cards = 1;
++ break;
++
++ case FCS_MOVE_TYPE_STACK_TO_FOUNDATION:
++ from = deal->store[fcs_move_get_src_stack(move)];
++ cards = 1;
++ to = 0;
++ break;
++
++ case FCS_MOVE_TYPE_FREECELL_TO_FOUNDATION:
++ from = deal->freecell[fcs_move_get_src_freecell(move)];
++ cards = 1;
++ to = 0;
++ }
++ Q_ASSERT(from);
++ Q_ASSERT(cards <= from->cards().count());
++ Q_ASSERT(to || cards == 1);
++ KCard *card = from->cards()[from->cards().count() - cards];
++
++ if (!to)
++ {
++ PatPile *target = 0;
++ PatPile *empty = 0;
++ for (int i = 0; i < 4; ++i) {
++ KCard *c = deal->target[i]->topCard();
++ if (c) {
++ if ( c->suit() == card->suit() )
++ {
++ target = deal->target[i];
++ break;
++ }
++ } else if ( !empty )
++ empty = deal->target[i];
++ }
++ to = target ? target : empty;
++ }
++ Q_ASSERT(to);
++
++ return MoveHint(card, to, 0);
++
++#if 0
+ // this is tricky as we need to want to build the "meta moves"
+
+ PatPile *frompile = nullptr;
+@@ -486,8 +452,43 @@ MoveHint FreecellSolver::translateMove( const MOVE &m )
+
+ return MoveHint( card, target, m.pri );
+ }
++#endif
+ }
+
++void FreecellSolver::translate_layout()
++{
++ strcpy(board_as_string, deal->solverFormat().toLatin1());
++
++ if (solver_instance)
++ {
++ freecell_solver_user_recycle(solver_instance);
++ solver_ret = FCS_STATE_NOT_BEGAN_YET;
++ }
++#if 0
++ /* Read the workspace. */
++ int total = 0;
++
++ for ( int w = 0; w < 10; ++w ) {
++ int i = translate_pile(deal->store[w], W[w], 52);
++ Wp[w] = &W[w][i - 1];
++ Wlen[w] = i;
++ total += i;
++ }
++
++ for (int i = 0; i < 4; ++i) {
++ O[i] = -1;
++ KCard *c = deal->target[i]->top();
++ if (c) {
++ total += 13;
++ O[i] = translateSuit( c->suit() );
++ }
++ }
++#endif
++}
++
++
++
++#if 0
+ unsigned int FreecellSolver::getClusterNumber()
+ {
+ int i = O[0] + (O[1] << 4);
+@@ -496,7 +497,9 @@ unsigned int FreecellSolver::getClusterNumber()
+ k |= i << 8;
+ return k;
+ }
++#endif
+
++#if 0
+ void FreecellSolver::print_layout()
+ {
+ int i, t, w, o;
+@@ -519,3 +522,4 @@ void FreecellSolver::print_layout()
+ }
+ fprintf(stderr, "\nprint-layout-end\n");
+ }
++#endif
+diff --git a/patsolve/freecellsolver.h b/patsolve/freecellsolver.h
+index 45ca063..99d1dbb 100644
+--- a/patsolve/freecellsolver.h
++++ b/patsolve/freecellsolver.h
+@@ -19,16 +19,17 @@
+ #ifndef FREECELLSOLVER_H
+ #define FREECELLSOLVER_H
+
+-class Freecell;
+-#include "patsolve.h"
++#include "abstract_fc_solve_solver.h"
+
+ constexpr auto Nwpiles = 8;
+ constexpr auto Ntpiles = 4;
++class Freecell;
+
+-class FreecellSolver : public Solver<Nwpiles + Ntpiles>
++class FreecellSolver : public FcSolveSolver
+ {
+ public:
+ explicit FreecellSolver(const Freecell *dealer);
++#if 0
+ int good_automove(int o, int r);
+ int get_possible_moves(int *a, int *numout) Q_DECL_OVERRIDE;
+ bool isWon() Q_DECL_OVERRIDE;
+@@ -40,8 +41,17 @@ public:
+ void translate_layout() Q_DECL_OVERRIDE;
+ void unpack_cluster( unsigned int k ) Q_DECL_OVERRIDE;
+ MoveHint translateMove(const MOVE &m) Q_DECL_OVERRIDE;
+-
+- void print_layout() Q_DECL_OVERRIDE;
++#endif
++ virtual void translate_layout();
++#if 0
++ virtual void unpack_cluster( unsigned int k );
++#endif
++ virtual MoveHint translateMove(const MOVE &m);
++ virtual void setFcSolverGameParams();
++ virtual int get_cmd_line_arg_count();
++ virtual const char * * get_cmd_line_args();
++#if 0
++ virtual void print_layout();
+
+ int Nwpiles; /* the numbers we're actually using */
+ int Ntpiles;
+@@ -51,10 +61,11 @@ public:
+ card_t O[4]; /* output piles store only the rank or NONE */
+ card_t Osuit[4];
+
+- const Freecell *deal;
+
+ static int Xparam[];
++#endif
+
++ const Freecell *deal;
+ };
+
+ #endif // FREECELLSOLVER_H
+diff --git a/patsolve/patsolve.h b/patsolve/patsolve.h
+index 03285d4..1c3a7c6 100644
+--- a/patsolve/patsolve.h
++++ b/patsolve/patsolve.h
+@@ -33,6 +33,10 @@
+
+ #include <cstdio>
+
++/* A card is represented as ( down << 6 ) + (suit << 4) + rank. */
++
++typedef quint8 card_t;
++
+ struct POSITION {
+ POSITION *queue; /* next position in the queue */
+ POSITION *parent; /* point back up the move stack */
+@@ -48,14 +52,15 @@ class MemoryManager;
+ template<size_t NumberPiles>
+ class Solver : public SolverInterface
+ {
++
+ public:
+
+ Solver();
+ virtual ~Solver();
+- ExitStatus patsolve( int max_positions = -1) final;
++ virtual ExitStatus patsolve( int max_positions = -1);
++ bool recursive(POSITION *pos = nullptr);
+ virtual void translate_layout() = 0;
+ virtual MoveHint translateMove(const MOVE &m ) = 0;
+-
+ void stopExecution() final;
+ QList<MOVE> firstMoves() const final;
+ QList<MOVE> winMoves() const final;
+@@ -126,8 +131,7 @@ protected:
+ POSITION *Stack = nullptr;
+ QMap<qint32,bool> recu_pos;
+ int max_positions;
+-
+-private:
++protected:
+ QList<MOVE> m_firstMoves;
+ QList<MOVE> m_winMoves;
+ std::atomic_bool m_shouldEnd;
+diff --git a/patsolve/simonsolver.cpp b/patsolve/simonsolver.cpp
+index a9d640c..e75dcaa 100644
+--- a/patsolve/simonsolver.cpp
++++ b/patsolve/simonsolver.cpp
+@@ -15,17 +15,26 @@
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
++#include <stdlib.h>
++#include <string.h>
++
++#include "freecell-solver/fcs_user.h"
++#include "freecell-solver/fcs_cl.h"
++
+ #include "simonsolver.h"
+
+ #include "../simon.h"
+
+ #include <QDebug>
+
++const int CHUNKSIZE = 100;
++const long int MAX_ITERS_LIMIT = 200000;
+
+ #define PRINT 0
+
+ /* These two routines make and unmake moves. */
+
++#if 0
+ void SimonSolver::make_move(MOVE *m)
+ {
+ #if PRINT
+@@ -136,13 +145,62 @@ void SimonSolver::undo_move(MOVE *m)
+ print_layout();
+ #endif
+ }
++#endif
++
++#define CMD_LINE_ARGS_NUM 4
++static const char * freecell_solver_cmd_line_args[CMD_LINE_ARGS_NUM] =
++{
++ "-g", "simple_simon", "--load-config", "the-last-mohican"
++};
++
++int SimonSolver::get_cmd_line_arg_count()
++{
++ return CMD_LINE_ARGS_NUM;
++}
++
++const char * * SimonSolver::get_cmd_line_args()
++{
++ return freecell_solver_cmd_line_args;
++}
++
++void SimonSolver::setFcSolverGameParams()
++{
++ freecell_solver_user_apply_preset(solver_instance, "simple_simon");
++}
+
++#if 0
+ /* Get the possible moves from a position, and store them in Possible[]. */
+
+ int SimonSolver::get_possible_moves(int *a, int *numout)
+ {
+ MOVE *mp;
++ int n;
++
++ mp = Possible;
++ n = 0;
++ *a = 1;
++
++ while (freecell_solver_user_get_moves_left(solver_instance))
++ {
++ fcs_move_t move;
++ fcs_move_t * move_ptr;
++ if (!freecell_solver_user_get_next_move(solver_instance, &move)) {
++ move_ptr = new fcs_move_t;
++ *move_ptr = move;
++ mp->ptr = (void *)move_ptr;
++ mp++;
++ n++;
++ }
++ else
++ {
++ Q_ASSERT(0);
++ }
++ }
++
++ *numout = n;
++ return n;
+
++#if 0
+ /* Check for moves from W to O. */
+
+ int n = 0;
+@@ -301,8 +359,11 @@ int SimonSolver::get_possible_moves(int *a, int *numout)
+ }
+
+ return n;
++#endif
+ }
++#endif
+
++#if 0
+ void SimonSolver::unpack_cluster( unsigned int k )
+ {
+ // TODO: this only works for easy
+@@ -314,7 +375,9 @@ void SimonSolver::unpack_cluster( unsigned int k )
+ O[i] = -1;
+ }
+ }
++#endif
+
++#if 0
+ bool SimonSolver::isWon()
+ {
+ // maybe won?
+@@ -324,7 +387,9 @@ bool SimonSolver::isWon()
+
+ return true;
+ }
++#endif
+
++#if 0
+ int SimonSolver::getOuts()
+ {
+ int k = 0;
+@@ -334,9 +399,10 @@ int SimonSolver::getOuts()
+
+ return k;
+ }
++#endif
+
+ SimonSolver::SimonSolver(const Simon *dealer)
+- : Solver()
++ : FcSolveSolver()
+ {
+ deal = dealer;
+ }
+@@ -346,6 +412,14 @@ card). Temp cells and Out on the last two lines, if any. */
+
+ void SimonSolver::translate_layout()
+ {
++ strcpy(board_as_string, deal->solverFormat().toLatin1());
++
++ if (solver_instance)
++ {
++ freecell_solver_user_recycle(solver_instance);
++ solver_ret = FCS_STATE_NOT_BEGAN_YET;
++ }
++#if 0
+ /* Read the workspace. */
+ int total = 0;
+
+@@ -364,8 +438,10 @@ void SimonSolver::translate_layout()
+ O[i] = translateSuit( c->suit() );
+ }
+ }
++#endif
+ }
+
++#if 0
+ unsigned int SimonSolver::getClusterNumber()
+ {
+ unsigned int k = 0;
+@@ -376,7 +452,9 @@ unsigned int SimonSolver::getClusterNumber()
+ }
+ return k;
+ }
++#endif
+
++#if 0
+ void SimonSolver::print_layout()
+ {
+ int i, w, o;
+@@ -397,9 +475,57 @@ void SimonSolver::print_layout()
+ }
+ fprintf(stderr, "\nprint-layout-end\n");
+ }
++#endif
+
+ MoveHint SimonSolver::translateMove( const MOVE &m )
+ {
++ fcs_move_t move = m.fcs;
++ int cards = fcs_move_get_num_cards_in_seq(move);
++ PatPile *from = 0;
++ PatPile *to = 0;
++
++ switch(fcs_move_get_type(move))
++ {
++ case FCS_MOVE_TYPE_STACK_TO_STACK:
++ from = deal->store[fcs_move_get_src_stack(move)];
++ to = deal->store[fcs_move_get_dest_stack(move)];
++ break;
++
++ case FCS_MOVE_TYPE_SEQ_TO_FOUNDATION:
++ from = deal->store[fcs_move_get_src_stack(move)];
++ cards = 13;
++ to = deal->target[fcs_move_get_foundation(move)];
++ break;
++
++ }
++ Q_ASSERT(from);
++ Q_ASSERT(cards <= from->cards().count());
++ Q_ASSERT(to || cards == 1);
++ KCard *card = from->cards()[from->cards().count() - cards];
++
++ if (!to)
++ {
++ PatPile *target = 0;
++ PatPile *empty = 0;
++ for (int i = 0; i < 4; ++i) {
++ KCard *c = deal->target[i]->topCard();
++ if (c) {
++ if ( c->suit() == card->suit() )
++ {
++ target = deal->target[i];
++ break;
++ }
++ } else if ( !empty )
++ empty = deal->target[i];
++ }
++ to = target ? target : empty;
++ }
++
++ Q_ASSERT(to);
++
++ return MoveHint(card, to, 0);
++
++#if 0
+ Q_ASSERT( m.from < 10 && m.to < 10 );
+
+ PatPile *frompile = deal->store[m.from];
+@@ -414,4 +540,5 @@ MoveHint SimonSolver::translateMove( const MOVE &m )
+
+ Q_ASSERT( m.to < 10 );
+ return MoveHint( card, deal->store[m.to], m.pri );
++#endif
+ }
+diff --git a/patsolve/simonsolver.h b/patsolve/simonsolver.h
+index 2d57dda..4a417b1 100644
+--- a/patsolve/simonsolver.h
++++ b/patsolve/simonsolver.h
+@@ -18,29 +18,37 @@
+ #ifndef SIMONSOLVER_H
+ #define SIMONSOLVER_H
+
+-#include "patsolve.h"
++#include "abstract_fc_solve_solver.h"
++#include "simon.h"
+ class Simon;
+
+
+-class SimonSolver : public Solver<10>
++class SimonSolver : public FcSolveSolver
+ {
+ public:
+ explicit SimonSolver(const Simon *dealer);
++#if 0
+ int get_possible_moves(int *a, int *numout) Q_DECL_OVERRIDE;
+ bool isWon() Q_DECL_OVERRIDE;
+ void make_move(MOVE *m) Q_DECL_OVERRIDE;
+ void undo_move(MOVE *m) Q_DECL_OVERRIDE;
+ int getOuts() Q_DECL_OVERRIDE;
+ unsigned int getClusterNumber() Q_DECL_OVERRIDE;
+- void translate_layout() Q_DECL_OVERRIDE;
++#endif
++ virtual void translate_layout() Q_DECL_OVERRIDE;
++ virtual MoveHint translateMove(const MOVE &m) Q_DECL_OVERRIDE;
++#if 0
+ void unpack_cluster( unsigned int k ) Q_DECL_OVERRIDE;
+- MoveHint translateMove(const MOVE &m) Q_DECL_OVERRIDE;
+-
+ void print_layout() Q_DECL_OVERRIDE;
++#endif
++ virtual void setFcSolverGameParams();
+
++ virtual int get_cmd_line_arg_count();
++ virtual const char * * get_cmd_line_args();
++#if 0
+ /* Names of the cards. The ordering is defined in pat.h. */
+-
+ int O[4];
++#endif
+ const Simon *deal;
+ };
+
+diff --git a/patsolve/solverinterface.h b/patsolve/solverinterface.h
+index d99d3b8..77fd410 100644
+--- a/patsolve/solverinterface.h
++++ b/patsolve/solverinterface.h
+@@ -4,6 +4,7 @@
+ #include <QList>
+
+ #include "../hint.h"
++#include "freecell-solver/fcs_user.h"
+
+
+ /* A card is represented as ( down << 6 ) + (suit << 4) + rank. */
+@@ -22,6 +23,7 @@ public:
+ PileType totype;
+ signed char pri; /* move priority (low priority == low value) */
+ int turn_index; /* turn the card index */
++ fcs_move_t fcs; /* A Freecell Solver move. */
+
+ bool operator<( const MOVE &m) const
+ {
+diff --git a/pileutils.cpp b/pileutils.cpp
+index 1e3da3e..609c716 100644
+--- a/pileutils.cpp
++++ b/pileutils.cpp
+@@ -48,6 +48,33 @@ bool isSameSuitAscending( const QList<KCard*> & cards )
+ return true;
+ }
+
++int countSameSuitDescendingSequences( const QList<KCard*> & cards )
++{
++ if ( cards.size() <= 1 )
++ return 0;
++
++ int suit = cards.first()->suit();
++ int lastRank = cards.first()->rank();
++
++ int count = 1;
++
++ for( int i = 1; i < cards.size(); ++i )
++ {
++ --lastRank;
++
++ if ( cards[i]->rank() != lastRank )
++ return -1;
++
++ if ( cards[i]->suit() != suit )
++ {
++ count++;
++ suit = cards[i]->suit();
++ }
++ }
++ return count;
++}
++
++
+
+ bool isSameSuitDescending( const QList<KCard*> & cards )
+ {
+@@ -121,3 +148,37 @@ bool checkAddAlternateColorDescendingFromKing( const QList<KCard*> & oldCards, c
+ && newCards.first()->rank() == oldCards.last()->rank() - 1;
+ }
+
++QString suitToString(int s) {
++ switch (s) {
++ case KCardDeck::Clubs:
++ return "C";
++ case KCardDeck::Hearts:
++ return "H";
++ case KCardDeck::Diamonds:
++ return "D";
++ case KCardDeck::Spades:
++ return "S";
++ default:
++ exit(-1);
++ }
++ return QString();
++}
++
++QString rankToString(int r)
++{
++ switch (r) {
++ case KCardDeck::King:
++ return "K";
++ case KCardDeck::Ace:
++ return "A";
++ case KCardDeck::Jack:
++ return "J";
++ case KCardDeck::Queen:
++ return "Q";
++ case KCardDeck::Ten:
++ return "T";
++ default:
++ return QString::number(r);
++ }
++}
++
+diff --git a/pileutils.h b/pileutils.h
+index 2fa1657..faa8c40 100644
+--- a/pileutils.h
++++ b/pileutils.h
+@@ -26,9 +26,13 @@ class KCard;
+ bool isSameSuitAscending( const QList<KCard*> & cards );
+ bool isSameSuitDescending( const QList<KCard*> & cards );
+ bool isAlternateColorDescending( const QList<KCard*> & cards );
++int countSameSuitDescendingSequences( const QList<KCard*> & cards );
+
+ bool checkAddSameSuitAscendingFromAce( const QList<KCard*> & oldCards, const QList<KCard*> & newCards );
+ bool checkAddAlternateColorDescending( const QList<KCard*> & oldCards, const QList<KCard*> & newCards );
+ bool checkAddAlternateColorDescendingFromKing( const QList<KCard*> & oldCards, const QList<KCard*> & newCards );
+
++extern QString suitToString(int s);
++extern QString rankToString(int r);
++
+ #endif
+diff --git a/simon.cpp b/simon.cpp
+index 8e3ef10..834dd55 100644
+--- a/simon.cpp
++++ b/simon.cpp
+@@ -111,24 +111,70 @@ bool Simon::checkAdd(const PatPile * pile, const QList<KCard*> & oldCards, const
+ {
+ if (pile->pileRole() == PatPile::Tableau)
+ {
+- return oldCards.isEmpty()
+- || oldCards.last()->rank() == newCards.first()->rank() + 1;
++ if (! (oldCards.isEmpty()
++ || oldCards.last()->rank() == newCards.first()->rank() + 1 ))
++ {
++ return false;
++ }
++
++ int seqs_count = countSameSuitDescendingSequences(newCards);
++
++ if (seqs_count < 0)
++ return false;
++
++ // This is similar to the supermoves of Freecell - we can use empty
++ // columns to temporarily hold intermediate sub-sequences which are
++ // not the same suit - only a "false" parent.
++ // Shlomi Fish
++
++ int empty_piles_count = 0;
++
++ for (int i = 0; i < 10; ++i )
++ if (store[i]->isEmpty() && ( store[i]->index() != pile->index() ))
++ empty_piles_count++;
++
++ return (seqs_count <= (1 << empty_piles_count));
+ }
+ else
+ {
+ return oldCards.isEmpty()
+ && newCards.first()->rank() == KCardDeck::King
+- && newCards.last()->rank() == KCardDeck::Ace;
++ && newCards.last()->rank() == KCardDeck::Ace
++ && isSameSuitDescending(newCards);
+ }
+ }
+
+ bool Simon::checkRemove(const PatPile * pile, const QList<KCard*> & cards) const
+ {
+- return pile->pileRole() == PatPile::Tableau
+- && isSameSuitDescending(cards);
++ if (pile->pileRole() != PatPile::Tableau)
++ return false;
++
++ int seqs_count = countSameSuitDescendingSequences(cards);
++
++ return (seqs_count >= 0);
+ }
+
++QString Simon::solverFormat() const
++{
++ QString output;
++ QString tmp;
++ for (int i = 0; i < 4 ; i++) {
++ if (target[i]->isEmpty())
++ continue;
++ tmp += suitToString(target[i]->topCard()->suit()) + "-K ";
++ }
++ if (!tmp.isEmpty())
++ output += QString::fromLatin1("Foundations: %1\n").arg(tmp);
+
++ for (int i = 0; i < 10 ; i++)
++ {
++ QList<KCard*> cards = store[i]->cards();
++ for (QList<KCard*>::ConstIterator it = cards.begin(); it != cards.end(); ++it)
++ output += rankToString((*it)->rank()) + suitToString((*it)->suit()) + ' ';
++ output += '\n';
++ }
++ return output;
++}
+
+ static class SimonDealerInfo : public DealerInfo
+ {
+diff --git a/simon.h b/simon.h
+index 83d10ab..d816f27 100644
+--- a/simon.h
++++ b/simon.h
+@@ -57,6 +57,7 @@ private:
+ PatPile* store[10];
+ PatPile* target[4];
+
++ virtual QString solverFormat() const;
+ friend class SimonSolver;
+ };
+
+--
+cgit v0.11.2
+
diff --git a/kde/patch/krita.patch b/kde/patch/krita.patch
new file mode 100644
index 0000000..e1604ab
--- /dev/null
+++ b/kde/patch/krita.patch
@@ -0,0 +1,3 @@
+# Fix compilation of Krita against Qt 5.9:
+#cat $CWD/patch/krita/krita_qt59.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/krita/krita_qt59.patch b/kde/patch/krita/krita_qt59.patch
new file mode 100644
index 0000000..f517995
--- /dev/null
+++ b/kde/patch/krita/krita_qt59.patch
@@ -0,0 +1,26 @@
+Source: https://github.com/KDE/krita/commit/2f59d0d1.patch
+
+From 2f59d0d1d91e3f79342c20d0df68aa9a51817e8d Mon Sep 17 00:00:00 2001
+From: Luca Beltrame <lbeltrame@kde.org>
+Date: Sat, 6 May 2017 16:00:21 +0200
+Subject: [PATCH] Drop QForeachContainer include and fix Qt 5.9 build
+
+It's not used anywhere.
+
+Acked by boud.
+---
+ libs/ui/KisResourceBundleManifest.cpp | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/libs/ui/KisResourceBundleManifest.cpp b/libs/ui/KisResourceBundleManifest.cpp
+index e4f94ca788..83761657d8 100644
+--- a/libs/ui/KisResourceBundleManifest.cpp
++++ b/libs/ui/KisResourceBundleManifest.cpp
+@@ -22,7 +22,6 @@
+ #include <QDomElement>
+ #include <QDomNode>
+ #include <QDomNodeList>
+-#include <QForeachContainer>
+
+ #include <KoXmlNS.h>
+ #include <KoXmlReader.h>
diff --git a/kde/patch/ksudoku.patch b/kde/patch/ksudoku.patch
new file mode 100644
index 0000000..d379585
--- /dev/null
+++ b/kde/patch/ksudoku.patch
@@ -0,0 +1,4 @@
+# Qt5 apps accept qwindowtitle, not caption. KDEBUG 381087,
+# however ksudoku-17.04.x is still kdelibs4 based:
+#cat $CWD/patch/ksudoku/ksudoku_qwindowtitle.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/ksudoku/ksudoku_qwindowtitle.patch b/kde/patch/ksudoku/ksudoku_qwindowtitle.patch
new file mode 100644
index 0000000..3417b34
--- /dev/null
+++ b/kde/patch/ksudoku/ksudoku_qwindowtitle.patch
@@ -0,0 +1,15 @@
+Qt5 .desktop files for apps accept qwindowtitle, not caption.
+However, the 17.04 branch of ksudoku is still kdelibs4 based, and the
+patch applied to KDE BUG 381087 broke the launch of ksudoku here:
+
+--- a/src/gui/org.kde.ksudoku.desktop 2017-06-13 02:44:11.000000000 +0200
++++ b/src/gui/org.kde.ksudoku.desktop 2017-07-15 11:58:32.027477614 +0200
+@@ -50,7 +50,7 @@
+ Name[x-test]=xxKSudokuxx
+ Name[zh_CN]=KSudoku
+ Name[zh_TW]=KSudoku
+-Exec=ksudoku %i -qwindowtitle %c
++Exec=ksudoku %i -caption %c
+ Icon=ksudoku
+ Type=Application
+ X-DocPath=ksudoku/index.html
diff --git a/kde/patch/ktexteditor.patch b/kde/patch/ktexteditor.patch
new file mode 100644
index 0000000..a4f0aad
--- /dev/null
+++ b/kde/patch/ktexteditor.patch
@@ -0,0 +1,3 @@
+# Fix indentation for some languages (e.g. Python) in Kate and KDevelop:
+#cat $CWD/patch/ktexteditor/ktexteditor_fix_indentation.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/ktexteditor/ktexteditor_fix_indentation.patch b/kde/patch/ktexteditor/ktexteditor_fix_indentation.patch
new file mode 100644
index 0000000..fc5d9d9
--- /dev/null
+++ b/kde/patch/ktexteditor/ktexteditor_fix_indentation.patch
@@ -0,0 +1,32 @@
+From aeebeadb5f5955995c17de56cf83ba7166a132dd Mon Sep 17 00:00:00 2001
+From: Sven Brauch <mail@svenbrauch.de>
+Date: Mon, 16 Oct 2017 18:35:50 +0200
+Subject: fix some indenters from indenting on random characters
+
+If triggerCharacters was not set, toString() would return "undefined",
+making indenters trigger on u, n, d, e, f, i and n.
+
+Differential Revision: https://phabricator.kde.org/D8333
+---
+ src/script/kateindentscript.cpp | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/src/script/kateindentscript.cpp b/src/script/kateindentscript.cpp
+index 15ce387..380bd45 100644
+--- a/src/script/kateindentscript.cpp
++++ b/src/script/kateindentscript.cpp
+@@ -46,7 +46,10 @@ const QString &KateIndentScript::triggerCharacters()
+
+ m_triggerCharactersSet = true;
+
+- m_triggerCharacters = global(QStringLiteral("triggerCharacters")).toString();
++ auto triggerCharacters = global(QStringLiteral("triggerCharacters"));
++ if ( !triggerCharacters.isUndefined() ) {
++ m_triggerCharacters = triggerCharacters.toString();
++ }
+
+ //qCDebug(LOG_KTE) << "trigger chars: '" << m_triggerCharacters << "'";
+
+--
+cgit v0.11.2
+
diff --git a/kde/patch/kwin.patch b/kde/patch/kwin.patch
new file mode 100644
index 0000000..9f05f30
--- /dev/null
+++ b/kde/patch/kwin.patch
@@ -0,0 +1,11 @@
+# Trivial patch for testing the CK2 session controller interface
+# as a replacement for systemd-logind;
+#cat $CWD/patch/kwin/kwin_replace_logind_with_ck2.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Workaround Qt regression no longer delivering events for the root window.
+# Fixed in kwin 5.10.3.
+#cat $CWD/patch/kwin/kwin_qt59_rootwindow_events.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Fix compilation with cmake 3.10 (fixed in 5.12.0):
+#cat $CWD/patch/kwin/kwin_cmake310.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/kwin/kwin_cmake310.patch b/kde/patch/kwin/kwin_cmake310.patch
new file mode 100644
index 0000000..5675ba5
--- /dev/null
+++ b/kde/patch/kwin/kwin_cmake310.patch
@@ -0,0 +1,52 @@
+Taken from:
+https://gitweb.gentoo.org/repo/gentoo.git/tree/kde-plasma/kwin/files/kwin-5.11.5-cmake-3.10.patch
+
+From cd544890ced4192d07467c89e23adbb62d8cea5c Mon Sep 17 00:00:00 2001
+From: Milian Wolff <mail@milianw.de>
+Date: Mon, 18 Dec 2017 11:40:35 +0100
+Subject: Fix build with CMake 3.10
+
+Looks like a classic false-positive, but this makes the compile
+pass for me without making the code harder to read:
+
+AutoMoc error
+-------------
+ "/ssd/milian/projects/kf5/src/kde/workspace/kwin/kcmkwin/kwinscripts/main.cpp"
+The file contains a K_PLUGIN_FACTORY macro, but does not include "main.moc"!
+Consider to
+ - add #include "main.moc"
+ - enable SKIP_AUTOMOC for this file
+
+So we just add the include and then get rid of the duplicate
+definition of the plugin factory and the problem is resolved.
+---
+ kcmkwin/kwinscripts/main.cpp | 2 ++
+ kcmkwin/kwinscripts/module.cpp | 2 --
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/kcmkwin/kwinscripts/main.cpp b/kcmkwin/kwinscripts/main.cpp
+index f5ee04b..baa5175 100644
+--- a/kcmkwin/kwinscripts/main.cpp
++++ b/kcmkwin/kwinscripts/main.cpp
+@@ -22,3 +22,5 @@
+
+ K_PLUGIN_FACTORY(KcmKWinScriptsFactory,
+ registerPlugin<Module>("kwin-scripts");)
++
++#include "main.moc"
+diff --git a/kcmkwin/kwinscripts/module.cpp b/kcmkwin/kwinscripts/module.cpp
+index a0d698e..ccf7d41 100644
+--- a/kcmkwin/kwinscripts/module.cpp
++++ b/kcmkwin/kwinscripts/module.cpp
+@@ -40,8 +40,6 @@
+
+ #include "version.h"
+
+-K_PLUGIN_FACTORY_DECLARATION(KcmKWinScriptsFactory)
+-
+ Module::Module(QWidget *parent, const QVariantList &args) :
+ KCModule(parent, args),
+ ui(new Ui::Module),
+--
+cgit v0.11.2
+
diff --git a/kde/patch/kwin/kwin_qt59_rootwindow_events.patch b/kde/patch/kwin/kwin_qt59_rootwindow_events.patch
new file mode 100644
index 0000000..178d081
--- /dev/null
+++ b/kde/patch/kwin/kwin_qt59_rootwindow_events.patch
@@ -0,0 +1,63 @@
+From a6dee74ee455d1da47dd5c9d55a84adbb5e1426a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Martin=20Fl=C3=B6ser?= <mgraesslin@kde.org>
+Date: Sun, 18 Jun 2017 14:23:33 +0200
+Subject: Workaround Qt regression of no longer delivering events for the root
+ window
+
+Summary:
+With qtbase 2b34aefcf02f09253473b096eb4faffd3e62b5f4 we do no longer get
+events reported for the X11 root window. Our keyboard handling in effects
+like PresentWindows and DesktopGrid relied on that.
+
+This change works around the regression by calling winId() on
+qApp->desktop() as suggested in the change. This is a short term solution
+for the 5.10 branch.
+
+This needs to be addressed properly by no longer relying on Qt in this
+area. KWin already does not rely on Qt for Wayland in that area and is
+able to compose the QKeyEvents. This should also be done on X11. It just
+needs some more hook up code for xkb, but that's needed anyway to improve
+modifier only shortcuts and friends.
+
+BUG: 360841
+FIXED-IN: 5.10.3
+
+Reviewers: #kwin, #plasma
+
+Subscribers: plasma-devel, kwin
+
+Tags: #kwin
+
+Differential Revision: https://phabricator.kde.org/D6258
+---
+ effects.cpp | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/effects.cpp b/effects.cpp
+index d2c4768..8155de6 100644
+--- a/effects.cpp
++++ b/effects.cpp
+@@ -48,6 +48,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ #include "kwinglutils.h"
+
+ #include <QDebug>
++#include <QDesktopWidget>
+
+ #include <Plasma/Theme>
+
+@@ -599,6 +600,11 @@ bool EffectsHandlerImpl::grabKeyboard(Effect* effect)
+ bool ret = grabXKeyboard();
+ if (!ret)
+ return false;
++ // Workaround for Qt 5.9 regression introduced with 2b34aefcf02f09253473b096eb4faffd3e62b5f4
++ // we no longer get any events for the root window, one needs to call winId() on the desktop window
++ // TODO: change effects event handling to create the appropriate QKeyEvent without relying on Qt
++ // as it's done already in the Wayland case.
++ qApp->desktop()->winId();
+ }
+ keyboard_grab_effect = effect;
+ return true;
+--
+cgit v0.11.2
+
+
diff --git a/kde/patch/kwin/kwin_replace_logind_with_ck2.patch b/kde/patch/kwin/kwin_replace_logind_with_ck2.patch
new file mode 100644
index 0000000..294a691
--- /dev/null
+++ b/kde/patch/kwin/kwin_replace_logind_with_ck2.patch
@@ -0,0 +1,85 @@
+From: Eric Koegel <eric.koegel@gmail.com>
+Date: Sun, 24 Jul 2016 14:37:26 +0300
+
+Trivial patch for testing the CK2 session controller interface
+as a replacement for systemd-logind;
+Eric Koegel's original patch was rebased to kwin-5.10.2 by Eric Hameleers
+
+diff -uar kwin-5.10.2.orig/logind.cpp kwin-5.10.2/logind.cpp
+--- kwin-5.10.2.orig/logind.cpp 2017-06-13 20:19:37.000000000 +0200
++++ kwin-5.10.2/logind.cpp 2017-06-18 20:49:56.018661568 +0200
+@@ -58,10 +58,10 @@
+ namespace KWin
+ {
+
+-const static QString s_login1Service = QStringLiteral("org.freedesktop.login1");
+-const static QString s_login1Path = QStringLiteral("/org/freedesktop/login1");
+-const static QString s_login1ManagerInterface = QStringLiteral("org.freedesktop.login1.Manager");
+-const static QString s_login1SessionInterface = QStringLiteral("org.freedesktop.login1.Session");
++const static QString s_login1Service = QStringLiteral("org.freedesktop.ConsoleKit");
++const static QString s_login1Path = QStringLiteral("/org/freedesktop/ConsoleKit/Manager");
++const static QString s_login1ManagerInterface = QStringLiteral("org.freedesktop.ConsoleKit.Manager");
++const static QString s_login1SessionInterface = QStringLiteral("org.freedesktop.ConsoleKit.Session");
+ const static QString s_dbusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
+
+ LogindIntegration *LogindIntegration::s_self = nullptr;
+@@ -151,7 +151,7 @@
+ return;
+ }
+ if (!reply.isValid()) {
+- qCDebug(KWIN_CORE) << "The session is not registered with logind" << reply.error().message();
++ qCDebug(KWIN_CORE) << "The session is not registered with ConsoleKit2" << reply.error().message();
+ return;
+ }
+ m_sessionPath = reply.value().path();
+@@ -199,7 +199,7 @@
+ m_sessionPath,
+ s_dbusPropertiesInterface,
+ QStringLiteral("Get"));
+- message.setArguments(QVariantList({s_login1SessionInterface, QStringLiteral("Active")}));
++ message.setArguments(QVariantList({s_login1SessionInterface, QStringLiteral("active")}));
+ QDBusPendingReply<QVariant> reply = m_bus.asyncCall(message);
+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
+ connect(watcher, &QDBusPendingCallWatcher::finished, this,
+@@ -207,7 +207,7 @@
+ QDBusPendingReply<QVariant> reply = *self;
+ self->deleteLater();
+ if (!reply.isValid()) {
+- qCDebug(KWIN_CORE) << "Failed to get Active Property of logind session:" << reply.error().message();
++ qCDebug(KWIN_CORE) << "Failed to get Active Property of ConsoleKit2 session:" << reply.error().message();
+ return;
+ }
+ const bool active = reply.value().toBool();
+@@ -236,7 +236,7 @@
+ QDBusPendingReply<QVariant> reply = *self;
+ self->deleteLater();
+ if (!reply.isValid()) {
+- qCDebug(KWIN_CORE) << "Failed to get VTNr Property of logind session:" << reply.error().message();
++ qCDebug(KWIN_CORE) << "Failed to get VTNr Property of ConsoleKit2 session:" << reply.error().message();
+ return;
+ }
+ const int vt = reply.value().toUInt();
+@@ -365,12 +365,12 @@
+ QDBusPendingReply<QVariant> reply = *self;
+ self->deleteLater();
+ if (!reply.isValid()) {
+- qCDebug(KWIN_CORE) << "Failed to get Seat Property of logind session:" << reply.error().message();
++ qCDebug(KWIN_CORE) << "Failed to get Seat Property of ConsoleKit2 session:" << reply.error().message();
+ return;
+ }
+ DBusLogindSeat seat = qdbus_cast<DBusLogindSeat>(reply.value().value<QDBusArgument>());
+ const QString seatPath = seat.path.path();
+- qCDebug(KWIN_CORE) << "Logind seat:" << seat.name << "/" << seatPath;
++ qCDebug(KWIN_CORE) << "ConsoleKit2 seat:" << seat.name << "/" << seatPath;
+ if (m_seatPath != seatPath) {
+ m_seatPath = seatPath;
+ }
+@@ -385,7 +385,7 @@
+ }
+ QDBusMessage message = QDBusMessage::createMethodCall(s_login1Service,
+ m_seatPath,
+- QStringLiteral("org.freedesktop.login1.Seat"),
++ QStringLiteral("org.freedesktop.ConsoleKit.Seat"),
+ QStringLiteral("SwitchTo"));
+ message.setArguments(QVariantList{vtNr});
+ m_bus.asyncCall(message);
diff --git a/kde/patch/libkface.patch b/kde/patch/libkface.patch
new file mode 100644
index 0000000..9775e82
--- /dev/null
+++ b/kde/patch/libkface.patch
@@ -0,0 +1,3 @@
+# Compile libkface against opencv 3.2:
+cat $CWD/patch/libkface/libkface_opencv3.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/libkface/libkface_opencv3.patch b/kde/patch/libkface/libkface_opencv3.patch
new file mode 100644
index 0000000..a6804d3
--- /dev/null
+++ b/kde/patch/libkface/libkface_opencv3.patch
@@ -0,0 +1,61 @@
+Taken from Gentoo:
+https://gitweb.gentoo.org/repo/gentoo.git/plain/kde-apps/libkface/files/libkface-16.11.80-opencv3.2-gentoo-3.1.patch
+
+--- a/src/recognition-opencv-lbph/facerec_borrowed.h 2016-11-26 14:19:01.492645170 +0100
++++ b/src/recognition-opencv-lbph/facerec_borrowed.h.new 2016-11-26 14:19:17.655835794 +0100
+@@ -141,7 +141,7 @@
+ /*
+ * Predict
+ */
+- void predict(cv::InputArray src, cv::Ptr<cv::face::PredictCollector> collector, const int state = 0) const override;
++ void predict(cv::InputArray src, cv::Ptr<cv::face::PredictCollector> collector) const override;
+ #endif
+
+ /**
+--- a/src/recognition-opencv-lbph/facerec_borrowed.cpp 2016-11-26 14:19:01.492645170 +0100
++++ b/src/recognition-opencv-lbph/facerec_borrowed.cpp.new 2016-11-26 14:19:29.184971765 +0100
+@@ -380,7 +380,7 @@
+ #if OPENCV_TEST_VERSION(3,1,0)
+ void LBPHFaceRecognizer::predict(InputArray _src, int &minClass, double &minDist) const
+ #else
+-void LBPHFaceRecognizer::predict(cv::InputArray _src, cv::Ptr<cv::face::PredictCollector> collector, const int state) const
++void LBPHFaceRecognizer::predict(cv::InputArray _src, cv::Ptr<cv::face::PredictCollector> collector) const
+ #endif
+ {
+ if(m_histograms.empty())
+@@ -404,7 +404,7 @@
+ minDist = DBL_MAX;
+ minClass = -1;
+ #else
+- collector->init((int)m_histograms.size(), state);
++ collector->init((int)m_histograms.size());
+ #endif
+
+ // This is the standard method
+@@ -424,7 +424,7 @@
+ }
+ #else
+ int label = m_labels.at<int>((int) sampleIdx);
+- if (!collector->emit(label, dist, state))
++ if (!collector->collect(label, dist))
+ {
+ return;
+ }
+@@ -470,7 +470,7 @@
+ minClass = it->first;
+ }
+ #else
+- if (!collector->emit(it->first, mean, state))
++ if (!collector->collect(it->first, mean))
+ {
+ return;
+ }
+@@ -523,7 +523,7 @@
+ }
+ #else
+ // large is better thus it is -score.
+- if (!collector->emit(it->first, -score, state))
++ if (!collector->collect(it->first, -score))
+ {
+ return;
+ }
diff --git a/kde/patch/libkleo.patch b/kde/patch/libkleo.patch
new file mode 100644
index 0000000..9c1cef3
--- /dev/null
+++ b/kde/patch/libkleo.patch
@@ -0,0 +1,4 @@
+# Compile libkleo with gcc7:
+# Fixed in Applications 17.04.2:
+#cat $CWD/patch/libkleo/libkleo_gcc7.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/libkleo/libkleo_gcc7.patch b/kde/patch/libkleo/libkleo_gcc7.patch
new file mode 100644
index 0000000..40e0616
--- /dev/null
+++ b/kde/patch/libkleo/libkleo_gcc7.patch
@@ -0,0 +1,27 @@
+From 675ce908a33d16f3b78d3fc741b0ff45790e4770 Mon Sep 17 00:00:00 2001
+From: Fabian Vogt <fabian@ritter-vogt.de>
+Date: Wed, 17 May 2017 17:05:41 +0200
+Subject: Fix compilation with GCC 7
+
+std::bind is part of functional, and GCC 7 requires an explicit include.
+---
+ src/kleo/enum.cpp | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/kleo/enum.cpp b/src/kleo/enum.cpp
+index 15ea1fd..829d4fd 100644
+--- a/src/kleo/enum.cpp
++++ b/src/kleo/enum.cpp
+@@ -33,6 +33,9 @@
+ #include "enum.h"
+ #include "libkleo_debug.h"
+ #include "models/keycache.h"
++
++#include <functional>
++
+ #include <KLocalizedString>
+
+ #include <gpgme++/key.h>
+--
+cgit v0.11.2
+
diff --git a/kde/patch/oxygen-gtk2.patch b/kde/patch/oxygen-gtk2.patch
new file mode 100644
index 0000000..4ca11e9
--- /dev/null
+++ b/kde/patch/oxygen-gtk2.patch
@@ -0,0 +1,3 @@
+# Add more firefox-derived browsers as supported by the theme:
+cat $CWD/patch/oxygen-gtk2/oxygen-gtk2_KDEBUG_341181.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/oxygen-gtk2/oxygen-gtk2_KDEBUG_341181.patch b/kde/patch/oxygen-gtk2/oxygen-gtk2_KDEBUG_341181.patch
new file mode 100644
index 0000000..b6a1e55
--- /dev/null
+++ b/kde/patch/oxygen-gtk2/oxygen-gtk2_KDEBUG_341181.patch
@@ -0,0 +1,115 @@
+From b1ee5fb80c44c6c8a625333af1cfdc997d408805 Mon Sep 17 00:00:00 2001
+From: Hugo Pereira Da Costa <hugo.pereira@free.fr>
+Date: Sat, 18 Jul 2015 20:09:28 +0200
+Subject: moved xul application names to dedicated header file, added a number
+ of xul applications to prevent crash for these CCBUG: 341181
+
+---
+ src/oxygenapplicationname.cpp | 18 ++-----------
+ src/oxygenxulapplicationnames.h | 56 +++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 58 insertions(+), 16 deletions(-)
+ create mode 100644 src/oxygenxulapplicationnames.h
+
+diff --git a/src/oxygenapplicationname.cpp b/src/oxygenapplicationname.cpp
+index feb5a23..17c8a1a 100644
+--- a/src/oxygenapplicationname.cpp
++++ b/src/oxygenapplicationname.cpp
+@@ -25,6 +25,7 @@
+
+ #include "oxygenapplicationname.h"
+ #include "oxygengtkutils.h"
++#include "oxygenxulapplicationnames.h"
+ #include "config.h"
+
+ #include <cstdlib>
+@@ -79,23 +80,8 @@ namespace Oxygen
+ gtkAppName == "chromium" ||
+ gtkAppName == "chromium-browser" ||
+ gtkAppName == "google-chrome" ) _name = GoogleChrome;
+- else {
+
+- // tag all mozilla-like applications (XUL)
+- static const std::string XulAppNames[] =
+- {
+- "firefox",
+- "thunderbird",
+- "seamonkey",
+- "iceweasel",
+- "icecat",
+- "icedove",
+- "xulrunner",
+- "komodo",
+- "aurora",
+- "zotero",
+- ""
+- };
++ else {
+
+ for( unsigned int index = 0; !XulAppNames[index].empty(); ++index )
+ {
+diff --git a/src/oxygenxulapplicationnames.h b/src/oxygenxulapplicationnames.h
+new file mode 100644
+index 0000000..252a1fc
+--- /dev/null
++++ b/src/oxygenxulapplicationnames.h
+@@ -0,0 +1,56 @@
++#ifndef oxygenxulapplicationname_h
++#define oxygenxulapplicationname_h
++/*
++* this file is part of the oxygen gtk engine
++* Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
++*
++* inspired notably from kdelibs/kdeui/color/kcolorutils.h
++* Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
++* Copyright (C) 2007 Thomas Zander <zander@kde.org>
++* Copyright (C) 2007 Zack Rusin <zack@kde.org>
++*
++* This library is free software; you can redistribute it and/or
++* modify it under the terms of the GNU Lesser General Public
++* License as published by the Free Software Foundation; either
++* version 2 of the License, or( at your option ) any later version.
++*
++* This library 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
++* Lesser General Public License for more details.
++*
++* You should have received a copy of the GNU Lesser General Public
++* License along with this library; if not, write to the Free
++* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
++* MA 02110-1301, USA.
++*/
++
++#include <string>
++
++namespace Oxygen
++{
++
++ // tag all mozilla-like applications (XUL)
++ static const std::string XulAppNames[] =
++ {
++ "aurora",
++ "earlybird",
++ "icecat",
++ "icedove",
++ "iceweasel",
++ "instantbird",
++ "firefox",
++ "fossamail",
++ "komodo",
++ "newmoon",
++ "palemoon",
++ "seamonkey",
++ "thunderbird",
++ "xulrunner",
++ "zotero",
++ ""
++ };
++
++}
++
++#endif
+--
+cgit v0.11.2
+
+
diff --git a/kde/patch/perlqt.patch b/kde/patch/perlqt.patch
new file mode 100644
index 0000000..1cdd7db
--- /dev/null
+++ b/kde/patch/perlqt.patch
@@ -0,0 +1,3 @@
+# Fix build:
+cat $CWD/patch/perlqt/perlqt.gcc6.diff | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/perlqt/perlqt.gcc6.diff b/kde/patch/perlqt/perlqt.gcc6.diff
new file mode 100644
index 0000000..9a6e42e
--- /dev/null
+++ b/kde/patch/perlqt/perlqt.gcc6.diff
@@ -0,0 +1,11 @@
+--- ./qtcore/src/util.cpp.orig 2014-11-04 16:59:39.000000000 -0600
++++ ./qtcore/src/util.cpp 2017-10-04 22:25:36.055839800 -0500
+@@ -2251,7 +2251,7 @@
+ methcache.insert(mcid, new Smoke::ModuleIndex(mi));
+ }
+
+- static smokeperl_object nothis = { 0, 0, 0, false };
++ static smokeperl_object nothis = { 0, 0, 0, NULL };
+ smokeperl_object* call_this = 0;
+ if ( SvOK(sv_this) ) {
+ call_this = sv_obj_info( sv_this );
diff --git a/kde/patch/plasma-workspace.patch b/kde/patch/plasma-workspace.patch
index c1e56fc..8671c65 100644
--- a/kde/patch/plasma-workspace.patch
+++ b/kde/patch/plasma-workspace.patch
@@ -4,5 +4,14 @@
#cat $CWD/patch/plasma-workspace/plasma-workspace_consolekit2.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
# Apply commit that fixes compilation of 5.6.5:
-#cat $CWD/patch/plasma-workspace/plasma-workspace_apply_767aa57.patc | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+#cat $CWD/patch/plasma-workspace/plasma-workspace_apply_767aa57.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Systray: Move all icon resolution to dataengine, preventing high CPU usage,
+# see https://phabricator.kde.org/D2986 :
+# Fixed in 5.9.0
+#cat $CWD/patch/plasma-workspace/plasma-workspace.systray_cpubug.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Fix vulnerability (CVE-2018-6791 - KDEBUG_389815)
+# (already fixed in Plasma 5.12.0):
+#cat $CWD/patch/plasma-workspace/plasma-workspace_kdebug389815.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
diff --git a/kde/patch/plasma-workspace/plasma-workspace.systray_cpubug.patch b/kde/patch/plasma-workspace/plasma-workspace.systray_cpubug.patch
new file mode 100644
index 0000000..4ad3c07
--- /dev/null
+++ b/kde/patch/plasma-workspace/plasma-workspace.systray_cpubug.patch
@@ -0,0 +1,152 @@
+https://phabricator.kde.org/D2986
+Systray: Move all icon resolution to dataengine
+
+diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml
+--- a/applets/systemtray/package/contents/ui/ConfigEntries.qml
++++ b/applets/systemtray/package/contents/ui/ConfigEntries.qml
+@@ -75,7 +75,7 @@
+ "index": i,
+ "taskId": item.Id,
+ "name": item.Title,
+- "iconName": plasmoid.nativeInterface.resolveIcon(item.IconName, item.IconThemePath),
++ "iconName": item.IconName,
+ "icon": item.Icon
+ });
+ }
+diff --git a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml
+--- a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml
++++ b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml
+@@ -28,7 +28,7 @@
+ text: Title
+ mainText: ToolTipTitle != "" ? ToolTipTitle : Title
+ subText: ToolTipSubTitle
+- icon: ToolTipIcon != "" ? ToolTipIcon : plasmoid.nativeInterface.resolveIcon(IconName != "" ? IconName : Icon, IconThemePath)
++ icon: ToolTipIcon != "" ? ToolTipIcon : Icon ? Icon : IconName
+ textFormat: Text.AutoText
+ category: Category
+
+@@ -48,7 +48,7 @@
+
+ PlasmaCore.IconItem {
+ id: iconItem
+- source: plasmoid.nativeInterface.resolveIcon(IconName != "" ? IconName : Icon, IconThemePath)
++ source: Icon ? Icon : IconName
+ width: Math.min(parent.width, parent.height)
+ height: width
+ active: taskIcon.containsMouse
+diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h
+--- a/applets/systemtray/systemtray.h
++++ b/applets/systemtray/systemtray.h
+@@ -60,12 +60,6 @@
+
+ //Invokable utilities
+ /**
+- * returns either a simple icon name or a custom path if the app is
+- * using a custom theme
+- */
+- Q_INVOKABLE QVariant resolveIcon(const QVariant &variant, const QString &iconThemePath);
+-
+- /**
+ * Given an AppletInterface pointer, shows a proper context menu for it
+ */
+ Q_INVOKABLE void showPlasmoidMenu(QQuickItem *appletInterface, int x, int y);
+diff --git a/applets/systemtray/systemtray.cpp b/applets/systemtray/systemtray.cpp
+--- a/applets/systemtray/systemtray.cpp
++++ b/applets/systemtray/systemtray.cpp
+@@ -37,37 +37,11 @@
+ #include <Plasma/PluginLoader>
+ #include <Plasma/ServiceJob>
+
+-#include <KIconLoader>
+-#include <KIconEngine>
+ #include <KActionCollection>
+ #include <KLocalizedString>
+
+ #include <plasma_version.h>
+
+-/*
+- * An app may also load icons from their own directories, so we need a new iconloader that takes this into account
+- * This is wrapped into a subclass of iconengine so the iconloader lifespan matches the icon object
+- */
+-class AppIconEngine : public KIconEngine
+-{
+-public:
+- AppIconEngine(const QString &variant, const QString &path, const QString &appName);
+- ~AppIconEngine();
+-private:
+- KIconLoader* m_loader;
+-};
+-
+-AppIconEngine::AppIconEngine(const QString &variant, const QString &path, const QString &appName) :
+- KIconEngine(variant, m_loader = new KIconLoader(appName, QStringList()))
+-{
+- m_loader->addAppDir(appName, path);
+-}
+-
+-AppIconEngine::~AppIconEngine()
+-{
+- delete m_loader;
+-}
+-
+ class PlasmoidModel: public QStandardItemModel
+ {
+ public:
+@@ -169,32 +143,6 @@
+ }
+ }
+
+-QVariant SystemTray::resolveIcon(const QVariant &variant, const QString &iconThemePath)
+-{
+- if (variant.canConvert<QString>()) {
+- if (!iconThemePath.isEmpty()) {
+- const QString path = iconThemePath;
+- if (!path.isEmpty()) {
+- // FIXME: If last part of path is not "icons", this won't work!
+- auto tokens = path.splitRef('/', QString::SkipEmptyParts);
+- if (tokens.length() >= 3 && tokens.takeLast() == QLatin1String("icons")) {
+- const QString appName = tokens.takeLast().toString();
+-
+- return QVariant(QIcon(new AppIconEngine(variant.toString(), path, appName)));
+- } else {
+- qCWarning(SYSTEM_TRAY) << "Wrong IconThemePath" << path << ": too short or does not end with 'icons'";
+- }
+- }
+-
+- //return just the string hoping that IconItem will know how to interpret it anyways as either a normal icon or a SVG from the theme
+- return variant;
+- }
+- }
+-
+- // Most importantly QIcons. Nothing to do for those.
+- return variant;
+-}
+-
+ void SystemTray::showPlasmoidMenu(QQuickItem *appletInterface, int x, int y)
+ {
+ if (!appletInterface) {
+diff --git a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp
+--- a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp
++++ b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp
+@@ -240,14 +240,19 @@
+ if (!m_customIconLoader) {
+ m_customIconLoader = new KIconLoader(QString(), QStringList(), this);
+ }
++ // FIXME: If last part of path is not "icons", this won't work!
++ QString appName;
++ auto tokens = path.splitRef('/', QString::SkipEmptyParts);
++ if (tokens.length() >= 3 && tokens.takeLast() == QLatin1String("icons"))
++ appName = tokens.takeLast().toString();
+
+ //icons may be either in the root directory of the passed path or in a appdir format
+ //i.e hicolor/32x32/iconname.png
+
+- m_customIconLoader->reconfigure(QString(), QStringList(path));
++ m_customIconLoader->reconfigure(appName, QStringList(path));
+
+ //add app dir requires an app name, though this is completely unused in this context
+- m_customIconLoader->addAppDir(QStringLiteral("unused"), path);
++ m_customIconLoader->addAppDir(appName.size() ? appName : QStringLiteral("unused"), path);
+ }
+ setData(QStringLiteral("IconThemePath"), path);
+
+
diff --git a/kde/patch/plasma-workspace/plasma-workspace_kdebug389815.patch b/kde/patch/plasma-workspace/plasma-workspace_kdebug389815.patch
new file mode 100644
index 0000000..e2f1e48
--- /dev/null
+++ b/kde/patch/plasma-workspace/plasma-workspace_kdebug389815.patch
@@ -0,0 +1,32 @@
+From f32002ce50edc3891f1fa41173132c820b917d57 Mon Sep 17 00:00:00 2001
+From: Marco Martin <notmart@gmail.com>
+Date: Mon, 5 Feb 2018 13:12:51 +0100
+Subject: Make sure device paths are quoted
+
+in the case a vfat removable device has $() or `` in its label,
+such as $(touch foo) the quoted command may get executed,
+leaving an attack vector. Use KMacroExpander::expandMacrosShellQuote
+to make sure everything is quoted and not interpreted as a command
+
+BUG:389815
+---
+ soliduiserver/deviceserviceaction.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/soliduiserver/deviceserviceaction.cpp b/soliduiserver/deviceserviceaction.cpp
+index f49c967..738b27c 100644
+--- a/soliduiserver/deviceserviceaction.cpp
++++ b/soliduiserver/deviceserviceaction.cpp
+@@ -158,7 +158,7 @@ void DelayedExecutor::delayedExecute(const QString &udi)
+
+ QString exec = m_service.exec();
+ MacroExpander mx(device);
+- mx.expandMacros(exec);
++ mx.expandMacrosShellQuote(exec);
+
+ KRun::runCommand(exec, QString(), m_service.icon(), 0);
+ deleteLater();
+--
+cgit v0.11.2
+
+
diff --git a/kde/patch/powerdevil.patch b/kde/patch/powerdevil.patch
new file mode 100644
index 0000000..b7e2bae
--- /dev/null
+++ b/kde/patch/powerdevil.patch
@@ -0,0 +1,4 @@
+# PowerDevil fails to setup power settings on a fresh install.
+# Fixed in 5.12.5.
+#cat $CWD/patch/powerdevil/powerdevil-5.12.4_firstrun.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/powerdevil/powerdevil-5.12.4_firstrun.patch b/kde/patch/powerdevil/powerdevil-5.12.4_firstrun.patch
new file mode 100644
index 0000000..165e67b
--- /dev/null
+++ b/kde/patch/powerdevil/powerdevil-5.12.4_firstrun.patch
@@ -0,0 +1,42 @@
+From be91abe7fc8cc731b57bec4cf2c004c07b0fd79b Mon Sep 17 00:00:00 2001
+From: Kai Uwe Broulik <kde@privat.broulik.de>
+Date: Wed, 25 Apr 2018 10:56:16 +0200
+Subject: Ignore "migration" key for determining whether the config is empty
+
+Otherwise we would never generate the default power management configuration
+leading to it not suspending on lid close, not handling power buttons etc etc
+
+CHANGELOG: Fixed bug that caused power management system to not work on a fresh install
+
+Reviewed-By: David Edmundson
+
+BUG: 391782
+FIXED-IN: 5.12.5
+---
+ daemon/powerdevilcore.cpp | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/daemon/powerdevilcore.cpp b/daemon/powerdevilcore.cpp
+index 2cf936e..53b7521 100644
+--- a/daemon/powerdevilcore.cpp
++++ b/daemon/powerdevilcore.cpp
+@@ -108,9 +108,15 @@ void Core::onBackendReady()
+
+ m_profilesConfig = KSharedConfig::openConfig("powermanagementprofilesrc", KConfig::CascadeConfig);
+
++ QStringList groups = m_profilesConfig->groupList();
++ // the "migration" key is for shortcuts migration in added by migratePre512KeyboardShortcuts
++ // and as such our configuration would never be considered empty, ignore it!
++ groups.removeOne(QStringLiteral("migration"));
++
+ // Is it brand new?
+- if (m_profilesConfig->groupList().isEmpty()) {
++ if (groups.isEmpty()) {
+ // Generate defaults
++ qCDebug(POWERDEVIL) << "Generating a default configuration";
+ bool toRam = m_backend->supportedSuspendMethods() & PowerDevil::BackendInterface::ToRam;
+ bool toDisk = m_backend->supportedSuspendMethods() & PowerDevil::BackendInterface::ToDisk;
+ ProfileGenerator::generateProfiles(toRam, toDisk);
+--
+cgit v0.11.2
+
diff --git a/kde/patch/pykde4.patch b/kde/patch/pykde4.patch
new file mode 100644
index 0000000..48073d4
--- /dev/null
+++ b/kde/patch/pykde4.patch
@@ -0,0 +1,5 @@
+# Fix compilation against sip-4.19:
+cat $CWD/patch/pykde4/0001-use-LIB_PYTHON-realpath.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+cat $CWD/patch/pykde4/0002-Add-some-missing-link-libraries.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+cat $CWD/patch/pykde4/0003-Fix-build-with-sip-4.19.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
diff --git a/kde/patch/pykde4/0001-use-LIB_PYTHON-realpath.patch b/kde/patch/pykde4/0001-use-LIB_PYTHON-realpath.patch
new file mode 100644
index 0000000..85ad4bc
--- /dev/null
+++ b/kde/patch/pykde4/0001-use-LIB_PYTHON-realpath.patch
@@ -0,0 +1,31 @@
+From 34bed3ceb7cd2bb43e67acce97f4cc3e8bbc1c1d Mon Sep 17 00:00:00 2001
+From: Rex Dieter <rdieter@math.unl.edu>
+Date: Tue, 11 Mar 2014 09:51:17 -0500
+Subject: [PATCH 1/3] use LIB_PYTHON realpath
+
+Use GET_FILENAME_COMPONENT( ... REALPATH). PYTHON_LIBRARY as returned
+by cmake, whose target is often a symlink. Some distro packaging
+reserves such library symlinks for -devel and not runtime.
+
+REVIEW: 116719
+---
+ kpythonpluginfactory/CMakeLists.txt | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/kpythonpluginfactory/CMakeLists.txt b/kpythonpluginfactory/CMakeLists.txt
+index c24160e..a777dac 100644
+--- a/kpythonpluginfactory/CMakeLists.txt
++++ b/kpythonpluginfactory/CMakeLists.txt
+@@ -3,7 +3,8 @@
+ set(kpythonpluginfactory_SRCS
+ kpythonpluginfactory.cpp)
+
+-GET_FILENAME_COMPONENT(LIB_PYTHON ${PYTHON_LIBRARIES} NAME)
++GET_FILENAME_COMPONENT(PYTHON_LIBRARY_REALPATH "${PYTHON_LIBRARY}" REALPATH)
++GET_FILENAME_COMPONENT(LIB_PYTHON ${PYTHON_LIBRARY_REALPATH} NAME)
+ ADD_DEFINITIONS(-DLIB_PYTHON="${LIB_PYTHON}")
+ ADD_DEFINITIONS(-DKDE_DEFAULT_DEBUG_AREA=15000)
+
+--
+2.9.3
+
diff --git a/kde/patch/pykde4/0002-Add-some-missing-link-libraries.patch b/kde/patch/pykde4/0002-Add-some-missing-link-libraries.patch
new file mode 100644
index 0000000..00283cc
--- /dev/null
+++ b/kde/patch/pykde4/0002-Add-some-missing-link-libraries.patch
@@ -0,0 +1,60 @@
+From b0137f694f946c7f10ac2863a71b4cdeda15eb87 Mon Sep 17 00:00:00 2001
+From: Wolfgang Bauer <wbauer@tmo.at>
+Date: Wed, 14 Sep 2016 23:54:40 +0200
+Subject: [PATCH 2/3] Add some missing(?) link libraries
+
+This fixes the following build errors in openSUSE Factory:
+
+CMakeFiles/python_module_PyKDE4_dnssd.dir/sip/dnssd/sipdnssdpart2.cpp.o:
+In function `meth_DNSSD_ServiceBrowser_resolveHostName':
+/home/abuild/rpmbuild/BUILD/pykde4-4.14.3/build/sip/dnssd/sipdnssdpart2.cpp:408:
+undefined reference to `QHostAddress::QHostAddress(QHostAddress
+const&)'
+/home/abuild/rpmbuild/BUILD/pykde4-4.14.3/build/sip/dnssd/sipdnssdpart2.cpp:408:
+undefined reference to `QHostAddress::~QHostAddress()'
+collect2: error: ld returned 1 exit status
+...
+CMakeFiles/python_module_PyKDE4_kio.dir/sip/kio/sipkiopart3.cpp.o: In
+function `meth_KFilePlacesModel_deviceForIndex':
+/home/abuild/rpmbuild/BUILD/pykde4-4.14.3/build/sip/kio/sipkiopart3.cpp:18560:
+undefined reference to `Solid::Device::Device(Solid::Device const&)'
+/home/abuild/rpmbuild/BUILD/pykde4-4.14.3/build/sip/kio/sipkiopart3.cpp:18560:
+undefined reference to `Solid::Device::~Device()'
+CMakeFiles/python_module_PyKDE4_kio.dir/sip/kio/sipkiopart4.cpp.o: In
+function `meth_KDeviceListModel_deviceForIndex':
+/home/abuild/rpmbuild/BUILD/pykde4-4.14.3/build/sip/kio/sipkiopart4.cpp:27090:
+undefined reference to `Solid::Device::Device(Solid::Device const&)'
+/home/abuild/rpmbuild/BUILD/pykde4-4.14.3/build/sip/kio/sipkiopart4.cpp:27090:
+undefined reference to `Solid::Device::~Device()'
+collect2: error: ld returned 1 exit status
+
+REVIEW: 127705
+---
+ CMakeLists.txt | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index b0768cf..b919d1b 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -166,7 +166,7 @@ add_sip_python_module(PyKDE4.kdeui sip/kdeui/kdeuimod.sip ${KDE4_KDEUI_LIBS} ${Q
+
+ file(GLOB kio_files_sip sip/kio/*.sip)
+ set(SIP_EXTRA_FILES_DEPEND ${kio_files_sip})
+-add_sip_python_module(PyKDE4.kio sip/kio/kiomod.sip ${KDE4_KIO_LIBS} ${KDE4_KFILE_LIBS})
++add_sip_python_module(PyKDE4.kio sip/kio/kiomod.sip ${KDE4_KIO_LIBS} ${KDE4_KFILE_LIBS} ${KDE4_SOLID_LIBS})
+
+ file(GLOB kutils_files_sip sip/kutils/*.sip)
+ set(SIP_EXTRA_FILES_DEPEND ${kutils_files_sip})
+@@ -190,7 +190,7 @@ add_sip_python_module(PyKDE4.knewstuff sip/knewstuff/knewstuffmod.sip ${KDE4_KNE
+
+ file(GLOB dnssd_files_sip sip/dnssd/*.sip)
+ set(SIP_EXTRA_FILES_DEPEND ${dnssd_files_sip})
+-add_sip_python_module(PyKDE4.dnssd sip/dnssd/dnssdmod.sip ${KDE4_KDNSSD_LIBS} ${QT_QTCORE_LIBRARY})
++add_sip_python_module(PyKDE4.dnssd sip/dnssd/dnssdmod.sip ${KDE4_KDNSSD_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTNETWORK_LIBRARY})
+
+ file(GLOB phonon_files_sip sip/phonon/*.sip)
+ set(SIP_EXTRA_FILES_DEPEND ${phonon_files_sip})
+--
+2.9.3
+
diff --git a/kde/patch/pykde4/0003-Fix-build-with-sip-4.19.patch b/kde/patch/pykde4/0003-Fix-build-with-sip-4.19.patch
new file mode 100644
index 0000000..61ef78d
--- /dev/null
+++ b/kde/patch/pykde4/0003-Fix-build-with-sip-4.19.patch
@@ -0,0 +1,599 @@
+From 2d1eadf5d0148c88cb4393993f0269e196cbe7b1 Mon Sep 17 00:00:00 2001
+From: Johannes Huber <johu@gentoo.org>
+Date: Mon, 9 Jan 2017 11:52:12 +0100
+Subject: [PATCH 3/3] Fix build with sip 4.19
+
+REVIEW: 129799
+---
+ sip/dnssd/remoteservice.sip | 10 +++++-----
+ sip/kdecore/kmimetype.sip | 10 +++++-----
+ sip/kdecore/ksharedconfig.sip | 4 ++--
+ sip/kdecore/ksycocaentry.sip | 10 +++++-----
+ sip/kdecore/typedefs.sip | 30 +++++++++++++++---------------
+ sip/kdeui/kcompletion.sip | 10 +++++-----
+ sip/kdeui/kxmlguibuilder.sip | 4 ++--
+ sip/kio/kservicegroup.sip | 10 +++++-----
+ sip/ktexteditor/markinterface.sip | 10 +++++-----
+ sip/phonon/objectdescription.sip | 10 +++++-----
+ sip/soprano/pluginmanager.sip | 30 +++++++++++++++---------------
+ 11 files changed, 69 insertions(+), 69 deletions(-)
+
+diff --git a/sip/dnssd/remoteservice.sip b/sip/dnssd/remoteservice.sip
+index 5c5397a..44db887 100644
+--- a/sip/dnssd/remoteservice.sip
++++ b/sip/dnssd/remoteservice.sip
+@@ -66,7 +66,7 @@ protected:
+ DNSSD::RemoteService::Ptr *t = new DNSSD::RemoteService::Ptr (sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromNewInstance(t->data(), sipClass_DNSSD_RemoteService, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromNewType(t->data(), sipType_DNSSD_RemoteService, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ delete t;
+@@ -88,7 +88,7 @@ protected:
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_DNSSD_RemoteService, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_DNSSD_RemoteService, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -99,11 +99,11 @@ protected:
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- DNSSD::RemoteService *t = reinterpret_cast<DNSSD::RemoteService *>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_DNSSD_RemoteService, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ DNSSD::RemoteService *t = reinterpret_cast<DNSSD::RemoteService *>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_DNSSD_RemoteService, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t, sipClass_DNSSD_RemoteService, state);
++ sipReleaseType(t, sipType_DNSSD_RemoteService, state);
+
+ delete ql;
+ return 0;
+@@ -113,7 +113,7 @@ protected:
+
+ ql->append(*tptr);
+
+- sipReleaseInstance(t, sipClass_DNSSD_RemoteService, state);
++ sipReleaseType(t, sipType_DNSSD_RemoteService, state);
+ }
+
+ *sipCppPtr = ql;
+diff --git a/sip/kdecore/kmimetype.sip b/sip/kdecore/kmimetype.sip
+index b2d21f7..2945210 100644
+--- a/sip/kdecore/kmimetype.sip
++++ b/sip/kdecore/kmimetype.sip
+@@ -100,7 +100,7 @@ public:
+ KMimeType::Ptr *t = new KMimeType::Ptr (sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromNewInstance(t->data(), sipClass_KMimeType, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromNewType(t->data(), sipType_KMimeType, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ delete t;
+@@ -122,7 +122,7 @@ public:
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_KMimeType, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_KMimeType, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -133,11 +133,11 @@ public:
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- KMimeType *t = reinterpret_cast<KMimeType *>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_KMimeType, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ KMimeType *t = reinterpret_cast<KMimeType *>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_KMimeType, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t, sipClass_KMimeType, state);
++ sipReleaseType(t, sipType_KMimeType, state);
+
+ delete ql;
+ return 0;
+@@ -147,7 +147,7 @@ public:
+
+ ql->append(*tptr);
+
+- sipReleaseInstance(t, sipClass_KMimeType, state);
++ sipReleaseType(t, sipType_KMimeType, state);
+ }
+
+ *sipCppPtr = ql;
+diff --git a/sip/kdecore/ksharedconfig.sip b/sip/kdecore/ksharedconfig.sip
+index 54b1599..9442d80 100644
+--- a/sip/kdecore/ksharedconfig.sip
++++ b/sip/kdecore/ksharedconfig.sip
+@@ -65,7 +65,7 @@ typedef KSharedConfig::Ptr KSharedConfigPtr;
+ KSharedConfigPtr kcpp = *sipCpp;
+ KSharedConfig *ksc = kcpp.data ();
+ ksc->ref.ref();
+- PyObject *pyKsc = sipConvertFromInstance(ksc, sipClass_KSharedConfig, sipTransferObj);
++ PyObject *pyKsc = sipConvertFromType(ksc, sipType_KSharedConfig, sipTransferObj);
+ return pyKsc;
+ %End
+
+@@ -74,7 +74,7 @@ typedef KSharedConfig::Ptr KSharedConfigPtr;
+ return 1;
+
+ int state;
+- KSharedConfig* ksc = (KSharedConfig *)sipConvertToInstance(sipPy, sipClass_KSharedConfig, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr);
++ KSharedConfig* ksc = (KSharedConfig *)sipConvertToType(sipPy, sipType_KSharedConfig, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr);
+ *sipCppPtr = new KSharedConfigPtr (ksc);
+ ksc->ref.deref();
+ return sipGetState(sipTransferObj);
+diff --git a/sip/kdecore/ksycocaentry.sip b/sip/kdecore/ksycocaentry.sip
+index 4632e4a..ceb85fa 100644
+--- a/sip/kdecore/ksycocaentry.sip
++++ b/sip/kdecore/ksycocaentry.sip
+@@ -83,7 +83,7 @@ private:
+ KSycocaEntry::Ptr *t = new KSycocaEntry::Ptr (sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromNewInstance(t->data(), sipClass_KSycocaEntry, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromNewType(t->data(), sipType_KSycocaEntry, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ delete t;
+@@ -105,7 +105,7 @@ private:
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_KSycocaEntry, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_KSycocaEntry, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -116,11 +116,11 @@ private:
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- KSycocaEntry *t = reinterpret_cast<KSycocaEntry *>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_KSycocaEntry, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ KSycocaEntry *t = reinterpret_cast<KSycocaEntry *>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_KSycocaEntry, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t, sipClass_KSycocaEntry, state);
++ sipReleaseType(t, sipType_KSycocaEntry, state);
+
+ delete ql;
+ return 0;
+@@ -130,7 +130,7 @@ private:
+
+ ql->append(*tptr);
+
+- sipReleaseInstance(t, sipClass_KSycocaEntry, state);
++ sipReleaseType(t, sipType_KSycocaEntry, state);
+ }
+
+ *sipCppPtr = ql;
+diff --git a/sip/kdecore/typedefs.sip b/sip/kdecore/typedefs.sip
+index af53f85..23956b7 100644
+--- a/sip/kdecore/typedefs.sip
++++ b/sip/kdecore/typedefs.sip
+@@ -397,8 +397,8 @@ template <TYPE1,TYPE2>
+ TYPE1 *t1 = new TYPE1(i.key());
+ TYPE2 *t2 = new TYPE2(i.value());
+
+- PyObject *t1obj = sipConvertFromNewInstance(t1, sipClass_TYPE1, sipTransferObj);
+- PyObject *t2obj = sipConvertFromNewInstance(t2, sipClass_TYPE2, sipTransferObj);
++ PyObject *t1obj = sipConvertFromNewType(t1, sipType_TYPE1, sipTransferObj);
++ PyObject *t2obj = sipConvertFromNewType(t2, sipType_TYPE2, sipTransferObj);
+
+ if (t1obj == NULL || t2obj == NULL || PyDict_SetItem(d, t1obj, t2obj) < 0)
+ {
+@@ -438,10 +438,10 @@ template <TYPE1,TYPE2>
+
+ while (PyDict_Next(sipPy, &i, &t1obj, &t2obj))
+ {
+- if (!sipCanConvertToInstance(t1obj, sipClass_TYPE1, SIP_NOT_NONE))
++ if (!sipCanConvertToType(t1obj, sipType_TYPE1, SIP_NOT_NONE))
+ return 0;
+
+- if (!sipCanConvertToInstance(t2obj, sipClass_TYPE2, SIP_NOT_NONE))
++ if (!sipCanConvertToType(t2obj, sipType_TYPE2, SIP_NOT_NONE))
+ return 0;
+ }
+
+@@ -454,13 +454,13 @@ template <TYPE1,TYPE2>
+ {
+ int state1, state2;
+
+- TYPE1 *t1 = reinterpret_cast<TYPE1 *>(sipConvertToInstance(t1obj, sipClass_TYPE1, sipTransferObj, SIP_NOT_NONE, &state1, sipIsErr));
+- TYPE2 *t2 = reinterpret_cast<TYPE2 *>(sipConvertToInstance(t2obj, sipClass_TYPE2, sipTransferObj, SIP_NOT_NONE, &state2, sipIsErr));
++ TYPE1 *t1 = reinterpret_cast<TYPE1 *>(sipConvertToType(t1obj, sipType_TYPE1, sipTransferObj, SIP_NOT_NONE, &state1, sipIsErr));
++ TYPE2 *t2 = reinterpret_cast<TYPE2 *>(sipConvertToType(t2obj, sipType_TYPE2, sipTransferObj, SIP_NOT_NONE, &state2, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t1, sipClass_TYPE1, state1);
+- sipReleaseInstance(t2, sipClass_TYPE2, state2);
++ sipReleaseType(t1, sipType_TYPE1, state1);
++ sipReleaseType(t2, sipType_TYPE2, state2);
+
+ delete qm;
+ return 0;
+@@ -468,8 +468,8 @@ template <TYPE1,TYPE2>
+
+ qm->insert(*t1, *t2);
+
+- sipReleaseInstance(t1, sipClass_TYPE1, state1);
+- sipReleaseInstance(t2, sipClass_TYPE2, state2);
++ sipReleaseType(t1, sipType_TYPE1, state1);
++ sipReleaseType(t2, sipType_TYPE2, state2);
+ }
+
+ *sipCppPtr = qm;
+@@ -669,7 +669,7 @@ template <TYPE*>
+ TYPE *t = (TYPE *)(sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromNewInstance(t, sipClass_TYPE, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromNewType(t, sipType_TYPE, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ delete t;
+@@ -691,7 +691,7 @@ template <TYPE*>
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_TYPE, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_TYPE, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -702,11 +702,11 @@ template <TYPE*>
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- TYPE *t = reinterpret_cast<TYPE *>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_TYPE, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ TYPE *t = reinterpret_cast<TYPE *>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_TYPE, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t, sipClass_TYPE, state);
++ sipReleaseType(t, sipType_TYPE, state);
+
+ delete qv;
+ return 0;
+@@ -714,7 +714,7 @@ template <TYPE*>
+
+ qv->append(t);
+
+- sipReleaseInstance(t, sipClass_TYPE, state);
++ sipReleaseType(t, sipType_TYPE, state);
+ }
+
+ *sipCppPtr = qv;
+diff --git a/sip/kdeui/kcompletion.sip b/sip/kdeui/kcompletion.sip
+index f1d327f..938506a 100644
+--- a/sip/kdeui/kcompletion.sip
++++ b/sip/kdeui/kcompletion.sip
+@@ -176,7 +176,7 @@ public:
+ #else
+ PyObject *kobj = PyInt_FromLong((int)i.key());
+ #endif
+- PyObject *tobj = sipConvertFromNewInstance(t, sipClass_KShortcut, sipTransferObj);
++ PyObject *tobj = sipConvertFromNewType(t, sipType_KShortcut, sipTransferObj);
+
+ if (kobj == NULL || tobj == NULL || PyDict_SetItem(d, kobj, tobj) < 0)
+ {
+@@ -213,7 +213,7 @@ public:
+ return 0;
+
+ while (PyDict_Next(sipPy, &i, &kobj, &tobj))
+- if (!sipCanConvertToInstance(tobj, sipClass_KShortcut, SIP_NOT_NONE))
++ if (!sipCanConvertToType(tobj, sipType_KShortcut, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -229,11 +229,11 @@ public:
+ #else
+ int k = PyInt_AsLong(kobj);
+ #endif
+- KShortcut *t = reinterpret_cast<KShortcut *>(sipConvertToInstance(tobj, sipClass_KShortcut, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ KShortcut *t = reinterpret_cast<KShortcut *>(sipConvertToType(tobj, sipType_KShortcut, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t, sipClass_KShortcut, state);
++ sipReleaseType(t, sipType_KShortcut, state);
+
+ delete qm;
+ return 0;
+@@ -241,7 +241,7 @@ public:
+
+ qm->insert((KCompletionBase::KeyBindingType)k, *t);
+
+- sipReleaseInstance(t, sipClass_KShortcut, state);
++ sipReleaseType(t, sipType_KShortcut, state);
+ }
+
+ *sipCppPtr = qm;
+diff --git a/sip/kdeui/kxmlguibuilder.sip b/sip/kdeui/kxmlguibuilder.sip
+index 41ae2aa..e4cf187 100644
+--- a/sip/kdeui/kxmlguibuilder.sip
++++ b/sip/kdeui/kxmlguibuilder.sip
+@@ -49,10 +49,10 @@ QAction *containerAction;
+ PyObject *pyWidget;
+ PyObject *pyContainerAction;
+
+- if ((pyWidget = sipConvertFromNewInstance(res, sipClass_QWidget, NULL)) == NULL)
++ if ((pyWidget = sipConvertFromNewType(res, sipType_QWidget, NULL)) == NULL)
+ return NULL;
+
+- if ((pyContainerAction = sipConvertFromNewInstance(containerAction, sipClass_QAction, NULL)) == NULL)
++ if ((pyContainerAction = sipConvertFromNewType(containerAction, sipType_QAction, NULL)) == NULL)
+ return NULL;
+
+ sipRes = Py_BuildValue ("NN", pyWidget, pyContainerAction);
+diff --git a/sip/kio/kservicegroup.sip b/sip/kio/kservicegroup.sip
+index a1ef981..1ddce37 100644
+--- a/sip/kio/kservicegroup.sip
++++ b/sip/kio/kservicegroup.sip
+@@ -151,7 +151,7 @@ public:
+ KServiceGroup::SPtr *t = new KServiceGroup::SPtr (sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromNewInstance(t->data(), sipClass_KServiceGroup, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromNewType(t->data(), sipType_KServiceGroup, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ delete t;
+@@ -173,7 +173,7 @@ public:
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_KServiceGroup, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_KServiceGroup, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -184,11 +184,11 @@ public:
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- KServiceGroup *t = reinterpret_cast<KServiceGroup *>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_KServiceGroup, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ KServiceGroup *t = reinterpret_cast<KServiceGroup *>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_KServiceGroup, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t, sipClass_KServiceGroup, state);
++ sipReleaseType(t, sipType_KServiceGroup, state);
+
+ delete ql;
+ return 0;
+@@ -198,7 +198,7 @@ public:
+
+ ql->append(*tptr);
+
+- sipReleaseInstance(t, sipClass_KServiceGroup, state);
++ sipReleaseType(t, sipType_KServiceGroup, state);
+ }
+
+ *sipCppPtr = ql;
+diff --git a/sip/ktexteditor/markinterface.sip b/sip/ktexteditor/markinterface.sip
+index d9b0ec9..888c506 100644
+--- a/sip/ktexteditor/markinterface.sip
++++ b/sip/ktexteditor/markinterface.sip
+@@ -158,7 +158,7 @@ signals:
+ #else
+ PyObject *t1obj = PyInt_FromLong ((long)t1);
+ #endif
+- PyObject *t2obj = sipConvertFromNewInstance(t2, sipClass_KTextEditor_Mark, sipTransferObj);
++ PyObject *t2obj = sipConvertFromNewType(t2, sipType_KTextEditor_Mark, sipTransferObj);
+
+ if (t2obj == NULL || PyDict_SetItem(d, t1obj, t2obj) < 0)
+ {
+@@ -203,7 +203,7 @@ signals:
+ #endif
+ return 0;
+
+- if (!sipCanConvertToInstance(t2obj, sipClass_KTextEditor_Mark, SIP_NOT_NONE))
++ if (!sipCanConvertToType(t2obj, sipType_KTextEditor_Mark, SIP_NOT_NONE))
+ return 0;
+ }
+
+@@ -221,11 +221,11 @@ signals:
+ #else
+ int t1 = PyInt_AS_LONG (t1obj);
+ #endif
+- KTextEditor::Mark *t2 = reinterpret_cast<KTextEditor::Mark *>(sipConvertToInstance(t2obj, sipClass_KTextEditor_Mark, sipTransferObj, SIP_NOT_NONE, &state2, sipIsErr));
++ KTextEditor::Mark *t2 = reinterpret_cast<KTextEditor::Mark *>(sipConvertToType(t2obj, sipType_KTextEditor_Mark, sipTransferObj, SIP_NOT_NONE, &state2, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t2, sipClass_KTextEditor_Mark, state2);
++ sipReleaseType(t2, sipType_KTextEditor_Mark, state2);
+
+ delete qm;
+ return 0;
+@@ -233,7 +233,7 @@ signals:
+
+ qm->insert(t1, t2);
+
+- sipReleaseInstance(t2, sipClass_KTextEditor_Mark, state2);
++ sipReleaseType(t2, sipType_KTextEditor_Mark, state2);
+ }
+
+ *sipCppPtr = qm;
+diff --git a/sip/phonon/objectdescription.sip b/sip/phonon/objectdescription.sip
+index 2b86d5e..015b2ef 100644
+--- a/sip/phonon/objectdescription.sip
++++ b/sip/phonon/objectdescription.sip
+@@ -116,7 +116,7 @@ void registerMetaTypes ();
+ DNSSD::RemoteService::Ptr *t = new Phonon::ObjectDescription (sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromNewInstance(t->data(), sipClass_DNSSD_RemoteService, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromNewType(t->data(), sipType_DNSSD_RemoteService, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ delete t;
+@@ -138,7 +138,7 @@ void registerMetaTypes ();
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_DNSSD_RemoteService, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_DNSSD_RemoteService, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -149,11 +149,11 @@ void registerMetaTypes ();
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- DNSSD::RemoteService *t = reinterpret_cast<DNSSD::RemoteService *>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_DNSSD_RemoteService, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ DNSSD::RemoteService *t = reinterpret_cast<DNSSD::RemoteService *>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_DNSSD_RemoteService, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(t, sipClass_DNSSD_RemoteService, state);
++ sipReleaseType(t, sipType_DNSSD_RemoteService, state);
+
+ delete ql;
+ return 0;
+@@ -163,7 +163,7 @@ void registerMetaTypes ();
+
+ ql->append(*tptr);
+
+- sipReleaseInstance(t, sipClass_DNSSD_RemoteService, state);
++ sipReleaseType(t, sipType_DNSSD_RemoteService, state);
+ }
+
+ *sipCppPtr = ql;
+diff --git a/sip/soprano/pluginmanager.sip b/sip/soprano/pluginmanager.sip
+index c2be1c3..fe990f8 100644
+--- a/sip/soprano/pluginmanager.sip
++++ b/sip/soprano/pluginmanager.sip
+@@ -73,7 +73,7 @@ public:
+ Soprano::Backend* t = const_cast<Soprano::Backend*>(sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromInstance(t, sipClass_Soprano_Backend, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromType(t, sipType_Soprano_Backend, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ return NULL;
+@@ -93,7 +93,7 @@ public:
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_Soprano_Backend, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_Soprano_Backend, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -104,18 +104,18 @@ public:
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- const Soprano::Backend*t = reinterpret_cast<const Soprano::Backend*>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_Soprano_Backend, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ const Soprano::Backend*t = reinterpret_cast<const Soprano::Backend*>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_Soprano_Backend, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(const_cast<Soprano::Backend*>(t), sipClass_Soprano_Backend, state);
++ sipReleaseType(const_cast<Soprano::Backend*>(t), sipType_Soprano_Backend, state);
+
+ delete ql;
+ return 0;
+ }
+ ql->append(t);
+
+- sipReleaseInstance(const_cast<Soprano::Backend*>(t), sipClass_Soprano_Backend, state);
++ sipReleaseType(const_cast<Soprano::Backend*>(t), sipType_Soprano_Backend, state);
+ }
+
+ *sipCppPtr = ql;
+@@ -144,7 +144,7 @@ public:
+ Soprano::Parser* t = const_cast<Soprano::Parser*>(sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromInstance(t, sipClass_Soprano_Parser, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromType(t, sipType_Soprano_Parser, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ return NULL;
+@@ -164,7 +164,7 @@ public:
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_Soprano_Parser, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_Soprano_Parser, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -175,18 +175,18 @@ public:
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- const Soprano::Parser*t = reinterpret_cast<const Soprano::Parser*>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_Soprano_Parser, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ const Soprano::Parser*t = reinterpret_cast<const Soprano::Parser*>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_Soprano_Parser, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(const_cast<Soprano::Parser*>(t), sipClass_Soprano_Parser, state);
++ sipReleaseType(const_cast<Soprano::Parser*>(t), sipType_Soprano_Parser, state);
+
+ delete ql;
+ return 0;
+ }
+ ql->append(t);
+
+- sipReleaseInstance(const_cast<Soprano::Parser*>(t), sipClass_Soprano_Parser, state);
++ sipReleaseType(const_cast<Soprano::Parser*>(t), sipType_Soprano_Parser, state);
+ }
+
+ *sipCppPtr = ql;
+@@ -215,7 +215,7 @@ public:
+ Soprano::Serializer* t = const_cast<Soprano::Serializer*>(sipCpp->at(i));
+ PyObject *tobj;
+
+- if ((tobj = sipConvertFromInstance(t, sipClass_Soprano_Serializer, sipTransferObj)) == NULL)
++ if ((tobj = sipConvertFromType(t, sipType_Soprano_Serializer, sipTransferObj)) == NULL)
+ {
+ Py_DECREF(l);
+ return NULL;
+@@ -235,7 +235,7 @@ public:
+ return 0;
+
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+- if (!sipCanConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_Soprano_Serializer, SIP_NOT_NONE))
++ if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_Soprano_Serializer, SIP_NOT_NONE))
+ return 0;
+
+ return 1;
+@@ -246,18 +246,18 @@ public:
+ for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i)
+ {
+ int state;
+- const Soprano::Serializer*t = reinterpret_cast<const Soprano::Serializer*>(sipConvertToInstance(PyList_GET_ITEM(sipPy, i), sipClass_Soprano_Serializer, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
++ const Soprano::Serializer*t = reinterpret_cast<const Soprano::Serializer*>(sipConvertToType(PyList_GET_ITEM(sipPy, i), sipType_Soprano_Serializer, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+- sipReleaseInstance(const_cast<Soprano::Serializer*>(t), sipClass_Soprano_Serializer, state);
++ sipReleaseType(const_cast<Soprano::Serializer*>(t), sipType_Soprano_Serializer, state);
+
+ delete ql;
+ return 0;
+ }
+ ql->append(t);
+
+- sipReleaseInstance(const_cast<Soprano::Serializer*>(t), sipClass_Soprano_Serializer, state);
++ sipReleaseType(const_cast<Soprano::Serializer*>(t), sipType_Soprano_Serializer, state);
+ }
+
+ *sipCppPtr = ql;
+--
+2.9.3
+
diff --git a/kde/patch/sddm-qt5.patch b/kde/patch/sddm-qt5.patch
index fa4e1b3..21f2c04 100644
--- a/kde/patch/sddm-qt5.patch
+++ b/kde/patch/sddm-qt5.patch
@@ -2,9 +2,24 @@
# (brings back the switch_user functionality in KDE):
cat $CWD/patch/sddm-qt5/sddm_consolekit.diff | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+# SDDM 0.14 sources $HOME/.xsession which in Slackware will override the
+# session selection you make in SDDM. We fix that unwanted side effect by
+# reverting the change:
+cat $CWD/patch/sddm-qt5/sddm_userxsession.diff | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Fix display of user avatars ($HOME/.face.icon file)
+# (fixed in sddm-0.15.0).
+#cat $CWD//patch/sddm-qt5/sddm_avatars.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
# Fix a compilation error on passwd backend:
#cat $CWD/patch/sddm-qt5/sddm_auth.diff | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
# Fix a compilation error on passwd backend:
# (fixed in sddm-0.12.0).
#cat $CWD/patch/sddm-qt5/sddm_qstring.patch | patch -p1 --verbose || { touch ${SLACK_KDE_BUILD_DIR}/${PKGNAME}.failed ; continue ; }
+
+# Add the dutch translation:
+if ! grep -q nl.ts data/translations/CMakeLists.txt ; then
+ sed -e '/set(TRANSLATION_FILES/s/TRANSLATION_FILES/&\n nl.ts/' \
+ -i data/translations/CMakeLists.txt
+fi
diff --git a/kde/patch/sddm-qt5/sddm_avatars.patch b/kde/patch/sddm-qt5/sddm_avatars.patch
new file mode 100644
index 0000000..d40f68c
--- /dev/null
+++ b/kde/patch/sddm-qt5/sddm_avatars.patch
@@ -0,0 +1,33 @@
+From ecb903e48822bd90650bdd64fe80754e3e9664cb Mon Sep 17 00:00:00 2001
+From: Bastian Beischer <bastian.beischer@gmail.com>
+Date: Fri, 2 Sep 2016 13:05:18 +0200
+Subject: [PATCH] Fix display of user avatars. (#684)
+
+QFile::exists("...") does not understand file:// URLs, at least in Qt
+5.7.0 and Qt 4.8.7.
+---
+ src/greeter/UserModel.cpp | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/greeter/UserModel.cpp b/src/greeter/UserModel.cpp
+index 41a9f10..94c492d 100644
+--- a/src/greeter/UserModel.cpp
++++ b/src/greeter/UserModel.cpp
+@@ -107,13 +107,13 @@ namespace SDDM {
+ d->lastIndex = i;
+
+ if (avatarsEnabled) {
+- const QString userFace = QStringLiteral("file://%1/.face.icon").arg(user->homeDir);
+- const QString systemFace = QStringLiteral("file://%1/%2.face.icon").arg(facesDir).arg(user->name);
++ const QString userFace = QStringLiteral("%1/.face.icon").arg(user->homeDir);
++ const QString systemFace = QStringLiteral("%1/%2.face.icon").arg(facesDir).arg(user->name);
+
+ if (QFile::exists(userFace))
+- user->icon = userFace;
++ user->icon = QStringLiteral("file://%1").arg(userFace);
+ else if (QFile::exists(systemFace))
+- user->icon = systemFace;
++ user->icon = QStringLiteral("file://%1").arg(systemFace);
+ }
+ }
+ }
diff --git a/kde/patch/sddm-qt5/sddm_consolekit.diff b/kde/patch/sddm-qt5/sddm_consolekit.diff
index af79f75..9b535bf 100644
--- a/kde/patch/sddm-qt5/sddm_consolekit.diff
+++ b/kde/patch/sddm-qt5/sddm_consolekit.diff
@@ -1,13 +1,9 @@
-diff --git a/data/scripts/Xsession b/data/scripts/Xsession
-index a5d270d..4b48524 100755
---- a/data/scripts/Xsession
-+++ b/data/scripts/Xsession
-@@ -74,7 +74,7 @@ case $session in
- exec xterm -geometry 80x24-0-0
- ;;
- *)
-- eval exec "$session"
-+ eval exec ck-launch-session dbus-launch --sh-syntax --exit-with-session "$session"
- ;;
- esac
- exec xmessage -center -buttons OK:0 -default OK "Sorry, cannot execute $session. Check $DESKTOP_SESSION.desktop."
+--- sddm-0.14.0/data/scripts/Xsession.orig 2016-08-28 13:54:03.000000000 +0200
++++ sddm-0.14.0/data/scripts/Xsession 2016-11-05 21:47:28.502096600 +0100
+@@ -91,5 +91,5 @@
+ if [ -z "$@" ]; then
+ exec xmessage -center -buttons OK:0 -default OK "Sorry, $DESKTOP_SESSION is no valid session."
+ else
+- exec $@
++ exec ck-launch-session dbus-launch --sh-syntax --exit-with-session $@
+ fi
diff --git a/kde/patch/sddm-qt5/sddm_userxsession.diff b/kde/patch/sddm-qt5/sddm_userxsession.diff
new file mode 100644
index 0000000..cbfa1ef
--- /dev/null
+++ b/kde/patch/sddm-qt5/sddm_userxsession.diff
@@ -0,0 +1,13 @@
+--- sddm-0.14.0/data/scripts/Xsession.orig 2016-08-28 13:54:03.000000000 +0200
++++ sddm-0.14.0/data/scripts/Xsession 2016-11-06 21:35:43.183138893 +0100
+@@ -84,10 +84,6 @@
+ fi
+ [ -f $HOME/.Xresources ] && xrdb -merge $HOME/.Xresources
+
+-if [ -f "$USERXSESSION" ]; then
+- . "$USERXSESSION"
+-fi
+-
+ if [ -z "$@" ]; then
+ exec xmessage -center -buttons OK:0 -default OK "Sorry, $DESKTOP_SESSION is no valid session."
+ else