diff --git a/.gitignore b/.gitignore index eb8d8e07bd..d814e73cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,32 @@ *.orig __pycache__ *.egg-info *.trace build qtcreator-build *.kdev4 *~ .kateconfig CMakeLists.txt.user* .directory *.autosave *.swp .gdb_history .kdev_include_paths *.config *.creator *.creator.user *.files *.includes .DS_Store *.kate-swap .idea GTAGS GPATH GRTAGS GSYMS BROWSE *.kate-swp /po/ -/kf5/ -/build-android/ -/b/ -/i/ -/d/ +build_dir +.flatpak-builder diff --git a/3rdparty/ext_frameworks/CMakeLists.txt b/3rdparty/ext_frameworks/CMakeLists.txt index 5e5354204e..f310500d8f 100644 --- a/3rdparty/ext_frameworks/CMakeLists.txt +++ b/3rdparty/ext_frameworks/CMakeLists.txt @@ -1,242 +1,244 @@ SET(EXTPREFIX_frameworks "${EXTPREFIX}" ) # # All needed frameworks: # # Archive # Config # WidgetsAddons # Completion # CoreAddons # GuiAddons # I18n # ItemModels # ItemViews # WindowSystem +# kimageformats # On Linux: # KCrash ExternalProject_Add( ext_extra_cmake_modules DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/extra-cmake-modules-5.44.0.zip - URL_MD5 74aa8fc501e27024390b01c81f2925eb + URL http://download.kde.org/stable/frameworks/5.60/extra-cmake-modules-5.60.0.zip + URL_MD5 c205e17d89028ab0b034e68081d5ebfb PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ecm_install_to_share.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" ) ExternalProject_Add( ext_karchive DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/karchive-5.44.0.zip - URL_MD5 c60a8e22b88cc7328610041638459689 + URL http://download.kde.org/stable/frameworks/5.60/karchive-5.60.0.zip + URL_MD5 50ae1495ef5b19898a06e43160d9b84d PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/karchive.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_extra_cmake_modules ) ExternalProject_Add( ext_kconfig DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kconfig-5.44.0.zip - URL_MD5 d0223ea471bbf463ec42c2a2355a5183 + URL http://download.kde.org/stable/frameworks/5.60/kconfig-5.60.0.zip + URL_MD5 db96fdd78a574d07b09abb720dae1a06 PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kconfig.diff INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} - -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} - ${GLOBAL_PROFILE} - -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} - -DBUILD_TESTING=false + -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} + ${GLOBAL_PROFILE} + -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} + -DBUILD_TESTING=false + -DKCONFIG_USE_DBUS=off UPDATE_COMMAND "" DEPENDS ext_karchive ) ExternalProject_Add( ext_kwidgetsaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kwidgetsaddons-5.44.0.zip - URL_MD5 a9911d8d0f8aaf7a7afd84c41c8f80a1 + URL http://download.kde.org/stable/frameworks/5.60/kwidgetsaddons-5.60.0.zip + URL_MD5 1b09306fe482dd5665b16a243eb2be94 INSTALL_DIR ${EXTPREFIX_frameworks} - PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwidgetsaddons.diff +# PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwidgetsaddons.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kconfig ) ExternalProject_Add( ext_kcompletion DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kcompletion-5.44.0.zip - URL_MD5 0647885a702c338a1b656eb4f311ad16 + URL http://download.kde.org/stable/frameworks/5.60/kcompletion-5.60.0.zip + URL_MD5 a9848cb26ff96246769c56244f0db25f INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwidgetsaddons ) ExternalProject_Add( ext_kcoreaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kcoreaddons-5.44.0.zip - URL_MD5 16a7379f3e2941d1c19d6f80939f15e8 + URL http://download.kde.org/stable/frameworks/5.60/kcoreaddons-5.60.0.zip + URL_MD5 da21c1532042c2c8a7e56f444c581196 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/desktoptojson.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcompletion ) ExternalProject_Add( ext_kguiaddons DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kguiaddons-5.44.0.zip - URL_MD5 440eefbf5abcafc492dcf857f7e4eaf5 + URL http://download.kde.org/stable/frameworks/5.60/kguiaddons-5.60.0.zip + URL_MD5 f8aeea516e264c80df470efafab6ded0 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kcoreaddons ) if(APPLE) ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/ki18n-5.44.0.zip - URL_MD5 333ab0a3f65a298e928d746144d4dc8e + URL http://download.kde.org/stable/frameworks/5.60/ki18n-5.60.0.zip + URL_MD5 e53f479f22ea17629319fec710dda036 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n-appdatalocation.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kguiaddons ext_gettext ) else() ExternalProject_Add( ext_ki18n DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/ki18n-5.44.0.zip - URL_MD5 333ab0a3f65a298e928d746144d4dc8e + URL http://download.kde.org/stable/frameworks/5.60/ki18n-5.60.0.zip + URL_MD5 e53f479f22ea17629319fec710dda036 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/ki18n-appdatalocation.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kguiaddons ) endif() ExternalProject_Add( ext_kitemmodels DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kitemmodels-5.44.0.zip - URL_MD5 ea43a5e2cc7033eb672796b108d7403b + URL http://download.kde.org/stable/frameworks/5.60/kitemmodels-5.60.0.zip + URL_MD5 c8f02881a65d496d6114bb2155cdfc36 INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_ki18n ) ExternalProject_Add( ext_kitemviews DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kitemviews-5.44.0.zip - URL_MD5 8b15c703313c7a790c7db897ef17de7d + URL http://download.kde.org/stable/frameworks/5.60/kitemviews-5.60.0.zip + URL_MD5 9d94b8da72f43fac39aa782f7efd941e INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemmodels ) ExternalProject_Add( ext_kimageformats DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kimageformats-5.44.0.zip - URL_MD5 02a98b682f9cb655592148d7ebcc05e7 + URL http://download.kde.org/stable/frameworks/5.60/kimageformats-5.60.0.zip + URL_MD5 a8b413560d12cd956d689784a056d6e0 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kimageformats.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kitemviews ) ExternalProject_Add( ext_kwindowsystem DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kwindowsystem-5.44.0.zip - URL_MD5 75329f47cf8cd413fa1d15a57c298563 + URL http://download.kde.org/stable/frameworks/5.60/kwindowsystem-5.60.0.zip + URL_MD5 c15c29141b5edca67143b735a94d3f43 INSTALL_DIR ${EXTPREFIX_frameworks} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/kwindowsystem-x11.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kimageformats ) ExternalProject_Add( ext_kcrash DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://download.kde.org/stable/frameworks/5.44/kcrash-5.44.0.zip - URL_MD5 61adc0e125c65288968d958acf25f4aa + URL http://download.kde.org/stable/frameworks/5.60/kcrash-5.60.0.zip + URL_MD5 7449a6f2b314b944ab2bed00124fe69b INSTALL_DIR ${EXTPREFIX_frameworks} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_frameworks} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DCMAKE_SYSTEM_PREFIX_PATH=${EXTPREFIX} -DBUILD_TESTING=false UPDATE_COMMAND "" DEPENDS ext_kwindowsystem ) diff --git a/3rdparty/ext_frameworks/karchive.diff b/3rdparty/ext_frameworks/karchive.diff index 909d6beb59..e5ce615438 100644 --- a/3rdparty/ext_frameworks/karchive.diff +++ b/3rdparty/ext_frameworks/karchive.diff @@ -1,39 +1,21 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index 2441977..2a20a13 100644 +index 45b3bf0..6afd3cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -29,20 +29,20 @@ set_package_properties(ZLIB PROPERTIES - PURPOSE "Required by the core KDE libraries and some critical kioslaves" +@@ -33,7 +33,7 @@ find_package(BZip2) + set_package_properties(BZip2 PROPERTIES + URL "https://sourceware.org/bzip2/" + DESCRIPTION "Support for BZip2 compressed files and data streams" +- TYPE RECOMMENDED ++ TYPE OPTIONAL + PURPOSE "Support for BZip2 compressed files and data streams" ) --find_package(BZip2) --set_package_properties(BZip2 PROPERTIES -- URL "http://www.bzip.org" -- DESCRIPTION "Support for BZip2 compressed files and data streams" -- TYPE RECOMMENDED -- PURPOSE "Support for BZip2 compressed files and data streams" --) -- --find_package(LibLZMA) --set_package_properties(LibLZMA PROPERTIES -- URL "http://tukaani.org/xz/" -- DESCRIPTION "Support for xz compressed files and data streams" -- PURPOSE "Support for xz compressed files and data streams" --) -+#find_package(BZip2) -+#set_package_properties(BZip2 PROPERTIES -+# URL "http://www.bzip.org" -+# DESCRIPTION "Support for BZip2 compressed files and data streams" -+# TYPE RECOMMENDED -+# PURPOSE "Support for BZip2 compressed files and data streams" -+#) -+ -+#find_package(LibLZMA) -+#set_package_properties(LibLZMA PROPERTIES -+# URL "http://tukaani.org/xz/" -+# DESCRIPTION "Support for xz compressed files and data streams" -+# PURPOSE "Support for xz compressed files and data streams" -+#) - include_directories( - ${ZLIB_INCLUDE_DIR} +@@ -41,6 +41,7 @@ find_package(LibLZMA) + set_package_properties(LibLZMA PROPERTIES + URL "http://tukaani.org/xz/" + DESCRIPTION "Support for xz compressed files and data streams" ++ TYPE OPTIONAL + PURPOSE "Support for xz compressed files and data streams" ) + include_directories( diff --git a/3rdparty/ext_frameworks/kconfig.diff b/3rdparty/ext_frameworks/kconfig.diff index df589aa57b..6bbc38cf7b 100644 --- a/3rdparty/ext_frameworks/kconfig.diff +++ b/3rdparty/ext_frameworks/kconfig.diff @@ -1,144 +1,89 @@ -diff --git a/autotests/kconfigtest.cpp b/autotests/kconfigtest.cpp -index 3e0578f..6c4408d 100644 ---- a/autotests/kconfigtest.cpp -+++ b/autotests/kconfigtest.cpp -@@ -587,7 +587,7 @@ void KConfigTest::testPathQtHome() - qunsetenv("QT_CACHE_HOME"); - qunsetenv("QT_CONFIG_HOME"); - QVERIFY(group.hasKey("dataDir")); -- QCOMPARE(group.readEntry("dataDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).append(QStringLiteral("/kconfigtest"))); -+ QCOMPARE(group.readEntry("dataDir", QString()), QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).append(QStringLiteral("/kconfigtest"))); - QVERIFY(group.hasKey("cacheDir")); - QCOMPARE(group.readEntry("cacheDir", QString()), QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation).append(QStringLiteral("/kconfigtest"))); - QVERIFY(group.hasKey("configDir")); -diff --git a/autotests/kdesktopfiletest.cpp b/autotests/kdesktopfiletest.cpp -index fd4a5c9..db08f22 100644 ---- a/autotests/kdesktopfiletest.cpp -+++ b/autotests/kdesktopfiletest.cpp -@@ -222,7 +222,7 @@ void KDesktopFileTest::testIsAuthorizedDesktopFile() - QVERIFY(QFile::exists(fileName)); - QVERIFY(!KDesktopFile::isAuthorizedDesktopFile(fileName)); - -- const QString installedFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("/kservices5/http_cache_cleaner.desktop")); -+ const QString installedFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("/kservices5/http_cache_cleaner.desktop")); - if (!installedFile.isEmpty()) { - QVERIFY(KDesktopFile::isAuthorizedDesktopFile(installedFile)); - } else { -@@ -281,8 +281,8 @@ void KDesktopFileTest::testLocateLocal_data() - { - QString systemConfigLocation = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation).last(); - QString writableConfigLocation = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); -- QString systemDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).last(); -- QString writableDataLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); -+ QString systemDataLocation = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).last(); -+ QString writableDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - - QTest::addColumn("path"); - QTest::addColumn("result"); -diff --git a/autotests/test_kconf_update.cpp b/autotests/test_kconf_update.cpp -index 3353061..a80ae1e 100644 ---- a/autotests/test_kconf_update.cpp -+++ b/autotests/test_kconf_update.cpp -@@ -625,7 +625,7 @@ void TestKConfUpdate::testScript() - - QSharedPointer updFile(writeUpdFile(updContent)); - -- const QString scriptDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kconf_update"; -+ const QString scriptDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/kconf_update"; - QVERIFY(QDir().mkpath(scriptDir)); - QString scriptPath = scriptDir + "/test.sh"; - writeFile(scriptPath, updScript); diff --git a/docs/options.md b/docs/options.md -index fab22e1..8823818 100644 +index c634c00..5e59219 100644 --- a/docs/options.md +++ b/docs/options.md @@ -96,4 +96,4 @@ They are: * `$QT_CACHE_HOME` - QStandardPaths::GenericCacheLocation * `$QT_CONFIG_HOME` - QStandardPaths::GenericConfigLocation -* `$QT_DATA_HOME` - QStandardPaths::GenericDataLocation +* `$QT_DATA_HOME` - QStandardPaths::AppDataLocation diff --git a/src/core/kconfig.cpp b/src/core/kconfig.cpp -index c8eb90a..18d1b69 100644 +index e1b11ed..e3511cf 100644 --- a/src/core/kconfig.cpp +++ b/src/core/kconfig.cpp -@@ -229,7 +229,7 @@ QString KConfigPrivate::expandString(const QString &value) +@@ -237,7 +237,7 @@ QString KConfigPrivate::expandString(const QString &value) env = QString::fromLocal8Bit(pEnv.constData()); } else { if (aVarName == QStringLiteral("QT_DATA_HOME")) { - env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); + env = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } else if (aVarName == QStringLiteral("QT_CONFIG_HOME")) { env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); } else if (aVarName == QStringLiteral("QT_CACHE_HOME")) { diff --git a/src/core/kdesktopfile.cpp b/src/core/kdesktopfile.cpp -index b0b6a87..daddd54 100644 +index d9283ce..f869fb0 100644 --- a/src/core/kdesktopfile.cpp +++ b/src/core/kdesktopfile.cpp -@@ -88,7 +88,7 @@ QString KDesktopFile::locateLocal(const QString &path) +@@ -89,8 +89,8 @@ QString KDesktopFile::locateLocal(const QString &path) } } // Relative to xdg data dir? (much more common) -- Q_FOREACH (const QString &dir, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { -+ Q_FOREACH (const QString &dir, QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)) { +- const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); +- for (const QString &dir : lstGenericDataLocation) { ++ const QStringList lstAppDataLocation = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); ++ for (const QString &dir : lstAppDataLocation) { if (path.startsWith(dir + plus)) { relativePath = path.mid(dir.length() + 1); } -@@ -97,7 +97,7 @@ QString KDesktopFile::locateLocal(const QString &path) +@@ -99,7 +99,7 @@ QString KDesktopFile::locateLocal(const QString &path) // What now? The desktop file doesn't come from XDG_DATA_DIRS. Use filename only and hope for the best. relativePath = path.mid(path.lastIndexOf(QLatin1Char('/')) + 1); } - return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + relativePath; + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + relativePath; } bool KDesktopFile::isDesktopFile(const QString &path) -@@ -134,7 +134,7 @@ bool KDesktopFile::isAuthorizedDesktopFile(const QString &path) +@@ -136,8 +136,8 @@ bool KDesktopFile::isAuthorizedDesktopFile(const QString &path) } } const QString servicesDir = QStringLiteral("kservices5/"); // KGlobal::dirs()->xdgDataRelativePath("services") -- Q_FOREACH (const QString &xdgDataPrefix, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { -+ Q_FOREACH (const QString &xdgDataPrefix, QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)) { +- const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); +- for (const QString &xdgDataPrefix : lstGenericDataLocation) { ++ const QStringList lstAppDataLocation = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); ++ for (const QString &xdgDataPrefix : lstAppDataLocation) { if (QDir(xdgDataPrefix).exists()) { const QString prefix = QFileInfo(xdgDataPrefix).canonicalFilePath(); if (realPath.startsWith(prefix + QLatin1Char('/') + servicesDir, sensitivity)) { diff --git a/src/kconf_update/kconf_update.cpp b/src/kconf_update/kconf_update.cpp -index ab7d946..eeaf6ca 100644 +index f6c7653..f9b83ac 100644 --- a/src/kconf_update/kconf_update.cpp +++ b/src/kconf_update/kconf_update.cpp -@@ -125,7 +125,7 @@ KonfUpdate::KonfUpdate(QCommandLineParser *parser) +@@ -145,7 +145,7 @@ KonfUpdate::KonfUpdate(QCommandLineParser *parser) m_bUseConfigInfo = false; if (parser->isSet(QStringLiteral("check"))) { m_bUseConfigInfo = true; - const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kconf_update/" + parser->value(QStringLiteral("check"))); + const QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "kconf_update/" + parser->value(QStringLiteral("check"))); if (file.isEmpty()) { qWarning("File '%s' not found.", parser->value(QStringLiteral("check")).toLocal8Bit().data()); - log() << "File '" << parser->value(QStringLiteral("check")) << "' passed on command line not found" << endl; -@@ -177,7 +177,7 @@ KonfUpdate::log() - { - if (!m_textStream) { - #if 0 -- QString dir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "kconf_update/log"; -+ QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + "kconf_update/log"; - QDir().mkpath(dir); - QString file = dir + "/update.log"; - m_file = new QFile(file); -@@ -206,7 +206,7 @@ QStringList KonfUpdate::findUpdateFiles(bool dirtyOnly) + qCDebug(KCONF_UPDATE_LOG) << "File" << parser->value(QStringLiteral("check")) << "passed on command line not found"; +@@ -190,7 +190,7 @@ QStringList KonfUpdate::findUpdateFiles(bool dirtyOnly) { QStringList result; - const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kconf_update"), QStandardPaths::LocateDirectory); + const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("kconf_update"), QStandardPaths::LocateDirectory); - Q_FOREACH (const QString &d, dirs) { + for (const QString &d : dirs) { const QDir dir(d); -@@ -760,7 +760,7 @@ void KonfUpdate::gotScript(const QString &_script) +@@ -751,7 +751,7 @@ void KonfUpdate::gotScript(const QString &_script) return; } - QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kconf_update/") + script); + QString path = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kconf_update/") + script); if (path.isEmpty()) { if (interpreter.isEmpty()) { path = CMAKE_INSTALL_PREFIX "/" LIB_INSTALL_DIR "/kconf_update_bin/" + script; diff --git a/3rdparty/ext_frameworks/ki18n-appdatalocation.diff b/3rdparty/ext_frameworks/ki18n-appdatalocation.diff index 7b548ef1da..0f8aa68083 100644 --- a/3rdparty/ext_frameworks/ki18n-appdatalocation.diff +++ b/3rdparty/ext_frameworks/ki18n-appdatalocation.diff @@ -1,22 +1,48 @@ +diff --git a/docs/programmers-guide.md b/docs/programmers-guide.md +index 9505366..65fc9a5 100644 +--- a/docs/programmers-guide.md ++++ b/docs/programmers-guide.md +@@ -2246,7 +2246,7 @@ then a call to + + ~~~ + QString splashPath = QStandardPaths::locate( +- QStandardPaths::GenericDataLocation, "splash.png"); ++ QStandardPaths::AppDataLocation, "splash.png"); + splashPath = KLocalizedString::localizedFilePath(splashPath); + ~~~ + diff --git a/src/kcatalog.cpp b/src/kcatalog.cpp -index c18d40f..b0ed09d 100644 +index c47ffac..e24eb84 100644 --- a/src/kcatalog.cpp +++ b/src/kcatalog.cpp -@@ -128,7 +128,7 @@ QString KCatalog::catalogLocaleDir(const QByteArray &domain, - } +@@ -144,7 +144,7 @@ QString KCatalog::catalogLocaleDir(const QByteArray &domain, } + return file; + #else +- const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("locale/") + relpath); ++ const QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("locale/") + relpath); -- QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, -+ QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, - QStringLiteral("locale/") + relpath); QString localeDir; - if (file.isEmpty()) { -@@ -143,7 +143,7 @@ QString KCatalog::catalogLocaleDir(const QByteArray &domain, + if (!file.isEmpty()) { +@@ -158,7 +158,7 @@ QString KCatalog::catalogLocaleDir(const QByteArray &domain, QSet KCatalog::availableCatalogLanguages(const QByteArray &domain_) { QString domain = QFile::decodeName(domain_); - QStringList localeDirPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + QStringList localeDirPaths = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory); +diff --git a/src/klocalizedstring.cpp b/src/klocalizedstring.cpp +index eb42c81..219bb51 100644 +--- a/src/klocalizedstring.cpp ++++ b/src/klocalizedstring.cpp +@@ -1363,7 +1363,7 @@ void KLocalizedStringPrivate::locateScriptingModule(const QByteArray &domain, + + // Try to find this module. + QString modapath = QStandardPaths::locate( +- QStandardPaths::GenericDataLocation, ++ QStandardPaths::AppDataLocation, + QLatin1String("locale") + QLatin1Char('/') + modrpath); + + // If the module exists and hasn't been already included. diff --git a/3rdparty/ext_frameworks/ki18n.diff b/3rdparty/ext_frameworks/ki18n.diff deleted file mode 100644 index fdf19a06a6..0000000000 --- a/3rdparty/ext_frameworks/ki18n.diff +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 178117e..497395d 100755 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -54,11 +54,11 @@ if(MSVC) - endif() - - add_definitions(-DTRANSLATION_DOMAIN=\"ki18n5\") --if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") -- ki18n_install(po) --endif() -+#if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") -+# ki18n_install(po) -+#endif() - add_subdirectory(src) --add_subdirectory(autotests) -+#add_subdirectory(autotests) - - # create a Config.cmake and a ConfigVersion.cmake file and install them - set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5I18n") -diff --git a/cmake/KF5I18NMacros.cmake b/cmake/KF5I18NMacros.cmake -index 53ba812..db8ea98 100755 ---- a/cmake/KF5I18NMacros.cmake -+++ b/cmake/KF5I18NMacros.cmake -@@ -25,14 +25,9 @@ - # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - # SUCH DAMAGE. - --find_package(Gettext REQUIRED) --find_package(PythonInterp REQUIRED) -- --set(_ki18n_pmap_compile_script ${CMAKE_CURRENT_LIST_DIR}/ts-pmap-compile.py) --set(_ki18n_uic_script ${CMAKE_CURRENT_LIST_DIR}/kf5i18nuic.cmake) -- - #create the implementation files from the ui files and add them to the list of sources - #usage: KI18N_WRAP_UI(foo_SRCS ${ui_files}) -+set(_ki18n_uic_script ${CMAKE_CURRENT_LIST_DIR}/kf5i18nuic.cmake) - macro (KI18N_WRAP_UI _sources ) - foreach (_current_FILE ${ARGN}) - -@@ -57,6 +52,13 @@ macro (KI18N_WRAP_UI _sources ) - endforeach (_current_FILE) - endmacro (KI18N_WRAP_UI) - -+find_package(Gettext) -+find_package(PythonInterp) -+ -+if (Gettext_FOUND AND PythonInterp_FOUND) -+ -+set(_ki18n_pmap_compile_script ${CMAKE_CURRENT_LIST_DIR}/ts-pmap-compile.py) -+ - #install the scripts for a given language in the target folder - #usage: KI18N_INSTALL_TS_FILES("ja" ${scripts_dir}) - function(KI18N_INSTALL_TS_FILES lang scripts_dir) -@@ -225,3 +227,7 @@ function(_KI18N_GETTEXT_GET_UNIQUE_TARGET_NAME _name _unique_name) - set_property(GLOBAL PROPERTY ${propertyName} ${currentCounter} ) - endfunction() - # End of CMake copied code #################################################### -+ -+else() -+ message("Warning: Python and Gettext are needed for some functionality.") -+endif() # Python and gettext found diff --git a/3rdparty/ext_frameworks/kwindowsystem-x11.diff b/3rdparty/ext_frameworks/kwindowsystem-x11.diff index b3620e15d6..e7aa0a3399 100644 --- a/3rdparty/ext_frameworks/kwindowsystem-x11.diff +++ b/3rdparty/ext_frameworks/kwindowsystem-x11.diff @@ -1,53 +1,53 @@ diff --git a/src/kstartupinfo.cpp b/src/kstartupinfo.cpp -index a97b8b5..0ae7e5b 100644 +index 76ab3be..014ab36 100644 --- a/src/kstartupinfo.cpp +++ b/src/kstartupinfo.cpp -@@ -493,7 +493,7 @@ bool KStartupInfo::sendStartupX(Display *disp_P, const KStartupInfoId &id_P, +@@ -490,7 +490,7 @@ bool KStartupInfo::sendStartupX(Display *disp_P, const KStartupInfoId &id_P, #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif - return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); + return true; // KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) Q_UNUSED(data_P) -@@ -548,7 +548,7 @@ bool KStartupInfo::sendChangeX(Display *disp_P, const KStartupInfoId &id_P, +@@ -565,7 +565,7 @@ bool KStartupInfo::sendChangeX(Display *disp_P, const KStartupInfoId &id_P, #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif - return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); + return true; //KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) Q_UNUSED(data_P) -@@ -580,7 +580,7 @@ bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P) +@@ -617,7 +617,7 @@ bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P) #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif - return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); + return true; //KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) return true; -@@ -615,7 +615,7 @@ bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P, +@@ -668,7 +668,7 @@ bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P, #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif - return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); + return true; //KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) Q_UNUSED(id_P) diff --git a/src/platforms/xcb/kwindowsystem.cpp b/src/platforms/xcb/kwindowsystem.cpp -index 9d28704..603d9b2 100644 +index 6789b7c..84b6d4c 100644 --- a/src/platforms/xcb/kwindowsystem.cpp +++ b/src/platforms/xcb/kwindowsystem.cpp -@@ -196,7 +196,7 @@ bool NETEventFilter::nativeEventFilter(xcb_generic_event_t *ev) +@@ -234,7 +234,7 @@ bool NETEventFilter::nativeEventFilter(xcb_generic_event_t *ev) int old_number_of_desktops = numberOfDesktops(); bool old_showing_desktop = showingDesktop(); unsigned long m[ 5 ]; - NETRootInfo::event(ev, m, 5); + //NETRootInfo::event(ev, m, 5); if ((m[ PROTOCOLS ] & CurrentDesktop) && currentDesktop() != old_current_desktop) { emit s_q->currentDesktopChanged(currentDesktop()); diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index dd5f353076..3a7c52c57a 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,291 +1,291 @@ SET(EXTPREFIX_qt "${EXTPREFIX}") if (WIN32) list(APPEND _QT_conf -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtxmlpatterns -no-sql-sqlite -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard # -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -openssl-linked -I ${EXTPREFIX_qt}/include -L ${EXTPREFIX_qt}/lib # -opensource -confirm-license # -release -platform win32-g++ -prefix ${EXTPREFIX_qt} QMAKE_LFLAGS_APP+=${SECURITY_EXE_LINKER_FLAGS} QMAKE_LFLAGS_SHLIB+=${SECURITY_SHARED_LINKER_FLAGS} QMAKE_LFLAGS_SONAME+=${SECURITY_SHARED_LINKER_FLAGS} ) if (QT_ENABLE_DEBUG_INFO) # Set the option to build Qt with debugging info enabled list(APPEND _QT_conf -force-debug-info) endif(QT_ENABLE_DEBUG_INFO) if (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl dynamic -angle) else (QT_ENABLE_DYNAMIC_OPENGL) list(APPEND _QT_conf -opengl desktop -no-angle) endif (QT_ENABLE_DYNAMIC_OPENGL) # MIME-type optimization patches set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Use-fast-path-for-unsupported-mime-types.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Hack-always-return-we-support-DIBV5.patch ) # Tablet support patches if (NOT USE_QT_TABLET_WINDOWS) set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-disable-wintab.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/disable-winink.patch ) else() set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0023-Implement-a-switch-for-tablet-API-on-Windows.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0025-Disable-tablet-relative-mode-in-Qt.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0050-Fix-using-tablet-on-QML-widgets.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0051-Add-workaround-for-handling-table-press-correctly-in.patch ) endif() # HDR patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Implement-openGL-surface-color-space-selection-in-An.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Implement-color-space-selection-for-QSurfaceFormat.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-Implement-color-conversion-for-the-backing-store-tex.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch ) # Other patches set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch COMMAND ${PATCH_COMMAND} -p1 -d qttools -i ${CMAKE_CURRENT_SOURCE_DIR}/windeployqt-force-allow-debug-info.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0031-Compute-logical-DPI-on-a-per-screen-basis.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0032-Update-Dpi-and-scale-factor-computation.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0034-Update-QT_SCREEN_SCALE_FACTORS.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch # COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch ) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz - URL_MD5 dda95b0239d13c5276834177af3a8588 + URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.zip + URL_MD5 16526e08adfad46e8e686b8af984b9b5 PATCH_COMMAND ${ext_qt_PATCH_COMMAND} INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure.bat ${_QT_conf} BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install UPDATE_COMMAND "" # Use a short name to reduce the chance of exceeding path length limit SOURCE_DIR s BINARY_DIR b DEPENDS ext_patch ext_openssl ) elseif (ANDROID) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz URL_MD5 99c2eb46e533371798b4ca2d1458e065 PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/show-proper-error.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/workaround-null-eglconfig-crash.patch CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -verbose -nomake examples -nomake tests -nomake tools -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia -android-sdk ${ANDROID_SDK_ROOT} -android-ndk ${CMAKE_ANDROID_NDK} -android-arch ${ANDROID_ABI} -xplatform android-clang -android-ndk-platform android-21 -make libs INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) elseif (NOT APPLE) ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.12/5.12.4/single/qt-everywhere-src-5.12.4.tar.xz URL_MD5 dda95b0239d13c5276834177af3a8588 PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -openssl-linked -verbose -nomake examples -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia INSTALL_DIR ${EXTPREFIX_qt} BUILD_COMMAND $(MAKE) INSTALL_COMMAND $(MAKE) install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) else( APPLE ) # XCODE_VERSION is set by CMake when using the Xcode generator, otherwise we need # to detect it manually here. if (NOT XCODE_VERSION) execute_process( COMMAND xcodebuild -version OUTPUT_VARIABLE xcodebuild_version OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_FILE /dev/null ) string(REGEX MATCH "Xcode ([0-9]+([.][0-9]+)*)" version_match ${xcodebuild_version}) if (version_match) message(STATUS "${EXTPREFIX_qt}:Identified Xcode Version: ${CMAKE_MATCH_1}") set(XCODE_VERSION ${CMAKE_MATCH_1}) else() # If detecting Xcode version failed, set a crazy high version so we default # to the newest. set(XCODE_VERSION 99) message(WARNING "${EXTPREFIX_qt}:Failed to detect the version of an installed copy of Xcode, falling back to highest supported version. Set XCODE_VERSION to override.") endif(version_match) endif(NOT XCODE_VERSION) # ------------------------------------------------------------------------------- # Verify the Xcode installation on Mac OS like Qt5.7 does/will # If not stop now, the system isn't configured correctly for Qt. # No reason to even proceed. # ------------------------------------------------------------------------------- set(XCSELECT_OUTPUT) find_program(XCSELECT_PROGRAM "xcode-select") if(XCSELECT_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCSELECT_PROGRAM as ${XCSELECT_PROGRAM}") set(XCSELECT_COMMAND ${XCSELECT_PROGRAM} "--print-path") execute_process( COMMAND ${XCSELECT_COMMAND} RESULT_VARIABLE XCSELECT_COMMAND_RESULT OUTPUT_VARIABLE XCSELECT_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCSELECT_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCSELECT_COMMAND_OUTPUT ${XCSELECT_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCSELECT_COMMAND_STR "${XCSELECT_COMMAND}") # message(STATUS "${XCSELECT_COMMAND_STR}") message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} test failed with status ${XCSELECT_COMMAND_RESULT}") endif() else() message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} not found. No Xcode is selected. Use xcode-select -switch to choose an Xcode version") endif() # Belts and suspenders # Beyond all the Xcode and Qt version checking, the proof of the pudding # lies in the success/failure of this command: xcrun --find xcrun. # On failure a patch is necessary, otherwise we're ok # So hard check xcrun now... set(XCRUN_OUTPUT) find_program(XCRUN_PROGRAM "xcrun") if(XCRUN_PROGRAM) message(STATUS "${EXTPREFIX_qt}:Found XCRUN_PROGRAM as ${XCRUN_PROGRAM}") set(XCRUN_COMMAND ${XCRUN_PROGRAM} "--find xcrun") execute_process( COMMAND ${XCRUN_COMMAND} RESULT_VARIABLE XCRUN_COMMAND_RESULT OUTPUT_VARIABLE XCRUN_COMMAND_OUTPUT ERROR_FILE /dev/null ) if(NOT XCRUN_COMMAND_RESULT) # returned 0, we're ok. string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" XCRUN_COMMAND_OUTPUT ${XCRUN_COMMAND_OUTPUT}) else() string(REPLACE ";" " " XCRUN_COMMAND_STR "${XCRUN_COMMAND}") # message(STATUS "${XCRUN_COMMAND_STR}") message(STATUS "${EXTPREFIX_qt}:xcrun test failed with status ${XCRUN_COMMAND_RESULT}") endif() else() message(STATUS "${EXTPREFIX_qt}:xcrun not found -- ${XCRUN_PROGRAM}") endif() # # Now configure ext_qt accordingly # if ((XCRUN_COMMAND_RESULT) AND (NOT (XCODE_VERSION VERSION_LESS 8.0.0))) # Fix Xcode xcrun related issue. # NOTE: This should be fixed by Qt 5.7.1 see here: http://code.qt.io/cgit/qt/qtbase.git/commit/?h=dev&id=77a71c32c9d19b87f79b208929e71282e8d8b5d9 # NOTE: but no one's holding their breath. set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND}} -p1 -b -d /qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/mac_standardpaths_qtbug-61159.diff COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch #COMMAND ${PATCH_COMMAND} -p1 -b -d /qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch ) message(STATUS "${EXTPREFIX_qt}:Additional patches injected.") else() # No extra patches will be applied # NOTE: defaults for some untested scenarios like xcrun fails and xcode_version < 8. # NOTE: that is uncharted territory and (hopefully) a very unlikely scenario... set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch ) endif() # Qt is big - try and parallelize if at all possible include(ProcessorCount) ProcessorCount(NUM_CORES) if(NOT NUM_CORES EQUAL 0) if (NUM_CORES GREATER 2) # be nice... MATH( EXPR NUM_CORES "${NUM_CORES} - 2" ) endif() set(PARALLEL_MAKE "make;-j${NUM_CORES}") message(STATUS "${EXTPREFIX_qt}:Parallelized make: ${PARALLEL_MAKE}") else() set(PARALLEL_MAKE "make") endif() ExternalProject_Add( ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} LOG_DOWNLOAD ON LOG_UPDATE ON LOG_CONFIGURE ON LOG_BUILD ON LOG_TEST ON LOG_INSTALL ON BUILD_IN_SOURCE ON URL https://download.qt.io/archive/qt/5.12/5.12.3/single/qt-everywhere-src-5.12.3.tar.xz URL_MD5 38017e0ed88b9baba063bd723d9ca8b2 CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' INSTALL_DIR ${EXTPREFIX_qt} CONFIGURE_COMMAND /configure -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland -skip qtwebchannel -skip qtwebsockets -skip qtwebview -skip qtwebengine -skip qtxmlpatterns -no-sql-sqlite -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth -skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -nomake examples -nomake tools -no-compile-examples -no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -system-zlib -qt-pcre -opensource -confirm-license -openssl-linked -prefix ${EXTPREFIX_qt} BUILD_COMMAND ${PARALLEL_MAKE} INSTALL_COMMAND make install UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index d87798a32b..baa079c32b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,927 +1,929 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.18.0) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.11 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) add_definitions(-Wno-suggest-override -Wextra) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.3.0-prealpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 3) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem Archive ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) if (ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS AndroidExtras ) endif() if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) find_package(XCB COMPONENTS XCB ATOM) set(HAVE_XCB ${XCB_FOUND}) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) set(HAVE_XCB FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING + -DQT_USE_FAST_CONCATENATION + -DQT_USE_FAST_OPERATOR_PLUS ) if (${Qt5_VERSION} VERSION_GREATER "5.8.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800) elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700) else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600) endif() add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### # FIXME: Still hardcoded if (ANDROID) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) endif() find_package(PNG REQUIRED) list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY}) if (APPLE) # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "http://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "http://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") if (TIFF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY}) endif() find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") if (JPEG_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY}) endif() find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") if (GIF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY}) endif() find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "http://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) if (ANDROID) list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES}) # because libexiv2 depends on libexpat and it is installed in the same folder get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY) list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so) endif() ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES}) ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so ${KF5_LIBRARIES}/libKF5Archive.so) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() elseif(ANDROID) message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/benchmarks/kis_composition_benchmark.cpp b/benchmarks/kis_composition_benchmark.cpp index efb4f1e9e7..4065a1adfb 100644 --- a/benchmarks/kis_composition_benchmark.cpp +++ b/benchmarks/kis_composition_benchmark.cpp @@ -1,951 +1,949 @@ /* * Copyright (c) 2012 Dmitry Kazakov * Copyright (c) 2015 Thorsten Zachmann * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // for calculation of the needed alignment #include #ifdef HAVE_VC #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #include #include #include #endif #include "kis_composition_benchmark.h" #include #include #include #include #include #include #include #include #include // for posix_memalign() #include #include #if defined _MSC_VER #define MEMALIGN_ALLOC(p, a, s) ((*(p)) = _aligned_malloc((s), (a)), *(p) ? 0 : errno) #define MEMALIGN_FREE(p) _aligned_free((p)) #else #define MEMALIGN_ALLOC(p, a, s) posix_memalign((p), (a), (s)) #define MEMALIGN_FREE(p) free((p)) #endif -const int alpha_pos = 3; - enum AlphaRange { ALPHA_ZERO, ALPHA_UNIT, ALPHA_RANDOM }; template inline channel_type generateAlphaValue(AlphaRange range, RandomGenerator &rnd) { channel_type value = 0; switch (range) { case ALPHA_ZERO: break; case ALPHA_UNIT: value = rnd.unit(); break; case ALPHA_RANDOM: value = rnd(); break; } return value; } #include #include #include template struct RandomGenerator { channel_type operator() () { qFatal("Wrong template instantiation"); return channel_type(0); } channel_type unit() { qFatal("Wrong template instantiation"); return channel_type(0); } }; template <> struct RandomGenerator { RandomGenerator(int seed) : m_smallint(0,255), m_rnd(seed) { } quint8 operator() () { return m_smallint(m_rnd); } quint8 unit() { return KoColorSpaceMathsTraits::unitValue; } boost::uniform_smallint m_smallint; boost::mt11213b m_rnd; }; template <> struct RandomGenerator { RandomGenerator(int seed) : m_rnd(seed) { } float operator() () { //return float(m_rnd()) / float(m_rnd.max()); return m_smallfloat(m_rnd); } float unit() { return KoColorSpaceMathsTraits::unitValue; } boost::uniform_real m_smallfloat; boost::mt11213b m_rnd; }; template <> struct RandomGenerator : RandomGenerator { RandomGenerator(int seed) : RandomGenerator(seed) { } }; template void generateDataLine(uint seed, int numPixels, quint8 *srcPixels, quint8 *dstPixels, quint8 *mask, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange) { Q_ASSERT(numPixels >= 4); RandomGenerator rnd(seed); RandomGenerator maskRnd(seed + 1); channel_type *srcArray = reinterpret_cast(srcPixels); channel_type *dstArray = reinterpret_cast(dstPixels); for (int i = 0; i < numPixels; i++) { for (int j = 0; j < 3; j++) { channel_type s = rnd(); channel_type d = rnd(); *(srcArray++) = s; *(dstArray++) = d; } channel_type sa = generateAlphaValue(srcAlphaRange, rnd); channel_type da = generateAlphaValue(dstAlphaRange, rnd); *(srcArray++) = sa; *(dstArray++) = da; *(mask++) = maskRnd(); } } void printData(int numPixels, quint8 *srcPixels, quint8 *dstPixels, quint8 *mask) { for (int i = 0; i < numPixels; i++) { qDebug() << "Src: " << srcPixels[i*4] << "\t" << srcPixels[i*4+1] << "\t" << srcPixels[i*4+2] << "\t" << srcPixels[i*4+3] << "\t" << "Msk:" << mask[i]; qDebug() << "Dst: " << dstPixels[i*4] << "\t" << dstPixels[i*4+1] << "\t" << dstPixels[i*4+2] << "\t" << dstPixels[i*4+3]; } } const int rowStride = 64; const int totalRows = 64; const QRect processRect(0,0,64,64); const int numPixels = rowStride * totalRows; const int numTiles = 1024; struct Tile { quint8 *src; quint8 *dst; quint8 *mask; }; #include QVector generateTiles(int size, const int srcAlignmentShift, const int dstAlignmentShift, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange, const quint32 pixelSize) { QVector tiles(size); #ifdef HAVE_VC const int vecSize = Vc::float_v::size(); #else const int vecSize = 1; #endif // the 256 are used to make sure that we have a good alignment no matter what build options are used. const size_t pixelAlignment = qMax(size_t(vecSize * sizeof(float)), size_t(256)); const size_t maskAlignment = qMax(size_t(vecSize), size_t(256)); for (int i = 0; i < size; i++) { void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, pixelAlignment, numPixels * pixelSize + srcAlignmentShift); if (error) { qFatal("posix_memalign failed: %d", error); } tiles[i].src = (quint8*)ptr + srcAlignmentShift; error = MEMALIGN_ALLOC(&ptr, pixelAlignment, numPixels * pixelSize + dstAlignmentShift); if (error) { qFatal("posix_memalign failed: %d", error); } tiles[i].dst = (quint8*)ptr + dstAlignmentShift; error = MEMALIGN_ALLOC(&ptr, maskAlignment, numPixels); if (error) { qFatal("posix_memalign failed: %d", error); } tiles[i].mask = (quint8*)ptr; if (pixelSize == 4) { generateDataLine(1, numPixels, tiles[i].src, tiles[i].dst, tiles[i].mask, srcAlphaRange, dstAlphaRange); } else if (pixelSize == 16) { generateDataLine(1, numPixels, tiles[i].src, tiles[i].dst, tiles[i].mask, srcAlphaRange, dstAlphaRange); } else { qFatal("Pixel size %i is not implemented", pixelSize); } } return tiles; } void freeTiles(QVector tiles, const int srcAlignmentShift, const int dstAlignmentShift) { Q_FOREACH (const Tile &tile, tiles) { MEMALIGN_FREE(tile.src - srcAlignmentShift); MEMALIGN_FREE(tile.dst - dstAlignmentShift); MEMALIGN_FREE(tile.mask); } } template inline bool fuzzyCompare(channel_type a, channel_type b, channel_type prec) { return qAbs(a - b) <= prec; } template inline bool comparePixels(channel_type *p1, channel_type *p2, channel_type prec) { return (p1[3] == p2[3] && p1[3] == 0) || (fuzzyCompare(p1[0], p2[0], prec) && fuzzyCompare(p1[1], p2[1], prec) && fuzzyCompare(p1[2], p2[2], prec) && fuzzyCompare(p1[3], p2[3], prec)); } template bool compareTwoOpsPixels(QVector &tiles, channel_type prec) { channel_type *dst1 = reinterpret_cast(tiles[0].dst); channel_type *dst2 = reinterpret_cast(tiles[1].dst); channel_type *src1 = reinterpret_cast(tiles[0].src); channel_type *src2 = reinterpret_cast(tiles[1].src); for (int i = 0; i < numPixels; i++) { if (!comparePixels(dst1, dst2, prec)) { qDebug() << "Wrong result:" << i; qDebug() << "Act: " << dst1[0] << dst1[1] << dst1[2] << dst1[3]; qDebug() << "Exp: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; qDebug() << "Dif: " << dst1[0] - dst2[0] << dst1[1] - dst2[1] << dst1[2] - dst2[2] << dst1[3] - dst2[3]; channel_type *s1 = src1 + 4 * i; channel_type *s2 = src2 + 4 * i; qDebug() << "SrcA:" << s1[0] << s1[1] << s1[2] << s1[3]; qDebug() << "SrcE:" << s2[0] << s2[1] << s2[2] << s2[3]; qDebug() << "MskA:" << tiles[0].mask[i]; qDebug() << "MskE:" << tiles[1].mask[i]; return false; } dst1 += 4; dst2 += 4; } return true; } bool compareTwoOps(bool haveMask, const KoCompositeOp *op1, const KoCompositeOp *op2) { Q_ASSERT(op1->colorSpace()->pixelSize() == op2->colorSpace()->pixelSize()); const quint32 pixelSize = op1->colorSpace()->pixelSize(); const int alignment = 16; QVector tiles = generateTiles(2, alignment, alignment, ALPHA_RANDOM, ALPHA_RANDOM, op1->colorSpace()->pixelSize()); KoCompositeOp::ParameterInfo params; params.dstRowStride = 4 * rowStride; params.srcRowStride = 4 * rowStride; params.maskRowStride = rowStride; params.rows = processRect.height(); params.cols = processRect.width(); // This is a hack as in the old version we get a rounding of opacity to this value params.opacity = float(Arithmetic::scale(0.5*1.0f))/255.0; params.flow = 0.3*1.0f; params.channelFlags = QBitArray(); params.dstRowStart = tiles[0].dst; params.srcRowStart = tiles[0].src; params.maskRowStart = haveMask ? tiles[0].mask : 0; op1->composite(params); params.dstRowStart = tiles[1].dst; params.srcRowStart = tiles[1].src; params.maskRowStart = haveMask ? tiles[1].mask : 0; op2->composite(params); bool compareResult = true; if (pixelSize == 4) { compareResult = compareTwoOpsPixels(tiles, 10); } else if (pixelSize == 16) { compareResult = compareTwoOpsPixels(tiles, 2e-7); } else { qFatal("Pixel size %i is not implemented", pixelSize); } freeTiles(tiles, alignment, alignment); return compareResult; } QString getTestName(bool haveMask, const int srcAlignmentShift, const int dstAlignmentShift, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange) { QString testName; testName += !srcAlignmentShift && !dstAlignmentShift ? "Aligned " : !srcAlignmentShift && dstAlignmentShift ? "SrcUnalig " : srcAlignmentShift && !dstAlignmentShift ? "DstUnalig " : srcAlignmentShift && dstAlignmentShift ? "Unaligned " : "###"; testName += haveMask ? "Mask " : "NoMask "; testName += srcAlphaRange == ALPHA_RANDOM ? "SrcRand " : srcAlphaRange == ALPHA_ZERO ? "SrcZero " : srcAlphaRange == ALPHA_UNIT ? "SrcUnit " : "###"; testName += dstAlphaRange == ALPHA_RANDOM ? "DstRand" : dstAlphaRange == ALPHA_ZERO ? "DstZero" : dstAlphaRange == ALPHA_UNIT ? "DstUnit" : "###"; return testName; } void benchmarkCompositeOp(const KoCompositeOp *op, bool haveMask, qreal opacity, qreal flow, const int srcAlignmentShift, const int dstAlignmentShift, AlphaRange srcAlphaRange, AlphaRange dstAlphaRange) { QString testName = getTestName(haveMask, srcAlignmentShift, dstAlignmentShift, srcAlphaRange, dstAlphaRange); QVector tiles = generateTiles(numTiles, srcAlignmentShift, dstAlignmentShift, srcAlphaRange, dstAlphaRange, op->colorSpace()->pixelSize()); const int tileOffset = 4 * (processRect.y() * rowStride + processRect.x()); KoCompositeOp::ParameterInfo params; params.dstRowStride = 4 * rowStride; params.srcRowStride = 4 * rowStride; params.maskRowStride = rowStride; params.rows = processRect.height(); params.cols = processRect.width(); params.opacity = opacity; params.flow = flow; params.channelFlags = QBitArray(); QTime timer; timer.start(); Q_FOREACH (const Tile &tile, tiles) { params.dstRowStart = tile.dst + tileOffset; params.srcRowStart = tile.src + tileOffset; params.maskRowStart = haveMask ? tile.mask : 0; op->composite(params); } qDebug() << testName << "RESULT:" << timer.elapsed() << "msec"; freeTiles(tiles, srcAlignmentShift, dstAlignmentShift); } void benchmarkCompositeOp(const KoCompositeOp *op, const QString &postfix) { qDebug() << "Testing Composite Op:" << op->id() << "(" << postfix << ")"; benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, true, 0.5, 0.3, 8, 0, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 8, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, true, 0.5, 0.3, 4, 8, ALPHA_RANDOM, ALPHA_RANDOM); /// --- Vary the content of the source and destination benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_ZERO, ALPHA_RANDOM); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_UNIT, ALPHA_RANDOM); /// --- benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_RANDOM, ALPHA_ZERO); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_ZERO, ALPHA_ZERO); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_UNIT, ALPHA_ZERO); /// --- benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_RANDOM, ALPHA_UNIT); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_ZERO, ALPHA_UNIT); benchmarkCompositeOp(op, false, 1.0, 1.0, 0, 0, ALPHA_UNIT, ALPHA_UNIT); } #ifdef HAVE_VC template void checkRounding(qreal opacity, qreal flow, qreal averageOpacity = -1, quint32 pixelSize = 4) { QVector tiles = generateTiles(2, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM, pixelSize); const int vecSize = Vc::float_v::size(); const int numBlocks = numPixels / vecSize; quint8 *src1 = tiles[0].src; quint8 *dst1 = tiles[0].dst; quint8 *msk1 = tiles[0].mask; quint8 *src2 = tiles[1].src; quint8 *dst2 = tiles[1].dst; quint8 *msk2 = tiles[1].mask; KoCompositeOp::ParameterInfo params; params.opacity = opacity; params.flow = flow; if (averageOpacity >= 0.0) { params._lastOpacityData = averageOpacity; params.lastOpacity = ¶ms._lastOpacityData; } params.channelFlags = QBitArray(); typename Compositor::ParamsWrapper paramsWrapper(params); // The error count is needed as 38.5 gets rounded to 38 instead of 39 in the vc version. int errorcount = 0; for (int i = 0; i < numBlocks; i++) { Compositor::template compositeVector(src1, dst1, msk1, params.opacity, paramsWrapper); for (int j = 0; j < vecSize; j++) { //if (8 * i + j == 7080) { // qDebug() << "src: " << src2[0] << src2[1] << src2[2] << src2[3]; // qDebug() << "dst: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; // qDebug() << "msk:" << msk2[0]; //} Compositor::template compositeOnePixelScalar(src2, dst2, msk2, params.opacity, paramsWrapper); bool compareResult = true; if (pixelSize == 4) { compareResult = comparePixels(dst1, dst2, 0); if (!compareResult) { ++errorcount; compareResult = comparePixels(dst1, dst2, 1); if (!compareResult) { ++errorcount; } } } else if (pixelSize == 16) { compareResult = comparePixels(reinterpret_cast(dst1), reinterpret_cast(dst2), 0); } else { qFatal("Pixel size %i is not implemented", pixelSize); } if(!compareResult || errorcount > 1) { qDebug() << "Wrong rounding in pixel:" << 8 * i + j; qDebug() << "Vector version: " << dst1[0] << dst1[1] << dst1[2] << dst1[3]; qDebug() << "Scalar version: " << dst2[0] << dst2[1] << dst2[2] << dst2[3]; qDebug() << "src:" << src1[0] << src1[1] << src1[2] << src1[3]; qDebug() << "msk:" << msk1[0]; QFAIL("Wrong rounding"); } src1 += pixelSize; dst1 += pixelSize; src2 += pixelSize; dst2 += pixelSize; msk1++; msk2++; } } freeTiles(tiles, 0, 0); } #endif void KisCompositionBenchmark::checkRoundingAlphaDarken_05_03() { #ifdef HAVE_VC checkRounding >(0.5,0.3); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_05() { #ifdef HAVE_VC checkRounding >(0.5,0.5); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_07() { #ifdef HAVE_VC checkRounding >(0.5,0.7); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_10() { #ifdef HAVE_VC checkRounding >(0.5,1.0); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarken_05_10_08() { #ifdef HAVE_VC checkRounding >(0.5,1.0,0.8); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_03() { #ifdef HAVE_VC checkRounding >(0.5, 0.3, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_05() { #ifdef HAVE_VC checkRounding >(0.5, 0.5, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_07() { #ifdef HAVE_VC checkRounding >(0.5, 0.7, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_10() { #ifdef HAVE_VC checkRounding >(0.5, 1.0, -1, 16); #endif } void KisCompositionBenchmark::checkRoundingAlphaDarkenF32_05_10_08() { #ifdef HAVE_VC checkRounding >(0.5, 1.0, 0.8, 16); #endif } void KisCompositionBenchmark::checkRoundingOver() { #ifdef HAVE_VC checkRounding >(0.5, 0.3); #endif } void KisCompositionBenchmark::checkRoundingOverRgbaF32() { #ifdef HAVE_VC checkRounding >(0.5, 0.3, -1, 16); #endif } void KisCompositionBenchmark::compareAlphaDarkenOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs); KoCompositeOp *opExp = new KoCompositeOpAlphaDarken(cs); QVERIFY(compareTwoOps(true, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareRgbF32AlphaDarkenOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy128(cs); KoCompositeOp *opExp = new KoCompositeOpAlphaDarken(cs); QVERIFY(compareTwoOps(true, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareAlphaDarkenOpsNoMask() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs); KoCompositeOp *opExp = new KoCompositeOpAlphaDarken(cs); QVERIFY(compareTwoOps(false, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareOverOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createOverOp32(cs); KoCompositeOp *opExp = new KoCompositeOpOver(cs); QVERIFY(compareTwoOps(true, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareOverOpsNoMask() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createOverOp32(cs); KoCompositeOp *opExp = new KoCompositeOpOver(cs); QVERIFY(compareTwoOps(false, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::compareRgbF32OverOps() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createOverOp128(cs); KoCompositeOp *opExp = new KoCompositeOpOver(cs); QVERIFY(compareTwoOps(false, opAct, opExp)); delete opExp; delete opAct; } void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *op = new KoCompositeOpAlphaDarken(cs); benchmarkCompositeOp(op, "Legacy"); delete op; } void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *op = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy32(cs); benchmarkCompositeOp(op, "Optimized"); delete op; } void KisCompositionBenchmark::testRgb8CompositeOverLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *op = new KoCompositeOpOver(cs); benchmarkCompositeOp(op, "Legacy"); delete op; } void KisCompositionBenchmark::testRgb8CompositeOverOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoCompositeOp *op = KoOptimizedCompositeOpFactory::createOverOp32(cs); benchmarkCompositeOp(op, "Optimized"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeAlphaDarkenLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *op = new KoCompositeOpAlphaDarken(cs); benchmarkCompositeOp(op, "Legacy"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeAlphaDarkenOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *op = KoOptimizedCompositeOpFactory::createAlphaDarkenOpCreamy128(cs); benchmarkCompositeOp(op, "Optimized"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeOverLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *op = new KoCompositeOpOver(cs); benchmarkCompositeOp(op, "RGBF32 Legacy"); delete op; } void KisCompositionBenchmark::testRgbF32CompositeOverOptimized() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""); KoCompositeOp *op = KoOptimizedCompositeOpFactory::createOverOp128(cs); benchmarkCompositeOp(op, "RGBF32 Optimized"); delete op; } void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenReal_Aligned() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); const KoCompositeOp *op = cs->compositeOp(COMPOSITE_ALPHA_DARKEN); benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); } void KisCompositionBenchmark::testRgb8CompositeOverReal_Aligned() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); const KoCompositeOp *op = cs->compositeOp(COMPOSITE_OVER); benchmarkCompositeOp(op, true, 0.5, 0.3, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM); } void KisCompositionBenchmark::testRgb8CompositeCopyLegacy() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); const KoCompositeOp *op = cs->compositeOp(COMPOSITE_COPY); benchmarkCompositeOp(op, "Copy"); } void KisCompositionBenchmark::benchmarkMemcpy() { QVector tiles = generateTiles(numTiles, 0, 0, ALPHA_UNIT, ALPHA_UNIT, 4); QBENCHMARK_ONCE { Q_FOREACH (const Tile &tile, tiles) { memcpy(tile.dst, tile.src, 4 * numPixels); } } freeTiles(tiles, 0, 0); } #ifdef HAVE_VC const int vecSize = Vc::float_v::size(); const size_t uint8VecAlignment = qMax(vecSize * sizeof(quint8), sizeof(void*)); const size_t uint32VecAlignment = qMax(vecSize * sizeof(quint32), sizeof(void*)); const size_t floatVecAlignment = qMax(vecSize * sizeof(float), sizeof(void*)); #endif void KisCompositionBenchmark::benchmarkUintFloat() { #ifdef HAVE_VC using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint8VecAlignment, dataSize); if (error) { qFatal("posix_memalign failed: %d", error); } quint8 *iData = (quint8*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // convert uint -> float directly, this causes // static_cast helper be called Vc::float_v b(uint_v(iData + i)); b.store(fData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } void KisCompositionBenchmark::benchmarkUintIntFloat() { #ifdef HAVE_VC using int_v = Vc::SimdArray; using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint8VecAlignment, dataSize); if (error) { qFatal("posix_memalign failed: %d", error); } quint8 *iData = (quint8*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // convert uint->int->float, that avoids special sign // treating, and gives 2.6 times speedup Vc::float_v b(int_v(uint_v(iData + i))); b.store(fData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } void KisCompositionBenchmark::benchmarkFloatUint() { #ifdef HAVE_VC using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint32VecAlignment, dataSize * sizeof(quint32)); if (error) { qFatal("posix_memalign failed: %d", error); } quint32 *iData = (quint32*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // conversion float -> uint uint_v b(Vc::float_v(fData + i)); b.store(iData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } void KisCompositionBenchmark::benchmarkFloatIntUint() { #ifdef HAVE_VC using int_v = Vc::SimdArray; using uint_v = Vc::SimdArray; const int dataSize = 4096; void *ptr = 0; int error = MEMALIGN_ALLOC(&ptr, uint32VecAlignment, dataSize * sizeof(quint32)); if (error) { qFatal("posix_memalign failed: %d", error); } quint32 *iData = (quint32*)ptr; error = MEMALIGN_ALLOC(&ptr, floatVecAlignment, dataSize * sizeof(float)); if (error) { qFatal("posix_memalign failed: %d", error); } float *fData = (float*)ptr; QBENCHMARK { for (int i = 0; i < dataSize; i += Vc::float_v::size()) { // conversion float -> int -> uint uint_v b(int_v(Vc::float_v(fData + i))); b.store(iData + i); } } MEMALIGN_FREE(iData); MEMALIGN_FREE(fData); #endif } QTEST_MAIN(KisCompositionBenchmark) diff --git a/krita/data/input/kritadefault.profile b/krita/data/input/kritadefault.profile index da28ca5dd0..704fe28370 100644 --- a/krita/data/input/kritadefault.profile +++ b/krita/data/input/kritadefault.profile @@ -1,73 +1,73 @@ [Alternate Invocation] 0={1;2;[1000023,1000020];1;0;0} 1={0;2;[1000021,1000020];1;0;0} 2={5;2;[1000021];2;0;0} 3={3;2;[1000021,1000023];2;0;0} 4={2;2;[1000023,1000021];1;0;0} 5={4;2;[1000021];1;0;0} [Change Primary Setting] 0={0;2;[1000020];1;0;0} [Exposure or Gamma] 0={0;2;[59];1;0;0} [General] name=Krita Default -version=3 +version=4 [Pan Canvas] 0={0;4;[];0;0;2} 1={0;2;[20];1;0;0} 2={0;2;[];4;0;0} 3={1;1;[];0;0;0} 4={2;1;[];0;0;0} 5={3;1;[];0;0;0} 6={4;1;[];0;0;0} 7={0;3;[];0;5;0} [Rotate Canvas] 0={0;2;[1000020,20];1;0;0} 1={1;2;[1000020,1000023,20];1;0;0} 2={0;2;[1000020];4;0;0} 3={2;1;[34];0;0;0} 4={4;1;[35];0;0;0} 5={3;1;[36];0;0;0} 6={0;4;[];0;0;3} [Select Layer] 0={1;2;[1000020,52];1;0;0} 1={0;2;[52];1;0;0} [Show Popup Palette] 0={0;2;[];2;0;0} [Switch Time] 0={0;1;[1000014];0;0;0} 1={1;1;[1000012];0;0;0} [Tool Invocation] 0={3;2;[56];1;0;0} 1={1;1;[1000005];0;0;0} 2={0;2;[];1;0;0} 3={1;1;[1000004];0;0;0} 4={2;1;[1000000];0;0;0} [Zoom Canvas] 0={2;1;[2b];0;0;0} 1={4;1;[31];0;0;0} 10={5;1;[32];0;0;0} 11={0;4;[];0;0;1} 12={8;2;[1000021,1000023];4;0;0} 13={0;4;[];0;0;4} 2={3;1;[2d];0;0;0} 3={2;1;[3d];0;0;0} 4={3;3;[];0;2;0} 5={2;3;[];0;1;0} 6={7;2;[1000021];4;0;0} 7={7;2;[1000021,20];1;0;0} 8={6;1;[33];0;0;0} 9={8;2;[1000021,1000023,20];1;0;0} [Zoom and Rotate Canvas] 0={0;4;[];0;0;5} diff --git a/krita/data/templates/comics/Manga-JpTemplate.desktop b/krita/data/templates/comics/Manga-JpTemplate.desktop index faaad80bb6..55fe278cbb 100644 --- a/krita/data/templates/comics/Manga-JpTemplate.desktop +++ b/krita/data/templates/comics/Manga-JpTemplate.desktop @@ -1,85 +1,85 @@ [Desktop Entry] Type=Link URL=.source/Manga-JpTemplate.kra Icon=template_comics_empty Name=Manga template Name[ar]=قالب مانغا Name[bs]=Manga predložak Name[ca]=Plantilla per a manga Name[ca@valencia]=Plantilla per a manga Name[cs]=Šablona Mangy Name[da]=Manga-skabelon Name[de]=Manga-Vorlage Name[el]=Πρότυπο μάνγκα Name[en_GB]=Manga template Name[es]=Plantilla manga Name[et]=Manga mall Name[eu]=Manga-txantiloia Name[fi]=Mangapohja Name[fr]=Modèle de Manga Name[gl]=Formato Manga Name[hu]=Manga sablon Name[ia]=Patrono de Manga Name[is]=Manga sniðmát Name[it]=Modello manga Name[ja]=漫画テンプレート Name[kk]=Үлгіні басқару Name[ko]=망가 템플릿 Name[lt]=Manga šablonas Name[nb]=Manga-mal Name[nds]=Manga-Vörlaag Name[nl]=Manga-sjabloon -Name[nn]=Manga-mal +Name[nn]=Japansk teikneserie (manga) Name[pl]=Szablon Mangi Name[pt]=Modelo Manga Name[pt_BR]=Modelo de mangá Name[ru]=Шаблон манги Name[sk]=Manga šablóna Name[sl]=Predloga Manga Name[sv]=Manga-mall Name[tr]=Manga şablonu Name[uk]=Шаблон манґи Name[wa]=Modele di manga Name[x-test]=xxManga templatexx Name[zh_CN]=日式漫画模板 Name[zh_TW]=日本漫畫範本 Comment=template for Japanese Manga-style comics Comment[ar]=قالب للهزليّات بنمط المانغا اليابانية Comment[bs]=predložak za japanske Manga stripove Comment[ca]=Plantilla per a còmics d'estil manga japonès Comment[ca@valencia]=Plantilla per a còmics d'estil manga japonés Comment[cs]=šablona pro japonské komiksy ve stylu Manga Comment[da]=skabelon til tegneserier i japansk Manga-stil Comment[de]=Vorlage für Comics im Stil japanischer Mangas Comment[el]=Πρότυπο για Ιαπωνικά μάνγκα κόμικς Comment[en_GB]=template for Japanese Manga-style comics Comment[es]=plantilla para cómics de estilo manga japonés Comment[et]=Jaapani manga-stiilis koomiksi mall Comment[eu]=Japoniako Manga-estiloko komikietarako txantiloia Comment[fi]=japanilaisen mangatyylisen sarjakuvan pohja Comment[fr]=Modèle de mangas japonais Comment[gl]=Páxina de banda deseñada de formato Manga, con 2×3 viñetas non regulares. Comment[hu]=sablon a japán Manga-stílusú képregényekhez Comment[is]=Sniðmát fyrir japanskar Manga-teiknimyndir Comment[it]=modello per fumetti in stile manga giapponese Comment[ja]=日本式漫画用テンプレート Comment[kk]=Жапондық манга-стильдегі комикс үлгісі Comment[ko]=일본 망가 스타일 만화를 위한 템플릿 Comment[nb]=mal for japanske tegneserier i Manga-stil Comment[nds]=Vörlaag för japaansche Manga-Comics Comment[nl]=sjabloon voor strips in Japanse Manga-stijl Comment[nn]=Mal for teikneserie i japansk stil (manga) Comment[pl]=szablon dla Japońskiego stylu komiksów Mangi Comment[pt]=modelo de banda desenhada Manga do estilo Japonês Comment[pt_BR]=Modelo de quadrinhos no estilo mangá japonês Comment[ru]=Шаблон комиксов в японском стиле манга Comment[sk]=šablóna pre japonské manga komixy Comment[sl]=predloge za stripe v japonskem slogu Manga Comment[sv]=seriemall med japansk Manga-stil Comment[tr]=Japon Manga çizgi romanları için şablon Comment[uk]=шаблон японських коміксів у стилі манґа Comment[wa]=Modele di bindes d' imådje al môde des mangas djaponès Comment[x-test]=xxtemplate for Japanese Manga-style comicsxx Comment[zh_CN]=日式漫画模板 Comment[zh_TW]=日本 Manga-風格的連環漫畫範本 X-Krita-Version=28 diff --git a/krita/data/templates/texture/Texture1k32bitscalar.desktop b/krita/data/templates/texture/Texture1k32bitscalar.desktop index 89e91392cd..02bccd6e90 100755 --- a/krita/data/templates/texture/Texture1k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture1k32bitscalar.desktop @@ -1,40 +1,40 @@ [Desktop Entry] Icon=template_texture Name=Texture 1k 32bit scalar Name[bs]=Tekstura 1k 32bit scalar Name[ca]=Textura 1k de 32 bits escalar Name[ca@valencia]=Textura 1k de 32 bits escalar Name[cs]=Textura 1k 32bit skalární Name[da]=Tekstur 1k 32bit scalar Name[de]=Textur 1k 32bit scalar Name[el]=Υφή 1k 32bit βαθμωτό Name[en_GB]=Texture 1k 32bit scalar Name[es]=Textura 1k 32 bit escalar Name[et]=Tekstuur 1k 32bit skalaar Name[eu]=Ehundura 1k 32bit eskalarra Name[fi]=Pintakuvio 1k 32 bit skalaarinen Name[fr]=Texture 1k 32bit scalaire Name[gl]=Textura de 1k e 32 bits escalar Name[hu]=Textúra 1k 32bit skalár Name[is]=Efnisáferð 1k 32bita scalar Name[it]=Trama 1k 32bit scalare Name[ja]=テクスチャ 1k 32 ビットスカラー Name[kk]=Текстура 1k 32 бит скаляр Name[ko]=텍스처 1k 32비트 스칼라 Name[nb]=Tekstur 1k 32bit skalar Name[nl]=Textuur 1k 32bit scalar -Name[nn]=Tekstur 1k 32-bits skalar +Name[nn]=Tekstur 1k 32-bits gråtonar Name[pl]=Tekstura 1k 32bit skalar Name[pt]=Textura 1k 32-bits escalar Name[pt_BR]=Textura 1k 32bits escalar Name[ru]=Текстура 1k 32 бит scalar Name[sk]=Textúra 1k 32bit skalár Name[sv]=Struktur 1k 32-bitar skalär Name[tr]=Doku 1k 32bit sayısal Name[uk]=Текстура 1k, 32-бітова, скалярна Name[x-test]=xxTexture 1k 32bit scalarxx Name[zh_CN]=1K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 1k 32位元 scalar Type=Link URL[$e]=.source/Texture1k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture2k32bitscalar.desktop b/krita/data/templates/texture/Texture2k32bitscalar.desktop index e1be3d1c12..e118d9f648 100755 --- a/krita/data/templates/texture/Texture2k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture2k32bitscalar.desktop @@ -1,40 +1,40 @@ [Desktop Entry] Icon=template_texture Name=Texture 2k 32bit scalar Name[bs]=Tekstura 2k 32bit scalar Name[ca]=Textura 2k de 32 bits escalar Name[ca@valencia]=Textura 2k de 32 bits escalar Name[cs]=Textura 2k 32bit skalární Name[da]=Tekstur 2k 32bit scalar Name[de]=Textur 2k 32bit scalar Name[el]=Υφή 2k 32bit βαθμωτό Name[en_GB]=Texture 2k 32bit scalar Name[es]=Textura 2k 32bit escalar Name[et]=Tekstuur 2k 32bit skalaar Name[eu]=Ehundura 2k 32bit eskalarra Name[fi]=Pintakuvio 2k 32 bit skalaarinen Name[fr]=Texture 2k 32bit scalaire Name[gl]=Textura de 2k e 32 bits escalar Name[hu]=Textúra 2k 32bit skalár Name[is]=Efnisáferð 2k 32bita scalar Name[it]=Trama 2k 32bit scalare Name[ja]=テクスチャ 2k 32 ビットスカラー Name[kk]=Текстура 2k 32 бит скаляр Name[ko]=텍스처 2k 32비트 스칼라 Name[nb]=Tekstur 2k 32bit skalar Name[nl]=Textuur 2k 32bit scalar -Name[nn]=Tekstur 2k 32-bits skalar +Name[nn]=Tekstur 2k 32-bits gråtonar Name[pl]=Tekstura 2k 32bit skalar Name[pt]=Textura 2k 32-bits escalar Name[pt_BR]=Textura 2k 32bits escalar Name[ru]=Текстура 2k 32 бит scalar Name[sk]=Textúra 2k 32bit skalár Name[sv]=Struktur 2k 32-bitar skalär Name[tr]=Doku 2k 32bit sayısal Name[uk]=Текстура 2k, 32-бітова, скалярна Name[x-test]=xxTexture 2k 32bit scalarxx Name[zh_CN]=2K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 2k 32位元 scalar Type=Link URL[$e]=.source/Texture2k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture4k32bitscalar.desktop b/krita/data/templates/texture/Texture4k32bitscalar.desktop index 69285adaf6..ed038b9cb8 100755 --- a/krita/data/templates/texture/Texture4k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture4k32bitscalar.desktop @@ -1,40 +1,40 @@ [Desktop Entry] Icon=template_texture Name=Texture 4k 32bit scalar Name[bs]=Tekstura 4k 32bit scalar Name[ca]=Textura 4k de 32 bits escalar Name[ca@valencia]=Textura 4k de 32 bits escalar Name[cs]=Textura 4k 32bit skalární Name[da]=Tekstur 4k 32bit scalar Name[de]=Textur 4k 32bit scalar Name[el]=Υφή 4k 32bit βαθμωτό Name[en_GB]=Texture 4k 32bit scalar Name[es]=Textura 4k 32bit escalar Name[et]=Tekstuur 4k 32bit skalaar Name[eu]=Ehundura 4k 32bit eskalarra Name[fi]=Pintakuvio 4k 32 bit skalaarinen Name[fr]=Texture 4k 32bit scalaire Name[gl]=Textura de 4k e 32 bits escalar Name[hu]=Textúra 4k 32bit skalár Name[is]=Efnisáferð 4k 32bita scalar Name[it]=Trama 4k 32bit scalare Name[ja]=テクスチャ 4k 32 ビットスカラー Name[kk]=Текстура 4k 32 бит скаляр Name[ko]=텍스처 4k 32비트 스칼라 Name[nb]=Tekstur 4k 32bit skalar Name[nl]=Textuur 4k 32bit scalar -Name[nn]=Tekstur 4k 32-bits skalar +Name[nn]=Tekstur 4k 32-bits gråtonar Name[pl]=Tekstura 4k 32bit skalar Name[pt]=Textura 4k 32-bits escalar Name[pt_BR]=Textura 4k 32bits escalar Name[ru]=Текстура 4k 32 бит scalar Name[sk]=Textúra 4k 32bit skalár Name[sv]=Struktur 4k 32-bitar skalär Name[tr]=Doku 4k 32bit sayısal Name[uk]=Текстура 4k, 32-бітова, скалярна Name[x-test]=xxTexture 4k 32bit scalarxx Name[zh_CN]=4K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 4k 32位元 scalar Type=Link URL[$e]=.source/Texture4k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/data/templates/texture/Texture8k32bitscalar.desktop b/krita/data/templates/texture/Texture8k32bitscalar.desktop index 91a3207dfe..d81d0fab31 100755 --- a/krita/data/templates/texture/Texture8k32bitscalar.desktop +++ b/krita/data/templates/texture/Texture8k32bitscalar.desktop @@ -1,40 +1,40 @@ [Desktop Entry] Icon=template_texture Name=Texture 8k 32bit scalar Name[bs]=Tekstura 8k 32bit scalar Name[ca]=Textura 8k de 32 bits escalar Name[ca@valencia]=Textura 8k de 32 bits escalar Name[cs]=Textura 8k 32bit skalární Name[da]=Tekstur 8k 32bit scalar Name[de]=Textur 8k 32bit scalar Name[el]=Υφή 8k 32bit βαθμωτό Name[en_GB]=Texture 8k 32bit scalar Name[es]=Textura 8k 32 bit escalar Name[et]=Tekstuur 8k 32bit skalaar Name[eu]=Ehundura 8k 32bit eskalarra Name[fi]=Pintakuvio 8k 32 bit skalaarinen Name[fr]=Texture 8k 32bit scalaire Name[gl]=Textura de 8k e 32 bits escalar Name[hu]=Textúra 8k 32bit skalár Name[is]=Efnisáferð 8k 32bita scalar Name[it]=Trama 8k 32bit scalare Name[ja]=テクスチャ 8k 32 ビットスカラー Name[kk]=Текстура 8k 32 бит скаляр Name[ko]=텍스처 8k 32비트 스칼라 Name[nb]=Tekstur 8k 32bit skalar Name[nl]=Textuur 8k 32bit scalar -Name[nn]=Tekstur 8k 32-bits skalar +Name[nn]=Tekstur 8k 32-bits gråtonar Name[pl]=Tekstura 8k 32bit skalar Name[pt]=Textura 8k 32-bits escalar Name[pt_BR]=Textura 8k 32bits escalar Name[ru]=Текстура 8k 32 бит scalar Name[sk]=Textúra 8k 32bit skalár Name[sv]=Struktur 8k 32-bitar skalär Name[tr]=Doku 8k 32bit sayısal Name[uk]=Текстура 8k, 32-бітова, скалярна Name[x-test]=xxTexture 8k 32bit scalarxx Name[zh_CN]=8K 纹理模板 32 位 Scalar 色彩空间 Name[zh_TW]=紋理 8k 32位元 scalar Type=Link URL[$e]=.source/Texture8k32bitscalar.kra X-KDE-Hidden=false diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui index c0f035efad..88160ddea8 100644 --- a/krita/krita4.xmlgui +++ b/krita/krita4.xmlgui @@ -1,406 +1,406 @@ &File &Edit Fill Special &View &Canvas &Snap To &Image &Rotate &Layer New &Import/Export Import &Convert &Select &Group &Transform &Rotate Transform &All Layers &Rotate S&plit S&plit Alpha &Select Select &Opaque Filte&r - + &Tools Scripts Setti&ngs &Help File Brushes and Stuff diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index fb148c294a..fcfd2e2c6f 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,351 +1,353 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita Krita Fundazioa Krita Foundation La Fondation Krita Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa 디지털 페인팅, 자유로운 창의성 Digital Painting, Creative Freedom Digital teikning – kreativ fridom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita je potpuni digitalni umjetnički studio.

El Krita és l'estudi d'art digital ple de funcionalitats.

El Krita és l'estudi d'art digital ple de funcionalitats.

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

Krita est le studio d'art numérique complet.

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

Krita は、フル機能を備えたデジタルなアートスタジオです。

Krita는 디지털 예술 스튜디오입니다.

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

O Krita é o estúdio de arte digital completo.

O Krita é o estúdio de arte digital completo.

Krita — полнофункциональный инструмент для создания цифровой графики.

Krita je plne vybavené digitálne umelecké štúdio.

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

Krita — повноцінний комплекс для створення цифрових художніх творів.

xxKrita is the full-featured digital art studio.xx

Krita 是一款功能齐全的数字绘画工作室软件。

Krita 是全功能的數位藝術工作室。

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.

See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.

Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.

Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.

Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.

Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.

Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.

Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.

Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.

스케치, 페인팅을 위한 완벽한 도구이며, 생각에서부터 디지털 페인팅 파일을 만들어 낼 수 있는 종합적인 도구를 제공합니다.

Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.

Passar perfekt for både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale måleri frå grunnen av.

Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.

Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.

Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.

Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.

Eskiz ve boyama için mükemmeldir ve ustaların sıfırdan dijital boyama dosyaları oluşturmak için uçtan-uca bir çözüm sunar.

Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.

xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx

它专门为数字绘画设计,为美术工作者提供了一个从起草、上色到完成作品等整个创作流程的完整解决方案。

它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.

Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.

Krita aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.

Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.

Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme RVB et CMJN avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.

Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.

Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.

Krita adalah pilihan yang cocok untuk menciptakan konsep seni, komik, tekstur untuk rendering dan lukisan matte. Krita mendukung banyak ruang warna seperti RGB dan CMYK pada channel integer 8 dan 16 bit, serta channel floating point 16 dan 32 bit.

Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.

コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。

Krita는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.

Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.

Krita er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, både RGB- og CMYK-baserte, med 8- og 16-bits heiltals- eller flyttalskanalar.

Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.

O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.

O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.

Krita — отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.

Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.

Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.

Krita, konsept sanat, çizgi roman, kaplama ve mat resimler için dokular oluşturmak için mükemmel bir seçimdir. Krita, 8 ve 16 bit tamsayı kanallarında RGB ve CMYK gibi birçok renk alanını ve 16 ve 32 bit kayan nokta kanallarını desteklemektedir.

Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.

xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx

Krita 是绘制概念美术、漫画、纹理和电影布景的理想选择。Krita 支持多种色彩空间,如 8 位和 16 位整数及 16 位和 32 位浮点的 RGB 和 CMYK 颜色模型。

Krita 是創造概念藝術、漫畫、彩現紋理和場景繪畫的絕佳選擇。Krita 在 8 位元和 16 位元整數色版,以及 16 位元和 32 位元浮點色板中支援 RGB 和 CMYK 等多種色彩空間。

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.

Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.

Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.

Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.

Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.

Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.

Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.

Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.

Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.

Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

Krita의 고급 브러시 엔진, 다양한 필터, 여러 도움이 되는 기능으로 생산성을 즐겁게 향상시킬 수 있습니다.

Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.

Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.

Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.

Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.

Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.

Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.

Gelişmiş fırça motorları, şaşırtıcı filtreler ve Krita'yı son derece üretken yapan bir çok kullanışlı özellikli boya ile iyi eğlenceler.

Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.

xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx

Krita 具有功能强大的笔刷引擎、种类繁多的滤镜以及便于操作的交互设计,可让你尽情、高效地挥洒无限创意。

使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。

https://www.krita.org/ https://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. Krita és un estudi de pintura digital ple de funcionalitats. Krita és un estudi de pintura digital ple de funcionalitats. Krita es un completo estudio de dibujo digital. Krita adalah studio pelukisan digital dengan fitur yang lengkap. Krita è uno studio d'arte digitale completo. Krita는 다기능 디지털 예술 스튜디오입니다. Krita is een digitale schilderstudio vol mogelijkheden. Krita er ei funksjonsrik digital teiknestove. Krita jest pełnowymiarowym, cyfrowym studiem artystycznym. O Krita é um estúdio de arte digital completo. O Krita é um estúdio de pintura digital completo. Krita är en fullfjädrad digital konststudio. Krita — повноцінний комплекс для цифрового малювання. xxKrita is a full-featured digital painting studio.xx Krita 是一款功能齐全的数字绘画工作室软件。 Krita 是全功能的數位繪圖工作室。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita. Window pemulaian kini juga memberikan kamu kabar terbaru tentang Krita. La finestra di avvio ora fornisce anche le ultime novità su Krita. 시작 창에서 Krita의 최신 소식을 볼 수 있습니다. Het opstartvenster geeft u nu ook you het laatste nieuws over Krita. Oppstartsvindauget viser no siste nytt om Krita. Okno początkowe teraz wyświetla wieści o Kricie. A janela inicial agora também lhe dá as últimas notícias sobre o Krita. Startfönstret ger nu också senaste nytt om Krita. У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita. xxThe startup window now also gives you the latest news about Krita.xx 启动画面现在可以为你呈现与 Krita 有关的最新资讯。 開始視窗也提供給您關於 Krita 的最新消息。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. Hi ha uns deu motors de pinzell immensament potents. Hi ha uns deu motors de pinzell immensament potents. Existen unos diez inmensamente potentes motores de pinceles. Ada lebih dari sepuluh mesin kuas yang sangat manjur. Ci sono oltre dieci motori di pennelli incredibilmente potenti. 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다. Er zijn meer dan tien immens krachtige penseelengines. Det finst meir enn ti enormt kraftige penselmotorar. Istnieje ponad dziesięć zaawansowanych silników pędzli. Existem mais de dez motores de pincéis extremamente poderosos. Det finns mer än tio enormt kraftfulla penselgränssnitt. У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів. xxThere are over ten immensely powerful brush engines.xx 它具备超过十种相当强大的笔刷引擎。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Cree y use gamas para proporcionar a sus imágenes un aspecto coherente. Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren. Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente. 색역 마스크를 만들고 사용할 수 있습니다. Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven. Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk. Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente. Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla. Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду. xxCreate and use gamut masks to give your images a coherent feel.xx 创建并使用色域蒙版可以为你的图像带来更加一致的观感。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional. Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional. Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano. 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다. Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie. Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar. Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão. Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar. Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації. xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx 喜欢制作动画吗?Krita 提供了制作传统手绘动画的全套工具。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual. Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas. Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato. 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오. Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding. Viss du er nybegynnar innan digital teikning, eller ønskjer å veta meir om kva som er mogleg med Krita, finst det ei omfattande og oppdatert brukarhandbok. Jeśli cyfrowe malowanie to dla ciebie nowość, lub jeśli chcesz dowiedzieć się więcej o możliwościach Krity, to dostępny jest wyczerpująca i aktualna instrukcja obsługi. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado. Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok. Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником. xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx 不管你是数字绘画的新手,还是想发现 Krita 更多的用法,你都可以在我们详尽并持续更新的使用手册中找到答案。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics + 2DGraphics + RasterGraphics KDE krita org.kde.krita.desktop
diff --git a/krita/org.kde.krita.desktop b/krita/org.kde.krita.desktop index e448aab3ff..ae6dbfec3d 100644 --- a/krita/org.kde.krita.desktop +++ b/krita/org.kde.krita.desktop @@ -1,159 +1,159 @@ [Desktop Entry] Name=Krita Name[af]=Krita Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[nn]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=Krita Exec=krita %F GenericName=Digital Painting GenericName[ar]=رسم رقمي GenericName[bs]=Digitalno Bojenje GenericName[ca]=Dibuix digital GenericName[ca@valencia]=Dibuix digital GenericName[cs]=Digitální malování GenericName[da]=Digital tegning GenericName[de]=Digitales Malen GenericName[el]=Ψηφιακή ζωγραφική GenericName[en_GB]=Digital Painting GenericName[es]=Pintura digital GenericName[et]=Digitaalne joonistamine GenericName[eu]=Margolan digitala GenericName[fi]=Digitaalimaalaus GenericName[fr]=Peinture numérique GenericName[gl]=Debuxo dixital GenericName[hu]=Digitális festészet GenericName[ia]=Pintura Digital GenericName[is]=Stafræn málun GenericName[it]=Pittura digitale GenericName[ja]=デジタルペインティング GenericName[kk]=Цифрлық сурет салу GenericName[ko]=디지털 페인팅 GenericName[lt]=Skaitmeninis piešimas GenericName[mr]=डिजिटल पेंटिंग GenericName[nb]=Digital maling GenericName[nl]=Digitaal schilderen GenericName[nn]=Digital teikning GenericName[pl]=Cyfrowe malowanie GenericName[pt]=Pintura Digital GenericName[pt_BR]=Pintura digital GenericName[ru]=Цифровая живопись GenericName[sk]=Digitálne maľovanie GenericName[sl]=Digitalno slikanje GenericName[sv]=Digital målning GenericName[tr]=Sayısal Boyama GenericName[ug]=سىفىرلىق رەسىم سىزغۇ GenericName[uk]=Цифрове малювання GenericName[x-test]=xxDigital Paintingxx GenericName[zh_CN]=数字绘画 GenericName[zh_TW]=數位繪畫 MimeType=application/x-krita;image/openraster;application/x-krita-paintoppreset; Comment=Digital Painting Comment[ar]=رسم رقمي Comment[bs]=Digitalno Bojenje Comment[ca]=Dibuix digital Comment[ca@valencia]=Dibuix digital Comment[cs]=Digitální malování Comment[da]=Digital tegning Comment[de]=Digitales Malen Comment[el]=Ψηφιακή ζωγραφική Comment[en_GB]=Digital Painting Comment[es]=Pintura digital Comment[et]=Digitaalne joonistamine Comment[eu]=Margolan digitala Comment[fi]=Digitaalimaalaus Comment[fr]=Peinture numérique Comment[gl]=Debuxo dixital. Comment[hu]=Digitális festészet Comment[ia]=Pintura Digital Comment[is]=Stafræn málun Comment[it]=Pittura digitale Comment[ja]=デジタルペインティング Comment[kk]=Цифрлық сурет салу Comment[ko]=디지털 페인팅 Comment[lt]=Skaitmeninis piešimas Comment[mr]=डिजिटल पेंटिंग Comment[nb]=Digital maling Comment[nl]=Digitaal schilderen Comment[nn]=Digital teikning Comment[pl]=Cyfrowe malowanie Comment[pt]=Pintura Digital Comment[pt_BR]=Pintura digital Comment[ru]=Цифровая живопись Comment[sk]=Digitálne maľovanie Comment[sl]=Digitalno slikanje Comment[sv]=Digitalt målningsverktyg Comment[tr]=Sayısal Boyama Comment[ug]=سىفىرلىق رەسىم سىزغۇ Comment[uk]=Цифрове малювання Comment[x-test]=xxDigital Paintingxx Comment[zh_CN]=数字绘画 Comment[zh_TW]=數位繪畫 Type=Application Icon=calligrakrita -Categories=Qt;KDE;Graphics; +Categories=Qt;KDE;Graphics;2DGraphics;RasterGraphics; X-KDE-NativeMimeType=application/x-krita X-KDE-ExtraNativeMimeTypes= StartupNotify=true X-Krita-Version=28 StartupWMClass=krita # Always be the preferred handler for .kra files InitialPreference=99 diff --git a/libs/command/kis_command_ids.h b/libs/command/kis_command_ids.h index a19705d09d..7ad31146d2 100644 --- a/libs/command/kis_command_ids.h +++ b/libs/command/kis_command_ids.h @@ -1,42 +1,43 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_COMMAND_IDS_H #define KIS_COMMAND_IDS_H namespace KisCommandUtils { enum CommandId { MoveShapeId = 9999, ResizeShapeId, TransformShapeId, ChangeShapeTransparencyId, ChangeShapeBackgroundId, ChangeShapeStrokeId, ChangeShapeMarkersId, ChangeShapeParameterId, ChangeEllipseShapeId, ChangeRectangleShapeId, ChangePathShapePointId, ChangePathShapeControlPointId, - ChangePaletteId + ChangePaletteId, + TransformToolId }; } #endif // KIS_COMMAND_IDS_H diff --git a/libs/command/kundo2commandextradata.h b/libs/command/kundo2commandextradata.h index 3524d763ad..21776e07b2 100644 --- a/libs/command/kundo2commandextradata.h +++ b/libs/command/kundo2commandextradata.h @@ -1,31 +1,32 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KUNDO2COMMANDEXTRADATA_H #define __KUNDO2COMMANDEXTRADATA_H #include "kritacommand_export.h" class KRITACOMMAND_EXPORT KUndo2CommandExtraData { public: virtual ~KUndo2CommandExtraData(); + virtual KUndo2CommandExtraData* clone() const = 0; }; #endif /* __KUNDO2COMMANDEXTRADATA_H */ diff --git a/libs/flake/KoColorBackground.h b/libs/flake/KoColorBackground.h index bef030bb39..4398b16b0f 100644 --- a/libs/flake/KoColorBackground.h +++ b/libs/flake/KoColorBackground.h @@ -1,67 +1,67 @@ /* This file is part of the KDE project * Copyright (C) 2008 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORBACKGROUND_H #define KOCOLORBACKGROUND_H #include "KoShapeBackground.h" #include "kritaflake_export.h" #include #include class KoColorBackgroundPrivate; class QColor; class QBrush; /// A simple solid color shape background class KRITAFLAKE_EXPORT KoColorBackground : public KoShapeBackground { public: KoColorBackground(); /// Creates background from given color and style explicit KoColorBackground(const QColor &color, Qt::BrushStyle style = Qt::SolidPattern); ~KoColorBackground() override; bool compareTo(const KoShapeBackground *other) const override; /// Returns the background color QColor color() const; /// Sets the background color void setColor(const QColor &color); /// Returns the background style Qt::BrushStyle style() const; QBrush brush() const; // reimplemented from KoShapeBackground void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override; // reimplemented from KoShapeBackground void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override; // reimplemented from KoShapeBackground bool loadStyle(KoOdfLoadingContext & context, const QSizeF &shapeSize) override; private: - struct Private; + class Private; QSharedDataPointer d; }; #endif // KOCOLORBACKGROUND_H diff --git a/libs/flake/KoResourceManager_p.cpp b/libs/flake/KoResourceManager_p.cpp index 680f94cd32..c16d758599 100644 --- a/libs/flake/KoResourceManager_p.cpp +++ b/libs/flake/KoResourceManager_p.cpp @@ -1,242 +1,245 @@ /* Copyright (c) 2006, 2011 Boudewijn Rempt (boud@valdyas.org) Copyright (C) 2007, 2010 Thomas Zander Copyright (c) 2008 Carlos Licea Copyright (c) 2011 Jan Hambrecht This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoResourceManager_p.h" #include #include #include "KoShape.h" #include "kis_assert.h" #include "kis_debug.h" void KoResourceManager::slotResourceInternalsChanged(int key) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_resources.contains(key)); notifyDerivedResourcesChanged(key, m_resources[key]); } void KoResourceManager::setResource(int key, const QVariant &value) { KoDerivedResourceConverterSP converter = m_derivedResources.value(key, KoDerivedResourceConverterSP()); if (converter) { const int sourceKey = converter->sourceKey(); const QVariant oldSourceValue = m_resources.value(sourceKey, QVariant()); bool valueChanged = false; const QVariant newSourceValue = converter->writeToSource(value, oldSourceValue, &valueChanged); if (valueChanged) { notifyResourceChanged(key, value); } if (oldSourceValue != newSourceValue) { m_resources[sourceKey] = newSourceValue; notifyResourceChanged(sourceKey, newSourceValue); } - - } else if (m_resources.contains(key)) { + } else if (m_resources.contains(key)) { const QVariant oldValue = m_resources.value(key, QVariant()); m_resources[key] = value; if (m_updateMediators.contains(key)) { m_updateMediators[key]->connectResource(value); } + if (oldValue != value) { notifyResourceChanged(key, value); } - } - else { + } else { m_resources[key] = value; + if (m_updateMediators.contains(key)) { + m_updateMediators[key]->connectResource(value); + } notifyResourceChanged(key, value); } + } void KoResourceManager::notifyResourceChanged(int key, const QVariant &value) { emit resourceChanged(key, value); notifyDerivedResourcesChanged(key, value); } void KoResourceManager::notifyDerivedResourcesChanged(int key, const QVariant &value) { QMultiHash::const_iterator it = m_derivedFromSource.constFind(key); QMultiHash::const_iterator end = m_derivedFromSource.constEnd(); while (it != end && it.key() == key) { KoDerivedResourceConverterSP converter = it.value(); if (converter->notifySourceChanged(value)) { notifyResourceChanged(converter->key(), converter->readFromSource(value)); } it++; } } QVariant KoResourceManager::resource(int key) const { KoDerivedResourceConverterSP converter = m_derivedResources.value(key, KoDerivedResourceConverterSP()); const int realKey = converter ? converter->sourceKey() : key; QVariant value = m_resources.value(realKey, QVariant()); return converter ? converter->readFromSource(value) : value; } void KoResourceManager::setResource(int key, const KoColor &color) { QVariant v; v.setValue(color); setResource(key, v); } void KoResourceManager::setResource(int key, KoShape *shape) { QVariant v; v.setValue(shape); setResource(key, v); } void KoResourceManager::setResource(int key, const KoUnit &unit) { QVariant v; v.setValue(unit); setResource(key, v); } KoColor KoResourceManager::koColorResource(int key) const { if (! m_resources.contains(key)) { KoColor empty; return empty; } return resource(key).value(); } KoShape *KoResourceManager::koShapeResource(int key) const { if (! m_resources.contains(key)) return 0; return resource(key).value(); } KoUnit KoResourceManager::unitResource(int key) const { return resource(key).value(); } bool KoResourceManager::boolResource(int key) const { if (! m_resources.contains(key)) return false; return m_resources[key].toBool(); } int KoResourceManager::intResource(int key) const { if (! m_resources.contains(key)) return 0; return m_resources[key].toInt(); } QString KoResourceManager::stringResource(int key) const { if (! m_resources.contains(key)) { QString empty; return empty; } return qvariant_cast(resource(key)); } QSizeF KoResourceManager::sizeResource(int key) const { if (! m_resources.contains(key)) { QSizeF empty; return empty; } return qvariant_cast(resource(key)); } bool KoResourceManager::hasResource(int key) const { KoDerivedResourceConverterSP converter = m_derivedResources.value(key, KoDerivedResourceConverterSP()); const int realKey = converter ? converter->sourceKey() : key; return m_resources.contains(realKey); } void KoResourceManager::clearResource(int key) { // we cannot remove a derived resource if (m_derivedResources.contains(key)) return; if (m_resources.contains(key)) { m_resources.remove(key); notifyResourceChanged(key, QVariant()); } } void KoResourceManager::addDerivedResourceConverter(KoDerivedResourceConverterSP converter) { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_derivedResources.contains(converter->key())); m_derivedResources.insert(converter->key(), converter); m_derivedFromSource.insertMulti(converter->sourceKey(), converter); } bool KoResourceManager::hasDerivedResourceConverter(int key) { return m_derivedResources.contains(key); } void KoResourceManager::removeDerivedResourceConverter(int key) { Q_ASSERT(m_derivedResources.contains(key)); KoDerivedResourceConverterSP converter = m_derivedResources.value(key); m_derivedResources.remove(key); m_derivedFromSource.remove(converter->sourceKey(), converter); } void KoResourceManager::addResourceUpdateMediator(KoResourceUpdateMediatorSP mediator) { KIS_SAFE_ASSERT_RECOVER_NOOP(!m_updateMediators.contains(mediator->key())); m_updateMediators.insert(mediator->key(), mediator); connect(mediator.data(), SIGNAL(sigResourceChanged(int)), SLOT(slotResourceInternalsChanged(int))); } bool KoResourceManager::hasResourceUpdateMediator(int key) { return m_updateMediators.contains(key); } void KoResourceManager::removeResourceUpdateMediator(int key) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_updateMediators.contains(key)); m_updateMediators.remove(key); } diff --git a/libs/global/kis_assert.cpp b/libs/global/kis_assert.cpp index 1ca38a8379..641b72c92c 100644 --- a/libs/global/kis_assert.cpp +++ b/libs/global/kis_assert.cpp @@ -1,126 +1,129 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_assert.h" #include #include #include #include #include #include #include +#include #include #include "config-hide-safe-asserts.h" /** * TODO: Add automatic saving of the documents * * Requirements: * 1) Should save all open KisDocument objects * 2) Should *not* overwrite original document since the saving * process may fail. * 3) Should *not* overwrite any autosaved documents since the saving * process may fail. * 4) Double-fault tolerance! Assert during emergency saving should not * lead to an infinite loop. */ void kis_assert_common(const char *assertion, const char *file, int line, bool throwException, bool isIgnorable) { QString shortMessage = QString("%4ASSERT (krita): \"%1\" in file %2, line %3") .arg(assertion) .arg(file) .arg(line) .arg(isIgnorable ? "SAFE " : ""); QString longMessage = QString( "Krita has encountered an internal error:\n\n" "%1\n\n" "Please report a bug to developers!\n\n" "Press Ignore to try to continue.\n" "Press Abort to see developers information (all unsaved data will be lost)") .arg(shortMessage); + KisUsageLogger::log(shortMessage); + bool disableAssertMsg = QProcessEnvironment::systemEnvironment().value("KRITA_NO_ASSERT_MSG", "0").toInt(); // disable message box if the assert happened in non-gui thread :( if (QThread::currentThread() != QCoreApplication::instance()->thread()) { disableAssertMsg = true; } #ifdef HIDE_SAFE_ASSERTS const bool shouldIgnoreAsserts = HIDE_SAFE_ASSERTS; #else const bool shouldIgnoreAsserts = false; #endif disableAssertMsg |= shouldIgnoreAsserts; QMessageBox::StandardButton button = isIgnorable ? QMessageBox::Ignore : QMessageBox::Abort; if (!disableAssertMsg) { button = QMessageBox::critical(0, i18nc("@title:window", "Krita: Internal Error"), longMessage, QMessageBox::Ignore | QMessageBox::Abort, QMessageBox::Ignore); } if (button == QMessageBox::Abort) { qFatal("%s", shortMessage.toLatin1().data()); } else if (isIgnorable) { // Assert is a bug! Please don't change this line to warnKrita, // the user must see it! qWarning("%s", shortMessage.toLatin1().data()); } else if (throwException) { throw KisAssertException(shortMessage.toLatin1().data()); } } void kis_assert_recoverable(const char *assertion, const char *file, int line) { kis_assert_common(assertion, file, line, false, false); } void kis_safe_assert_recoverable(const char *assertion, const char *file, int line) { kis_assert_common(assertion, file, line, false, true); } void kis_assert_exception(const char *assertion, const char *file, int line) { kis_assert_common(assertion, file, line, true, false); } void kis_assert_x_exception(const char *assertion, const char *where, const char *what, const char *file, int line) { QString res = QString("ASSERT failure in %1: \"%2\" (%3)") .arg(where, what, assertion); kis_assert_common(res.toLatin1().data(), file, line, true, false); } diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index 086c88d1d2..12215ad707 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,381 +1,382 @@ add_subdirectory( tests ) add_subdirectory( tiles3 ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/brushengine ${CMAKE_CURRENT_SOURCE_DIR}/commands ${CMAKE_CURRENT_SOURCE_DIR}/commands_new ${CMAKE_CURRENT_SOURCE_DIR}/filter ${CMAKE_CURRENT_SOURCE_DIR}/floodfill ${CMAKE_CURRENT_SOURCE_DIR}/generator ${CMAKE_CURRENT_SOURCE_DIR}/layerstyles ${CMAKE_CURRENT_SOURCE_DIR}/processing ${CMAKE_SOURCE_DIR}/sdk/tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) if(FFTW3_FOUND) include_directories(${FFTW3_INCLUDE_DIR}) endif() if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) else() set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp) endif() if (GSL_FOUND) include_directories(${GSL_INCLUDE_DIR}) endif() set(kritaimage_LIB_SRCS tiles3/kis_tile.cc tiles3/kis_tile_data.cc tiles3/kis_tile_data_store.cc tiles3/kis_tile_data_pooler.cc tiles3/kis_tiled_data_manager.cc tiles3/KisTiledExtentManager.cpp tiles3/kis_memento_manager.cc tiles3/kis_hline_iterator.cpp tiles3/kis_vline_iterator.cpp tiles3/kis_random_accessor.cc tiles3/swap/kis_abstract_compression.cpp tiles3/swap/kis_lzf_compression.cpp tiles3/swap/kis_abstract_tile_compressor.cpp tiles3/swap/kis_legacy_tile_compressor.cpp tiles3/swap/kis_tile_compressor_2.cpp tiles3/swap/kis_chunk_allocator.cpp tiles3/swap/kis_memory_window.cpp tiles3/swap/kis_swapped_data_store.cpp tiles3/swap/kis_tile_data_swapper.cpp kis_distance_information.cpp kis_painter.cc kis_painter_blt_multi_fixed.cpp kis_marker_painter.cpp KisPrecisePaintDeviceWrapper.cpp kis_progress_updater.cpp brushengine/kis_paint_information.cc brushengine/kis_random_source.cpp brushengine/KisPerStrokeRandomSource.cpp brushengine/kis_stroke_random_source.cpp brushengine/kis_paintop.cc brushengine/kis_paintop_factory.cpp brushengine/kis_paintop_preset.cpp brushengine/kis_paintop_registry.cc brushengine/kis_paintop_settings.cpp brushengine/kis_paintop_settings_update_proxy.cpp brushengine/kis_paintop_utils.cpp brushengine/kis_no_size_paintop_settings.cpp brushengine/kis_locked_properties.cc brushengine/kis_locked_properties_proxy.cpp brushengine/kis_locked_properties_server.cpp brushengine/kis_paintop_config_widget.cpp brushengine/kis_uniform_paintop_property.cpp brushengine/kis_combo_based_paintop_property.cpp brushengine/kis_slider_based_paintop_property.cpp brushengine/kis_standard_uniform_properties_factory.cpp brushengine/KisStrokeSpeedMeasurer.cpp brushengine/KisPaintopSettingsIds.cpp commands/kis_deselect_global_selection_command.cpp commands/KisDeselectActiveSelectionCommand.cpp commands/kis_image_change_layers_command.cpp commands/kis_image_change_visibility_command.cpp commands/kis_image_command.cpp commands/kis_image_set_projection_color_space_command.cpp commands/kis_image_layer_add_command.cpp commands/kis_image_layer_move_command.cpp commands/kis_image_layer_remove_command.cpp commands/kis_image_layer_remove_command_impl.cpp commands/kis_image_lock_command.cpp commands/kis_node_command.cpp commands/kis_node_compositeop_command.cpp commands/kis_node_opacity_command.cpp commands/kis_node_property_list_command.cpp commands/kis_reselect_global_selection_command.cpp commands/KisReselectActiveSelectionCommand.cpp commands/kis_set_global_selection_command.cpp commands/KisNodeRenameCommand.cpp commands_new/kis_saved_commands.cpp commands_new/kis_processing_command.cpp commands_new/kis_image_resize_command.cpp commands_new/kis_image_set_resolution_command.cpp commands_new/kis_node_move_command2.cpp commands_new/kis_set_layer_style_command.cpp commands_new/kis_selection_move_command2.cpp commands_new/kis_update_command.cpp commands_new/kis_switch_current_time_command.cpp commands_new/kis_change_projection_color_command.cpp commands_new/kis_activate_selection_mask_command.cpp commands_new/kis_transaction_based_command.cpp + commands_new/KisHoldUIUpdatesCommand.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp processing/kis_crop_processing_visitor.cpp processing/kis_crop_selections_processing_visitor.cpp processing/kis_transform_processing_visitor.cpp processing/kis_mirror_processing_visitor.cpp processing/KisSelectionBasedProcessingHelper.cpp filter/kis_filter.cc filter/kis_filter_category_ids.cpp filter/kis_filter_configuration.cc filter/kis_color_transformation_configuration.cc filter/kis_filter_registry.cc filter/kis_color_transformation_filter.cc generator/kis_generator.cpp generator/kis_generator_layer.cpp generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp lazybrush/kis_min_cut_worker.cpp lazybrush/kis_lazy_fill_tools.cpp lazybrush/kis_multiway_cut.cpp lazybrush/KisWatershedWorker.cpp lazybrush/kis_colorize_mask.cpp lazybrush/kis_colorize_stroke_strategy.cpp KisDelayedUpdateNodeInterface.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp kis_base_accessor.cpp kis_base_node.cpp kis_base_processor.cpp kis_bookmarked_configuration_manager.cc kis_node_uuid_info.cpp kis_clone_layer.cpp kis_colorspace_convert_visitor.cpp kis_config_widget.cpp kis_convolution_kernel.cc kis_convolution_painter.cc kis_gaussian_kernel.cpp kis_edge_detection_kernel.cpp kis_cubic_curve.cpp kis_default_bounds.cpp kis_default_bounds_base.cpp kis_effect_mask.cc kis_fast_math.cpp kis_fill_painter.cc kis_filter_mask.cpp kis_filter_strategy.cc kis_transform_mask.cpp kis_transform_mask_params_interface.cpp kis_recalculate_transform_mask_job.cpp kis_recalculate_generator_layer_job.cpp kis_transform_mask_params_factory_registry.cpp kis_safe_transform.cpp kis_gradient_painter.cc kis_gradient_shape_strategy.cpp kis_cached_gradient_shape_strategy.cpp kis_polygonal_gradient_shape_strategy.cpp kis_iterator_ng.cpp kis_async_merger.cpp kis_merge_walker.cc kis_updater_context.cpp kis_update_job_item.cpp kis_stroke_strategy_undo_command_based.cpp kis_simple_stroke_strategy.cpp KisRunnableBasedStrokeStrategy.cpp KisRunnableStrokeJobDataBase.cpp KisRunnableStrokeJobData.cpp KisRunnableStrokeJobsInterface.cpp KisFakeRunnableStrokeJobsExecutor.cpp kis_stroke_job_strategy.cpp kis_stroke_strategy.cpp kis_stroke.cpp kis_strokes_queue.cpp KisStrokesQueueMutatedJobInterface.cpp kis_simple_update_queue.cpp kis_update_scheduler.cpp kis_queues_progress_updater.cpp kis_composite_progress_proxy.cpp kis_sync_lod_cache_stroke_strategy.cpp kis_lod_capable_layer_offset.cpp kis_update_time_monitor.cpp KisImageConfigNotifier.cpp kis_group_layer.cc kis_count_visitor.cpp kis_histogram.cc kis_image_interfaces.cpp kis_image_animation_interface.cpp kis_time_range.cpp kis_node_graph_listener.cpp kis_image.cc kis_image_signal_router.cpp KisImageSignals.cpp kis_image_config.cpp kis_projection_updates_filter.cpp kis_suspend_projection_updates_stroke_strategy.cpp kis_regenerate_frame_stroke_strategy.cpp kis_switch_time_stroke_strategy.cpp kis_crop_saved_extra_data.cpp kis_timed_signal_threshold.cpp kis_layer.cc kis_indirect_painting_support.cpp kis_abstract_projection_plane.cpp kis_layer_projection_plane.cpp kis_layer_utils.cpp kis_mask_projection_plane.cpp kis_projection_leaf.cpp KisSafeNodeProjectionStore.cpp kis_mask.cc kis_base_mask_generator.cpp kis_rect_mask_generator.cpp kis_circle_mask_generator.cpp kis_gauss_circle_mask_generator.cpp kis_gauss_rect_mask_generator.cpp ${__per_arch_circle_mask_generator_objs} kis_curve_circle_mask_generator.cpp kis_curve_rect_mask_generator.cpp kis_math_toolbox.cpp kis_memory_statistics_server.cpp kis_name_server.cpp kis_node.cpp kis_node_facade.cpp kis_node_progress_proxy.cpp kis_busy_progress_indicator.cpp kis_node_visitor.cpp kis_paint_device.cc kis_paint_device_debug_utils.cpp kis_fixed_paint_device.cpp KisOptimizedByteArray.cpp kis_paint_layer.cc kis_perspective_math.cpp kis_pixel_selection.cpp kis_processing_information.cpp kis_properties_configuration.cc kis_random_accessor_ng.cpp kis_random_generator.cc kis_random_sub_accessor.cpp kis_wrapped_random_accessor.cpp kis_selection.cc KisSelectionUpdateCompressor.cpp kis_selection_mask.cpp kis_update_outline_job.cpp kis_update_selection_job.cpp kis_serializable_configuration.cc kis_transaction_data.cpp kis_transform_worker.cc kis_perspectivetransform_worker.cpp bsplines/kis_bspline_1d.cpp bsplines/kis_bspline_2d.cpp bsplines/kis_nu_bspline_2d.cpp kis_warptransform_worker.cc kis_cage_transform_worker.cpp kis_liquify_transform_worker.cpp kis_green_coordinates_math.cpp kis_transparency_mask.cc kis_undo_adapter.cpp kis_macro_based_undo_store.cpp kis_surrogate_undo_adapter.cpp kis_legacy_undo_adapter.cpp kis_post_execution_undo_adapter.cpp kis_processing_visitor.cpp kis_processing_applicator.cpp krita_utils.cpp kis_outline_generator.cpp kis_layer_composition.cpp kis_selection_filters.cpp KisProofingConfiguration.h KisRecycleProjectionsJob.cpp kis_keyframe.cpp kis_keyframe_channel.cpp kis_keyframe_commands.cpp kis_scalar_keyframe_channel.cpp kis_raster_keyframe_channel.cpp kis_onion_skin_compositor.cpp kis_onion_skin_cache.cpp kis_idle_watcher.cpp kis_psd_layer_style.cpp kis_layer_properties_icons.cpp layerstyles/kis_multiple_projection.cpp layerstyles/kis_layer_style_filter.cpp layerstyles/kis_layer_style_filter_environment.cpp layerstyles/kis_layer_style_filter_projection_plane.cpp layerstyles/kis_layer_style_projection_plane.cpp layerstyles/kis_ls_drop_shadow_filter.cpp layerstyles/kis_ls_satin_filter.cpp layerstyles/kis_ls_stroke_filter.cpp layerstyles/kis_ls_bevel_emboss_filter.cpp layerstyles/kis_ls_overlay_filter.cpp layerstyles/kis_ls_utils.cpp layerstyles/gimp_bump_map.cpp KisProofingConfiguration.cpp kis_node_query_path.cc ) set(einspline_SRCS 3rdparty/einspline/bspline_create.cpp 3rdparty/einspline/bspline_data.cpp 3rdparty/einspline/multi_bspline_create.cpp 3rdparty/einspline/nubasis.cpp 3rdparty/einspline/nubspline_create.cpp 3rdparty/einspline/nugrid.cpp ) add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS}) generate_export_header(kritaimage BASE_NAME kritaimage) target_link_libraries(kritaimage PUBLIC kritaversion kritawidgets kritaglobal kritapsd kritaodf kritapigment kritacommand kritawidgetutils kritametadata Qt5::Concurrent ) target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY}) if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB) if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) target_link_libraries(kritaimage PUBLIC atomic) endif() endif() if(OPENEXR_FOUND) target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES}) endif() if(FFTW3_FOUND) target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES}) endif() if(HAVE_VC) target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES}) endif() if (NOT GSL_FOUND) message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.") else () target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif () target_include_directories(kritaimage PUBLIC $ $ $ $ $ ) set_target_properties(kritaimage PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/image/commands_new/KisHoldUIUpdatesCommand.cpp b/libs/image/commands_new/KisHoldUIUpdatesCommand.cpp new file mode 100644 index 0000000000..6964e75a18 --- /dev/null +++ b/libs/image/commands_new/KisHoldUIUpdatesCommand.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KisHoldUIUpdatesCommand.h" + +#include +#include "kis_image_interfaces.h" +#include "krita_utils.h" +#include "kis_paintop_utils.h" +#include "kis_image_signal_router.h" +#include "KisRunnableStrokeJobData.h" +#include "KisRunnableStrokeJobUtils.h" +#include "KisRunnableStrokeJobsInterface.h" + + +KisHoldUIUpdatesCommand::KisHoldUIUpdatesCommand(KisUpdatesFacade *updatesFacade, State state) + : KisCommandUtils::FlipFlopCommand(state), + m_updatesFacade(updatesFacade), + m_batchUpdateStarted(new bool(false)) +{ +} + +void KisHoldUIUpdatesCommand::partA() +{ + if (*m_batchUpdateStarted) { + m_updatesFacade->notifyBatchUpdateEnded(); + *m_batchUpdateStarted = false; + } + + m_updatesFacade->disableUIUpdates(); +} + +void KisHoldUIUpdatesCommand::partB() +{ + QVector totalDirtyRects = m_updatesFacade->enableUIUpdates(); + + const QRect totalRect = + m_updatesFacade->bounds() & + std::accumulate(totalDirtyRects.begin(), totalDirtyRects.end(), QRect(), std::bit_or()); + + totalDirtyRects = + KisPaintOpUtils::splitAndFilterDabRect(totalRect, + totalDirtyRects, + KritaUtils::optimalPatchSize().width()); + + *m_batchUpdateStarted = true; + m_updatesFacade->notifyBatchUpdateStarted(); + + KisUpdatesFacade *updatesFacade = m_updatesFacade; + QSharedPointer batchUpdateStarted = m_batchUpdateStarted; + + QVector jobsData; + Q_FOREACH (const QRect &rc, totalDirtyRects) { + KritaUtils::addJobConcurrent(jobsData, [updatesFacade, rc] () { + updatesFacade->notifyUIUpdateCompleted(rc); + }); + } + + KritaUtils::addJobBarrier(jobsData, [updatesFacade, batchUpdateStarted] () { + updatesFacade->notifyBatchUpdateEnded(); + *batchUpdateStarted = false; + }); + + runnableJobsInterface()->addRunnableJobs(jobsData); +} diff --git a/libs/command/kundo2commandextradata.h b/libs/image/commands_new/KisHoldUIUpdatesCommand.h similarity index 51% copy from libs/command/kundo2commandextradata.h copy to libs/image/commands_new/KisHoldUIUpdatesCommand.h index 3524d763ad..7f3bec9a43 100644 --- a/libs/command/kundo2commandextradata.h +++ b/libs/image/commands_new/KisHoldUIUpdatesCommand.h @@ -1,31 +1,43 @@ /* - * Copyright (c) 2015 Dmitry Kazakov + * Copyright (c) 2019 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __KUNDO2COMMANDEXTRADATA_H -#define __KUNDO2COMMANDEXTRADATA_H +#ifndef KISHOLDUIUPDATESCOMMAND_H +#define KISHOLDUIUPDATESCOMMAND_H -#include "kritacommand_export.h" +#include "kis_stroke_strategy_undo_command_based.h" +#include "kis_node.h" +#include "kis_command_utils.h" +#include +class KisUpdatesFacade; -class KRITACOMMAND_EXPORT KUndo2CommandExtraData + +class KRITAIMAGE_EXPORT KisHoldUIUpdatesCommand : public KisCommandUtils::FlipFlopCommand, public KisStrokeStrategyUndoCommandBased::MutatedCommandInterface { public: - virtual ~KUndo2CommandExtraData(); + KisHoldUIUpdatesCommand(KisUpdatesFacade *updatesFacade, State state); + + void partA() override; + void partB() override; + +private: + KisUpdatesFacade *m_updatesFacade; + QSharedPointer m_batchUpdateStarted; }; -#endif /* __KUNDO2COMMANDEXTRADATA_H */ +#endif // KISHOLDUIUPDATESCOMMAND_H diff --git a/libs/image/commands_new/kis_saved_commands.cpp b/libs/image/commands_new/kis_saved_commands.cpp index 97acc31fd0..ddec094bd9 100644 --- a/libs/image/commands_new/kis_saved_commands.cpp +++ b/libs/image/commands_new/kis_saved_commands.cpp @@ -1,275 +1,305 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_saved_commands.h" #include #include "kis_image_interfaces.h" #include "kis_stroke_strategy_undo_command_based.h" KisSavedCommandBase::KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade) : KUndo2Command(name), m_strokesFacade(strokesFacade), m_skipOneRedo(true) { } KisSavedCommandBase::~KisSavedCommandBase() { } KisStrokesFacade* KisSavedCommandBase::strokesFacade() { return m_strokesFacade; } void KisSavedCommandBase::runStroke(bool undo) { KisStrokeStrategyUndoCommandBased *strategy = new KisStrokeStrategyUndoCommandBased(text(), undo, 0); strategy->setUsedWhileUndoRedo(true); KisStrokeId id = m_strokesFacade->startStroke(strategy); addCommands(id, undo); m_strokesFacade->endStroke(id); } void KisSavedCommandBase::undo() { runStroke(true); } void KisSavedCommandBase::redo() { /** * All the commands are first executed in the stroke and then * added to the undo stack. It means that the first redo should be * skipped */ if(m_skipOneRedo) { m_skipOneRedo = false; return; } runStroke(false); } KisSavedCommand::KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade) : KisSavedCommandBase(command->text(), strokesFacade), m_command(command) { } int KisSavedCommand::id() const { return m_command->id(); } bool KisSavedCommand::mergeWith(const KUndo2Command* command) { const KisSavedCommand *other = dynamic_cast(command); if (other) { command = other->m_command.data(); } return m_command->mergeWith(command); } void KisSavedCommand::addCommands(KisStrokeId id, bool undo) { strokesFacade()-> addJob(id, new KisStrokeStrategyUndoCommandBased::Data(m_command, undo)); } int KisSavedCommand::timedId() { return m_command->timedId(); } void KisSavedCommand::setTimedID(int timedID) { m_command->setTimedID(timedID); } bool KisSavedCommand::timedMergeWith(KUndo2Command *other) { return m_command->timedMergeWith(other); } QVector KisSavedCommand::mergeCommandsVector() { return m_command->mergeCommandsVector(); } void KisSavedCommand::setTime() { m_command->setTime(); } QTime KisSavedCommand::time() { return m_command->time(); } void KisSavedCommand::setEndTime() { m_command->setEndTime(); } QTime KisSavedCommand::endTime() { return m_command->endTime(); } bool KisSavedCommand::isMerged() { return m_command->isMerged(); } struct KisSavedMacroCommand::Private { struct SavedCommand { KUndo2CommandSP command; KisStrokeJobData::Sequentiality sequentiality; KisStrokeJobData::Exclusivity exclusivity; }; QVector commands; int macroId = -1; + + const KisSavedMacroCommand *overriddenCommand = 0; + QVector skipWhenOverride; }; KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade) : KisSavedCommandBase(name, strokesFacade), m_d(new Private()) { } KisSavedMacroCommand::~KisSavedMacroCommand() { delete m_d; } void KisSavedMacroCommand::setMacroId(int value) { m_d->macroId = value; } int KisSavedMacroCommand::id() const { return m_d->macroId; } bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command) { const KisSavedMacroCommand *other = dynamic_cast(command); if (!other || other->id() != id()) return false; QVector &otherCommands = other->m_d->commands; + if (other->m_d->overriddenCommand == this) { + m_d->commands.clear(); + + Q_FOREACH (Private::SavedCommand cmd, other->m_d->commands) { + if (!other->m_d->skipWhenOverride.contains(cmd.command.data())) { + m_d->commands.append(cmd); + } + } + + setExtraData(other->extraData()->clone()); + return true; + } + if (m_d->commands.size() != otherCommands.size()) return false; auto it = m_d->commands.constBegin(); auto end = m_d->commands.constEnd(); auto otherIt = otherCommands.constBegin(); auto otherEnd = otherCommands.constEnd(); bool sameCommands = true; while (it != end && otherIt != otherEnd) { if (it->command->id() != otherIt->command->id() || it->sequentiality != otherIt->sequentiality || it->exclusivity != otherIt->exclusivity) { sameCommands = false; break; } ++it; ++otherIt; } if (!sameCommands) return false; it = m_d->commands.constBegin(); otherIt = otherCommands.constBegin(); while (it != end && otherIt != otherEnd) { if (it->command->id() != -1) { bool result = it->command->mergeWith(otherIt->command.data()); KIS_ASSERT_RECOVER(result) { return false; } } ++it; ++otherIt; } return true; } void KisSavedMacroCommand::addCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { Private::SavedCommand item; item.command = command; item.sequentiality = sequentiality; item.exclusivity = exclusivity; m_d->commands.append(item); } void KisSavedMacroCommand::performCancel(KisStrokeId id, bool strokeUndo) { addCommands(id, !strokeUndo); } -void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo) +void KisSavedMacroCommand::getCommandExecutionJobs(QVector *jobs, bool undo) const { QVector::iterator it; if(!undo) { for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) { - strokesFacade()-> - addJob(id, new KisStrokeStrategyUndoCommandBased:: + *jobs << new KisStrokeStrategyUndoCommandBased:: Data(it->command, undo, it->sequentiality, - it->exclusivity)); + it->exclusivity); } } else { for(it = m_d->commands.end(); it != m_d->commands.begin();) { --it; - strokesFacade()-> - addJob(id, new KisStrokeStrategyUndoCommandBased:: - Data(it->command, - undo, - it->sequentiality, - it->exclusivity)); + *jobs << new KisStrokeStrategyUndoCommandBased:: + Data(it->command, + undo, + it->sequentiality, + it->exclusivity); } } } + +void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride) +{ + m_d->overriddenCommand = overriddenCommand; + m_d->skipWhenOverride = skipWhileOverride; +} + +void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo) +{ + QVector jobs; + getCommandExecutionJobs(&jobs, undo); + + Q_FOREACH (KisStrokeJobData *job, jobs) { + strokesFacade()->addJob(id, job); + } +} diff --git a/libs/image/commands_new/kis_saved_commands.h b/libs/image/commands_new/kis_saved_commands.h index f15e7ee290..d1b805c847 100644 --- a/libs/image/commands_new/kis_saved_commands.h +++ b/libs/image/commands_new/kis_saved_commands.h @@ -1,101 +1,105 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SAVED_COMMANDS_H #define __KIS_SAVED_COMMANDS_H #include #include "kis_types.h" #include "kis_stroke_job_strategy.h" class KisStrokesFacade; class KRITAIMAGE_EXPORT KisSavedCommandBase : public KUndo2Command { public: KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade); ~KisSavedCommandBase() override; void undo() override; void redo() override; protected: virtual void addCommands(KisStrokeId id, bool undo) = 0; KisStrokesFacade* strokesFacade(); private: void runStroke(bool undo); private: KisStrokesFacade *m_strokesFacade; bool m_skipOneRedo; }; class KRITAIMAGE_EXPORT KisSavedCommand : public KisSavedCommandBase { public: KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade); int timedId() override; void setTimedID(int timedID) override; int id() const override; bool mergeWith(const KUndo2Command* command) override; bool timedMergeWith(KUndo2Command *other) override; QVector mergeCommandsVector() override; void setTime() override; QTime time() override; void setEndTime() override; QTime endTime() override; bool isMerged() override; protected: void addCommands(KisStrokeId id, bool undo) override; private: KUndo2CommandSP m_command; }; class KRITAIMAGE_EXPORT KisSavedMacroCommand : public KisSavedCommandBase { public: KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade); ~KisSavedMacroCommand() override; int id() const override; bool mergeWith(const KUndo2Command* command) override; void setMacroId(int value); void addCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL); void performCancel(KisStrokeId id, bool strokeUndo); + void getCommandExecutionJobs(QVector *jobs, bool undo) const; + + void setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector &skipWhileOverride); + protected: void addCommands(KisStrokeId id, bool undo) override; private: struct Private; Private * const m_d; }; #endif /* __KIS_SAVED_COMMANDS_H */ diff --git a/libs/image/kis_crop_saved_extra_data.h b/libs/image/kis_crop_saved_extra_data.h index 4647023525..41381c4550 100644 --- a/libs/image/kis_crop_saved_extra_data.h +++ b/libs/image/kis_crop_saved_extra_data.h @@ -1,61 +1,65 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_CROP_SAVED_EXTRA_DATA_H #define __KIS_CROP_SAVED_EXTRA_DATA_H #include #include "kundo2commandextradata.h" #include "kis_types.h" #include "kritaimage_export.h" class KRITAIMAGE_EXPORT KisCropSavedExtraData : public KUndo2CommandExtraData { public: enum Type { CROP_IMAGE, RESIZE_IMAGE, CROP_LAYER }; public: KisCropSavedExtraData(Type type, QRect cropRect, KisNodeSP cropNode = 0); ~KisCropSavedExtraData() override; inline Type type() const { return m_type; } inline QRect cropRect() const { return m_cropRect; } inline KisNodeSP cropNode() const { return m_cropNode; } + KUndo2CommandExtraData* clone() const override { + return new KisCropSavedExtraData(*this); + } + private: Type m_type; QRect m_cropRect; KisNodeSP m_cropNode; }; #endif /* __KIS_CROP_SAVED_EXTRA_DATA_H */ diff --git a/libs/image/kis_histogram.h b/libs/image/kis_histogram.h index ab6bbd5de0..ff74a21d4f 100644 --- a/libs/image/kis_histogram.h +++ b/libs/image/kis_histogram.h @@ -1,195 +1,198 @@ /* * Copyright (c) 2004 Boudewijn Rempt * (c) 2005 Bart Coppens * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_HISTOGRAM_ #define KIS_HISTOGRAM_ #include #include #include "KoHistogramProducer.h" #include "kis_shared.h" #include "kis_types.h" #include "kritaimage_export.h" enum enumHistogramType { LINEAR, LOGARITHMIC }; /** * The histogram class computes the histogram data from the specified layer * for the specified channel, through the use of a KoHistogramProducer. * This class is only for layers and paintdevices. KisImages are not supported, * but you can use the mergedImage function to create a paintdevice and feed that to this class. * * A Histogram also can have a selection: this is a specific range in the current histogram * that will get calculations done on it as well. If the range's begin and end are the same, * it is supposed to specify a single bin in the histogram. * * The calculations are done in the range 0 - 1, instead of the native range that a pixel * might have, so it's not always as precise as it could be. But you can't have it all... */ class KRITAIMAGE_EXPORT KisHistogram : public KisShared { public: /** * Class that stores the result of histogram calculations. * Doubles are in the 0-1 range, use the producer's positionToString function to display it. **/ class Calculations { double m_max, m_min, m_mean, m_total, m_median, m_stddev; quint32 m_high, m_low, m_count; friend class KisHistogram; public: Calculations() : m_max(0.0), m_min(0.0), m_mean(0.0), m_total(0.0), m_median(0.0), - m_stddev(0.0), m_high(0), m_low(0), m_count(0) {} + m_stddev(0.0), m_high(0), m_low(0), m_count(0) { + Q_UNUSED(m_median); + Q_UNUSED(m_stddev); + } /** * This function return the maximum bound of the histogram * (values at greater position than the maximum are null) */ inline double getMax() { return m_max; } /** * This function return the minimum bound of the histogram * (values at smaller position than the minimum are null) */ inline double getMin() { return m_min; } /// This function return the highest value of the histogram inline quint32 getHighest() { return m_high; } /// This function return the lowest value of the histogram inline quint32 getLowest() { return m_low; } /// This function return the mean of value of the histogram inline double getMean() { return m_mean; } //double getMedian() { return m_median; } //double getStandardDeviation() { return m_stddev; } /// This function return the number of pixels used by the histogram inline quint32 getCount() { return m_count; } /** The sum of (the contents of every bin * the double value of that bin)*/ inline double getTotal() { return m_total; } //quint8 getPercentile() { return m_percentile; } // What is this exactly? XXX }; KisHistogram(KisPaintLayerSP layer, KoHistogramProducer *producer, const enumHistogramType type); KisHistogram(KisPaintDeviceSP paintdev, const QRect &bounds, KoHistogramProducer *producer, const enumHistogramType type); virtual ~KisHistogram(); /** Updates the information in the producer */ void updateHistogram(); /** * (Re)computes the mathematical information from the information currently in the producer. * Needs to be called when you change the selection and want to get that information **/ void computeHistogram(); /** The information on the entire view for the current channel */ Calculations calculations(); /** The information on the current selection for the current channel */ Calculations selectionCalculations(); inline quint32 getValue(quint8 i) { return m_producer->getBinAt(m_channel, i); } inline enumHistogramType getHistogramType() { return m_type; } inline void setHistogramType(enumHistogramType type) { m_type = type; } inline void setProducer(KoHistogramProducer *producer) { m_channel = 0; m_producer = producer; } inline void setChannel(qint32 channel) { Q_ASSERT(m_channel < m_completeCalculations.size()); m_channel = channel; } inline KoHistogramProducer *producer() { return m_producer; } inline qint32 channel() { return m_channel; } inline bool hasSelection() { return m_selection; } inline double selectionFrom() { return m_selFrom; } inline double selectionTo() { return m_selTo; } inline void setNoSelection() { m_selection = false; } /** Sets the current selection */ inline void setSelection(double from, double to) { m_selection = true; m_selFrom = from; m_selTo = to; } private: // Dump the histogram to debug. void dump(); QVector calculateForRange(double from, double to); Calculations calculateSingleRange(int channel, double from, double to); const KisPaintDeviceSP m_paintDevice; QRect m_bounds; KoHistogramProducer *m_producer; enumHistogramType m_type; qint32 m_channel; double m_selFrom, m_selTo; bool m_selection; QVector m_completeCalculations, m_selectionCalculations; }; #endif // KIS_HISTOGRAM_WIDGET_ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 4d68fc5172..93e204ffe4 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,2029 +1,2049 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_projection_leaf.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_simple_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "tiles3/kis_lockless_stack.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q, _q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg(true); if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask KisSelectionMaskSP overlaySelectionMask; QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisLocklessStack savedDisabledUIUpdates; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool allowMasksOnRootNode = false; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore) { const KoColorSpace *colorSpace = 0; switch (image.format()) { case QImage::Format_Invalid: case QImage::Format_Mono: case QImage::Format_MonoLSB: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; case QImage::Format_Indexed8: case QImage::Format_RGB32: case QImage::Format_ARGB32: case QImage::Format_ARGB32_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_RGB16: colorSpace = KoColorSpaceRegistry::instance()->rgb16(); break; case QImage::Format_ARGB8565_Premultiplied: case QImage::Format_RGB666: case QImage::Format_ARGB6666_Premultiplied: case QImage::Format_RGB555: case QImage::Format_ARGB8555_Premultiplied: case QImage::Format_RGB888: case QImage::Format_RGB444: case QImage::Format_ARGB4444_Premultiplied: case QImage::Format_RGBX8888: case QImage::Format_RGBA8888: case QImage::Format_RGBA8888_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_BGR30: case QImage::Format_A2BGR30_Premultiplied: case QImage::Format_RGB30: case QImage::Format_A2RGB30_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); break; case QImage::Format_Alpha8: colorSpace = KoColorSpaceRegistry::instance()->alpha8(); break; case QImage::Format_Grayscale8: colorSpace = KoColorSpaceRegistry::instance()->graya8(); break; #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) case QImage::Format_Grayscale16: colorSpace = KoColorSpaceRegistry::instance()->graya16(); break; #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) case QImage::Format_RGBX64: case QImage::Format_RGBA64: case QImage::Format_RGBA64_Premultiplied: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0); break; #endif default: colorSpace = 0; } KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image")); KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(image, 0, 0, 0); img->addNode(layer.data(), img->rootLayer().data()); return img; } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } void KisImage::copyFromImage(const KisImage &rhs) { copyFromImageImpl(rhs, REPLACE); } void KisImage::copyFromImageImpl(const KisImage &rhs, int policy) { // make sure we choose exactly one from REPLACE and CONSTRUCT KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT)); // only when replacing do we need to emit signals #define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit if (policy & REPLACE) { // if we are constructing the image, these are already set if (m_d->width != rhs.width() || m_d->height != rhs.height()) { m_d->width = rhs.width(); m_d->height = rhs.height(); emit sigSizeChanged(QPointF(), QPointF()); } if (m_d->colorSpace != rhs.colorSpace()) { m_d->colorSpace = rhs.colorSpace(); emit sigColorSpaceChanged(m_d->colorSpace); } } // from KisImage::KisImage(const KisImage &, KisUndoStore *, bool) setObjectName(rhs.objectName()); if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) { m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres); } m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode; if (rhs.m_d->proofingConfig) { KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); if (policy & REPLACE) { setProofingConfiguration(proofingConfig); } else { m_d->proofingConfig = proofingConfig; } } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); bool exactCopy = policy & EXACT_COPY; if (exactCopy || rhs.m_d->isolatedRootNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (exactCopy) { node->setUuid(refNode->uuid()); } if (rhs.m_d->isolatedRootNode && rhs.m_d->isolatedRootNode == refNode) { m_d->isolatedRootNode = node; } }); } KisLayerUtils::recursiveApplyNodes(newRoot, [](KisNodeSP node) { dbgImage << "Node: " << (void *)node.data(); }); m_d->compositions.clear(); Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } EMIT_IF_NEEDED sigLayersChangedAsync(); m_d->nserver = rhs.m_d->nserver; vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; /** * The overlay device is not inherited when cloning the image! */ if (rhs.m_d->overlaySelectionMask) { const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent(); m_d->rootLayer->setDirty(dirtyRect); } #undef EMIT_IF_NEEDED } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { // make sure KisImage belongs to the GUI thread moveToThread(qApp->thread()); connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode())); copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0)); } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data()) && deletedNode == m_d->isolatedRootNode) { emit sigInternalStopIsolatedModeRequested(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask) { if (m_d->targetOverlaySelectionMask == mask) return; m_d->targetOverlaySelectionMask = mask; struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy { UpdateOverlaySelectionStroke(KisImageSP image) : KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask; KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask; if (oldMask == newMask) return; KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image); m_image->m_d->overlaySelectionMask = newMask; if (oldMask || newMask) { m_image->m_d->rootLayer->notifyChildMaskChanged(); } if (oldMask) { m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent()); } if (newMask) { newMask->setDirty(); } m_image->undoAdapter()->emitSelectionChanged(); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this)); endStroke(id); } KisSelectionMaskSP KisImage::overlaySelectionMask() const { return m_d->overlaySelectionMask; } bool KisImage::hasOverlaySelectionMask() const { return m_d->overlaySelectionMask; } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0); KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle(bool allowLocked) { return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; QPointF offset; { KisTransformWorker worker(0, scaleX, scaleY, 0, 0, 0, 0, 0.0, 0, 0, 0, 0); QTransform transform = worker.transform(); offset = center - transform.map(center); } KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, offset.x(), offset.y(), filterStrategy); visitor->setSelection(selection); if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection) { // we can either transform (and resize) the whole image or // transform a selection, we cannot do both at the same time KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) { selection = 0; } const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(baseBounds); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(baseBounds).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != baseBounds.width() || newSize.height() != baseBounds.height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0); } void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection) { if (node->inherits("KisMask")) { rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection); } else { rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection); } } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection) { const QRect baseBounds = resizeImage ? bounds() : selection ? selection->selectedExactRect() : rootNode->exactBounds(); const QPointF origin = QRectF(baseBounds).center(); //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(baseBounds); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == baseBounds.size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisTransformProcessingVisitor *visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); if (selection) { visitor->setSelection(selection); } if (selection) { applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); } else { applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection) { if (node->inherits("KisMask")) { shearImpl(kundo2_i18n("Shear Mask"), node, false, angleX, angleY, selection); } else { shearImpl(kundo2_i18n("Shear Layer"), node, false, angleX, angleY, selection); } } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, 0); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); bool retval = m_d->rootLayer->accept(visitor); m_d->signalRouter.emitNotification(ProfileChangedSignal); return retval; } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y())); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten(KisNodeSP activeNode) { KisLayerUtils::flattenImage(this, activeNode); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } +const KUndo2Command* KisImage::lastExecutedCommand() const +{ + return m_d->undoStore->presentCommand(); +} + void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { emit sigInternalStopIsolatedModeRequested(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); setRoot(m_d->rootLayer.data()); this->setDefaultProjectionColor(defaultProjectionColor); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { struct StartIsolatedModeStroke : public KisSimpleStrokeStrategy { StartIsolatedModeStroke(KisNodeSP node, KisImageSP image) : KisSimpleStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")), m_node(node), m_image(image) { this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); setClearsRedoOnStart(false); } void initStrokeCallback() { // pass-though node don't have any projection prepared, so we should // explicitly regenerate it before activating isolated mode. m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); m_image->m_d->isolatedRootNode = m_node; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); } private: KisNodeSP m_node; KisImageSP m_image; }; KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this)); endStroke(id); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; struct StopIsolatedModeStroke : public KisSimpleStrokeStrategy { StopIsolatedModeStroke(KisImageSP image) : KisSimpleStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")), m_image(image) { this->enableJob(JOB_INIT); setClearsRedoOnStart(false); } void initStrokeCallback() { if (!m_image->m_d->isolatedRootNode) return; //KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode; m_image->m_d->isolatedRootNode = 0; emit m_image->sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds()); m_image->invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } private: KisImageSP m_image; }; KisStrokeId id = startStroke(new StopIsolatedModeStroke(this)); endStroke(id); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // update filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } +void KisImage::notifyBatchUpdateStarted() +{ + m_d->signalRouter.emitNotifyBatchUpdateStarted(); +} + +void KisImage::notifyBatchUpdateEnded() +{ + m_d->signalRouter.emitNotifyBatchUpdateEnded(); +} + +void KisImage::notifyUIUpdateCompleted(const QRect &rc) +{ + notifyProjectionUpdated(rc); +} + QVector KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); QRect rect; QVector postponedUpdates; while (m_d->savedDisabledUIUpdates.pop(rect)) { postponedUpdates.append(rect); } return postponedUpdates; } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } else { m_d->savedDisabledUIUpdates.push(rc); } } void KisImage::setWorkingThreadsLimit(int value) { m_d->scheduler.setThreadsLimit(value); } int KisImage::workingThreadsLimit() const { return m_d->scheduler.threadsLimit(); } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect) { if (rects.isEmpty()) return; m_d->scheduler.updateProjection(node, rects, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) { return; } if (resetAnimationCache) { m_d->animationInterface->notifyNodeChanged(node, rects, false); } /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { QVector allSplitRects; const QRect boundRect = effectiveLodBounds(); Q_FOREACH (const QRect &rc, rects) { KisWrappedRect splitRect(rc, boundRect); allSplitRects.append(splitRect); } requestProjectionUpdateImpl(node, allSplitRects, boundRect); } else { requestProjectionUpdateImpl(node, rects, bounds()); } KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache); } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } KisNode *KisImage::graphOverlayNode() const { return m_d->overlaySelectionMask.data(); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { if (m_d->wrapAroundModePermitted != value) { requestStrokeEnd(); } m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (m_d->proofingConfig) { return m_d->proofingConfig; } return KisProofingConfigurationSP(); } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } void KisImage::setAllowMasksOnRootNode(bool value) { m_d->allowMasksOnRootNode = value; } bool KisImage::allowMasksOnRootNode() const { return m_d->allowMasksOnRootNode; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index 4844b6cb1d..dea01e86a1 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,1156 +1,1185 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include "kis_strokes_queue_undo_result.h" #include class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; class KisPaintDevice; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisStrokeUndoFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @p colorSpace can be null. In that case, it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name); ~KisImage() override; static KisImageSP fromQImage(const QImage &image, KisUndoStore *undoStore); public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index) override; void nodeHasBeenAdded(KisNode *parent, int index) override; void aboutToRemoveANode(KisNode *parent, int index) override; void nodeChanged(KisNode * node) override; void invalidateAllFrames() override; void notifySelectionChanged() override; void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override; void invalidateFrames(const KisTimeRange &range, const QRect &rect) override; void requestTimeSwitch(int time) override; KisNode* graphOverlayNode() const override; public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc) override; public: /** * Set the number of threads used by the image's working threads */ void setWorkingThreadsLimit(int value); /** * Return the number of threads available to the image's working threads */ int workingThreadsLimit() const; /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage *clone(bool exactCopy = false); void copyFromImage(const KisImage &rhs); private: // must specify exactly one from CONSTRUCT or REPLACE. enum CopyPolicy { CONSTRUCT = 1, ///< we are copy-constructing a new KisImage REPLACE = 2, ///< we are replacing the current KisImage with another EXACT_COPY = 4, /// we need an exact copy of the original image }; void copyFromImageImpl(const KisImage &rhs, int policy); public: /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * Render a thumbnail of the projection onto a QImage. */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * @return return true if the image is in a locked state, i.e. all the internal * jobs are blocked from execution by calling wither lock() or barrierLock(). * * When the image is locked, the user can do some modifications to the image * contents safely without a perspective having race conditions with internal * image jobs. */ bool locked() const; /** * Sets the mask (it must be a part of the node hierarchy already) to be paited on * the top of all layers. This method does all the locking and syncing for you. It * is executed asynchronously. */ void setOverlaySelectionMask(KisSelectionMaskSP mask); /** * \see setOverlaySelectionMask */ KisSelectionMaskSP overlaySelectionMask() const; /** * \see setOverlaySelectionMask */ bool hasOverlaySelectionMask() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * @brief start asynchronous operation on resizing the image * * The method will resize the image to fit the new size without * dropping any pixel data. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be visible * after operation is completed * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void resizeImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping the image * * The method will **drop** all the image data outside \p newRect * and resize the image to fit the new size. The GUI will get correct * notification with old and new sizes, so it adjust canvas origin * accordingly and avoid jumping of the canvas on screen * * @param newRect the rectangle of the image which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropImage(const QRect& newRect); /** * @brief start asynchronous operation on cropping a subtree of nodes starting at \p node * * The method will **drop** all the layer data outside \p newRect. Neither * image nor a layer will be moved anywhere * * @param node node to crop * @param newRect the rectangle of the layer which will be cut-out * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void cropNode(KisNodeSP node, const QRect& newRect); /** * @brief start asynchronous operation on scaling the image * @param size new image size in pixels * @param xres new image x-resolution pixels-per-pt * @param yres new image y-resolution pixels-per-pt * @param filterStrategy filtering strategy * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /** * @brief start asynchronous operation on scaling a subtree of nodes starting at \p node * @param node node to scale * @param center the center of the scaling * @param scaleX x-scale coefficient to be applied to the node * @param scaleY y-scale coefficient to be applied to the node * @param filterStrategy filtering strategy * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the image having new size * right after this call. */ void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection); /** * @brief start asynchronous operation on rotating the image * * The image is resized to fit the rotated rectangle * * @param radians rotation angle in radians * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateImage(double radians); /** * @brief start asynchronous operation on rotating a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param radians rotation angle in radians * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection); /** * @brief start asynchronous operation on shearing the image * * The image is resized to fit the sheared polygon * * @p angleX, @p angleY are given in degrees. * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shear(double angleX, double angleY); /** * @brief start asynchronous operation on shearing a subtree of nodes starting at \p node * * The image is not resized! * * @param node the root of the subtree to rotate * @param angleX x-shear given in degrees. * @param angleY y-shear given in degrees. * @param selection the selection we based on * * Please note that the actual operation starts asynchronously in * a background, so you cannot expect the operation being completed * right after the call */ void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * @note No conversion is done, only regeneration, so no rendering * intent needed */ void convertProjectionColorSpace(const KoColorSpace *dstColorSpace); // Get the profile associated with this image const KoColorProfile * profile() const; /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * * This does not create an undo action; only call it when creating or * loading an image. * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * and executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override; + /** + * Return the lastly executed LoD0 command. It is effectively the same + * as to call undoAdapter()->presentCommand(); + */ + const KUndo2Command* lastExecutedCommand() const; + /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate rounded down. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToImagePixelFloored(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(KisNodeSP activeNode); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is deleted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerCompositionSP composition); /** * Remove the layer compostion */ void removeComposition(KisLayerCompositionSP composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return current level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; /** * Configure the image to allow masks on the root not (as reported by * root()->allowAsChild()). By default it is not allowed (because it * looks weird from GUI point of view) */ void setAllowMasksOnRootNode(bool value); /** * \see setAllowMasksOnRootNode() */ bool allowMasksOnRootNode() const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; /** * Notifies that the node collapsed state has changed */ void notifyNodeCollpasedChanged(); KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public Q_SLOTS: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); public: KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. Parameter is the rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); /** * Emitted when the proofing configuration of the image is being changed. * */ void sigProofingConfigChanged(); /** * Internal signal for asynchronously requesting isolated mode to stop. Don't use it * outside KisImage, use sigIsolatedModeChanged() instead. */ void sigInternalStopIsolatedModeRequested(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); bool isIdle(bool allowLocked = false); /** * @brief Wait until all the queued background jobs are completed and lock the image. * * KisImage object has a local scheduler that executes long-running image * rendering/modifying jobs (we call them "strokes") in a background. Basically, * one should either access the image from the scope of such jobs (strokes) or * just lock the image before using. * * Calling barrierLock() will wait until all the queued operations are finished * and lock the image, so you can start accessing it in a safe way. * * @p readOnly tells the image if the caller is going to modify the image during * holding the lock. Locking with non-readOnly access will reset all * the internal caches of the image (lod-planes) when the lock status * will be lifted. */ void barrierLock(bool readOnly = false); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired * @see barrierLock */ bool tryBarrierLock(bool readOnly = false); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override; void addJob(KisStrokeId id, KisStrokeJobData *data) override; void endStroke(KisStrokeId id) override; bool cancelStroke(KisStrokeId id) override; /** * @brief blockUpdates block updating the image projection */ void blockUpdates() override; /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates() override; /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. * * The last call to enableUIUpdates() will return the list of udpates * that were requested while they were blocked. */ void disableUIUpdates() override; + /** + * Notify GUI about a bunch of updates planned. GUI is expected to wait + * until all the updates are completed, and render them on screen only + * in the very and of the batch. + */ + void notifyBatchUpdateStarted() override; + + /** + * Notify GUI that batch update has been completed. Now GUI can start + * showing all of them on screen. + */ + void notifyBatchUpdateEnded() override; + + /** + * Notify GUI that rect \p rc is now prepared in the image and + * GUI can read data from it. + * + * WARNING: GUI will read the data right in the handler of this + * signal, so exclusive access to the area must be guaranteed + * by the caller. + */ + void notifyUIUpdateCompleted(const QRect &rc) override; + /** * \see disableUIUpdates */ QVector enableUIUpdates() override; /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests() override; /** * \see disableDirtyRequests() */ void enableDirtyRequests() override; /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override; /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override; void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc) override; void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override; /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method requests the last stroke executed on the image to become undone. * If the stroke is not ended, or if all the Lod0 strokes are completed, the method * returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT * is returned and the caller should just wait for its completion and call global undo * instead. UNDO_OK means one unfinished stroke has been undone. */ UndoResult tryUndoUnfinishedLod0Stroke(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to distinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians, bool resizeImage, KisSelectionSP selection); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, KisSelectionSP selection); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QVector &rects, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; friend class Document; // For libkis /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_image_interfaces.h b/libs/image/kis_image_interfaces.h index 79c59d9819..4dc3387ca6 100644 --- a/libs/image/kis_image_interfaces.h +++ b/libs/image/kis_image_interfaces.h @@ -1,77 +1,84 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_IMAGE_INTERFACES_H #define __KIS_IMAGE_INTERFACES_H #include "kis_types.h" #include class QRect; class KisStrokeStrategy; class KisStrokeJobData; class KisPostExecutionUndoAdapter; class KRITAIMAGE_EXPORT KisStrokesFacade { public: virtual ~KisStrokesFacade(); virtual KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) = 0; virtual void addJob(KisStrokeId id, KisStrokeJobData *data) = 0; virtual void endStroke(KisStrokeId id) = 0; virtual bool cancelStroke(KisStrokeId id) = 0; }; class KRITAIMAGE_EXPORT KisUpdatesFacade { public: virtual ~KisUpdatesFacade(); virtual void blockUpdates() = 0; virtual void unblockUpdates() = 0; virtual void disableUIUpdates() = 0; virtual QVector enableUIUpdates() = 0; + virtual void notifyBatchUpdateStarted() = 0; + virtual void notifyBatchUpdateEnded() = 0; + virtual void notifyUIUpdateCompleted(const QRect &rc) = 0; + + virtual QRect bounds() const = 0; + virtual void disableDirtyRequests() = 0; virtual void enableDirtyRequests() = 0; virtual void refreshGraphAsync(KisNodeSP root) = 0; virtual void refreshGraphAsync(KisNodeSP root, const QRect &rc) = 0; virtual void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) = 0; virtual void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) = 0; virtual KisProjectionUpdatesFilterSP projectionUpdatesFilter() const = 0; }; class KRITAIMAGE_EXPORT KisProjectionUpdateListener { public: virtual ~KisProjectionUpdateListener(); virtual void notifyProjectionUpdated(const QRect &rc) = 0; }; class KRITAIMAGE_EXPORT KisStrokeUndoFacade { public: virtual ~KisStrokeUndoFacade(); virtual KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const = 0; + virtual const KUndo2Command* lastExecutedCommand() const = 0; }; #endif /* __KIS_IMAGE_INTERFACES_H */ diff --git a/libs/image/kis_stroke_strategy_undo_command_based.cpp b/libs/image/kis_stroke_strategy_undo_command_based.cpp index 0b0654fad9..37560264ee 100644 --- a/libs/image/kis_stroke_strategy_undo_command_based.cpp +++ b/libs/image/kis_stroke_strategy_undo_command_based.cpp @@ -1,170 +1,185 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_stroke_strategy_undo_command_based.h" #include #include "kis_image_interfaces.h" #include "kis_post_execution_undo_adapter.h" #include "commands_new/kis_saved_commands.h" KisStrokeStrategyUndoCommandBased:: KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name, bool undo, KisStrokeUndoFacade *undoFacade, KUndo2CommandSP initCommand, KUndo2CommandSP finishCommand) - : KisSimpleStrokeStrategy("STROKE_UNDO_COMMAND_BASED", name), + : KisRunnableBasedStrokeStrategy("STROKE_UNDO_COMMAND_BASED", name), m_undo(undo), m_initCommand(initCommand), m_finishCommand(finishCommand), m_undoFacade(undoFacade), m_macroId(-1), m_macroCommand(0) { enableJob(KisSimpleStrokeStrategy::JOB_INIT); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL); enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); } KisStrokeStrategyUndoCommandBased:: KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs) - : KisSimpleStrokeStrategy(rhs), + : KisRunnableBasedStrokeStrategy(rhs), m_undo(false), m_initCommand(rhs.m_initCommand), m_finishCommand(rhs.m_finishCommand), m_undoFacade(rhs.m_undoFacade), m_macroCommand(0) { KIS_ASSERT_RECOVER_NOOP(!rhs.m_macroCommand && !rhs.m_undo && "After the stroke has been started, no copying must happen"); } void KisStrokeStrategyUndoCommandBased::setUsedWhileUndoRedo(bool value) { setClearsRedoOnStart(!value); } void KisStrokeStrategyUndoCommandBased::executeCommand(KUndo2CommandSP command, bool undo) { if(!command) return; + if (MutatedCommandInterface *mutatedCommand = dynamic_cast(command.data())) { + mutatedCommand->setRunnableJobsInterface(this->runnableJobsInterface()); + } + if(undo) { command->undo(); } else { command->redo(); } } void KisStrokeStrategyUndoCommandBased::initStrokeCallback() { if(m_undoFacade) { m_macroCommand = m_undoFacade->postExecutionUndoAdapter()->createMacro(name()); } executeCommand(m_initCommand, m_undo); notifyCommandDone(m_initCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void KisStrokeStrategyUndoCommandBased::finishStrokeCallback() { executeCommand(m_finishCommand, m_undo); notifyCommandDone(m_finishCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); QMutexLocker locker(&m_mutex); if(m_macroCommand) { Q_ASSERT(m_undoFacade); postProcessToplevelCommand(m_macroCommand); m_undoFacade->postExecutionUndoAdapter()->addMacro(m_macroCommand); m_macroCommand = 0; } } void KisStrokeStrategyUndoCommandBased::cancelStrokeCallback() { QMutexLocker locker(&m_mutex); if(m_macroCommand) { m_macroCommand->performCancel(cancelStrokeId(), m_undo); delete m_macroCommand; m_macroCommand = 0; } } void KisStrokeStrategyUndoCommandBased::doStrokeCallback(KisStrokeJobData *data) { Data *d = dynamic_cast(data); - executeCommand(d->command, d->undo); - notifyCommandDone(d->command, d->sequentiality(), d->exclusivity()); + + if (d) { + executeCommand(d->command, d->undo); + notifyCommandDone(d->command, d->sequentiality(), d->exclusivity()); + } else { + KisRunnableBasedStrokeStrategy::doStrokeCallback(data); + } + } void KisStrokeStrategyUndoCommandBased::runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { if (!command) return; - command->redo(); + executeCommand(command, false); notifyCommandDone(command, sequentiality, exclusivity); } void KisStrokeStrategyUndoCommandBased::notifyCommandDone(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity) { if(!command) return; QMutexLocker locker(&m_mutex); if(m_macroCommand) { m_macroCommand->addCommand(command, sequentiality, exclusivity); } } void KisStrokeStrategyUndoCommandBased::setCommandExtraData(KUndo2CommandExtraData *data) { if (m_undoFacade && m_macroCommand) { warnKrita << "WARNING: KisStrokeStrategyUndoCommandBased::setCommandExtraData():" << "the extra data is set while the stroke has already been started!" << "The result is undefined, continued actions may not work!"; } m_commandExtraData.reset(data); } void KisStrokeStrategyUndoCommandBased::setMacroId(int value) { m_macroId = value; } void KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(KUndo2Command *command) { if (m_commandExtraData) { command->setExtraData(m_commandExtraData.take()); } KisSavedMacroCommand *savedCommand = dynamic_cast(command); if (savedCommand) { savedCommand->setMacroId(m_macroId); } } + + KisStrokeUndoFacade* KisStrokeStrategyUndoCommandBased::undoFacade() const + { + return m_undoFacade; + } diff --git a/libs/image/kis_stroke_strategy_undo_command_based.h b/libs/image/kis_stroke_strategy_undo_command_based.h index 9a08fde55c..41ca43c3fd 100644 --- a/libs/image/kis_stroke_strategy_undo_command_based.h +++ b/libs/image/kis_stroke_strategy_undo_command_based.h @@ -1,140 +1,162 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H #define __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H #include #include #include #include "kis_types.h" #include "kis_simple_stroke_strategy.h" +#include "KisRunnableBasedStrokeStrategy.h" class KisStrokeJob; class KisSavedMacroCommand; class KisStrokeUndoFacade; +class KisStrokesQueueMutatedJobInterface; -class KRITAIMAGE_EXPORT KisStrokeStrategyUndoCommandBased : public KisSimpleStrokeStrategy + +class KRITAIMAGE_EXPORT KisStrokeStrategyUndoCommandBased : public KisRunnableBasedStrokeStrategy { public: + struct MutatedCommandInterface + { + virtual ~MutatedCommandInterface() {} + + void setRunnableJobsInterface(KisRunnableStrokeJobsInterface *interface) { + m_mutatedJobsInterface = interface; + } + + KisRunnableStrokeJobsInterface* runnableJobsInterface() const { + return m_mutatedJobsInterface; + } + + private: + KisRunnableStrokeJobsInterface *m_mutatedJobsInterface; + }; + + class Data : public KisStrokeJobData { public: Data(KUndo2CommandSP _command, bool _undo = false, Sequentiality _sequentiality = SEQUENTIAL, Exclusivity _exclusivity = NORMAL) : KisStrokeJobData(_sequentiality, _exclusivity), command(_command), undo(_undo) { } Data(KUndo2Command *_command, bool _undo = false, Sequentiality _sequentiality = SEQUENTIAL, Exclusivity _exclusivity = NORMAL) : KisStrokeJobData(_sequentiality, _exclusivity), command(_command), undo(_undo) { } KUndo2CommandSP command; bool undo; }; public: KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name, bool undo, KisStrokeUndoFacade *undoFacade, KUndo2CommandSP initCommand = KUndo2CommandSP(0), KUndo2CommandSP finishCommand = KUndo2CommandSP(0)); using KisSimpleStrokeStrategy::setExclusive; void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; /** * Set extra data that will be assigned to the command * representing this action. Using extra data has the following * restrictions: * * 1) The \p data must be set *before* the stroke has been started. * Setting the \p data after the stroke has been started with * image->startStroke(strokeId) leads to an undefined behaviour. * * 2) \p data becomes owned by the strategy/command right after * setting it. Don't try to change it afterwards. */ void setCommandExtraData(KUndo2CommandExtraData *data); /** * Sets the id of this action. Will be used for merging the undo commands * * The \p value must be set *before* the stroke has been started. * Setting the \p value after the stroke has been started with * image->startStroke(strokeId) leads to an undefined behaviour. */ void setMacroId(int value); /** * The undo-command-based is a low-level strategy, so it allows * changing its wraparound mode status. * * WARNING: the switch must be called *before* the stroke has been * started! Otherwise the mode will not be activated. */ using KisStrokeStrategy::setSupportsWrapAroundMode; void setUsedWhileUndoRedo(bool value); protected: void runAndSaveCommand(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity); void notifyCommandDone(KUndo2CommandSP command, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity); KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs); virtual void postProcessToplevelCommand(KUndo2Command *command); + KisStrokeUndoFacade* undoFacade() const; + private: void executeCommand(KUndo2CommandSP command, bool undo); private: bool m_undo; KUndo2CommandSP m_initCommand; KUndo2CommandSP m_finishCommand; KisStrokeUndoFacade *m_undoFacade; QScopedPointer m_commandExtraData; int m_macroId; // protects done commands only QMutex m_mutex; KisSavedMacroCommand *m_macroCommand; }; #endif /* __KIS_STROKE_STRATEGY_UNDO_COMMAND_BASED_H */ diff --git a/libs/image/tests/kis_group_layer_test.cpp b/libs/image/tests/kis_group_layer_test.cpp index 10ba51190a..5c04bcac1e 100644 --- a/libs/image/tests/kis_group_layer_test.cpp +++ b/libs/image/tests/kis_group_layer_test.cpp @@ -1,111 +1,111 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_group_layer_test.h" #include #include "testutil.h" #include #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_types.h" #include "KoColorSpaceRegistry.h" #include "kis_image.h" #include "kis_undo_stores.h" void KisGroupLayerTest::testProjection() { const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 512, 512, colorSpace, "merge test"); } #include "commands/kis_image_layer_remove_command.h" void KisGroupLayerTest::testRemoveAndUndo() { KisSurrogateUndoStore *undoStore = new KisSurrogateUndoStore(); - QRect transpRect(50,50,300,300); - QRect blurRect(66,66,300,300); - QPoint blurShift(34,34); - QPoint cloneShift(75,75); +// QRect transpRect(50,50,300,300); +// QRect blurRect(66,66,300,300); +// QPoint blurShift(34,34); +// QPoint cloneShift(75,75); QRect imageRect = QRect(0, 0, 64, 64); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "merge test"); QRect rect1(10, 10, 30, 30); QRect rect2(30, 30, 30, 30); KisPaintLayerSP paintLayer0 = new KisPaintLayer(image, "paint0", OPACITY_OPAQUE_U8); paintLayer0->paintDevice()->fill(imageRect, KoColor(Qt::white, cs)); KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); paintLayer1->paintDevice()->fill(rect1, KoColor(Qt::red, cs)); KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); paintLayer2->paintDevice()->fill(rect2, KoColor(Qt::blue, cs)); KisGroupLayerSP groupLayer1 = new KisGroupLayer(image, "group1", OPACITY_OPAQUE_U8); image->addNode(paintLayer0, image->root()); image->addNode(groupLayer1, image->root()); image->addNode(paintLayer1, groupLayer1); image->addNode(paintLayer2, groupLayer1); image->initialRefreshGraph(); QVERIFY(TestUtil::checkQImage(image->projection()->convertToQImage(0, imageRect), "group_layer_test", "undo_removal", "0_initial")); // touch original to cause clearing of the projection groupLayer1->original(); image->undoAdapter()->addCommand(new KisImageLayerRemoveCommand(image, groupLayer1)); image->waitForDone(); // touch original again groupLayer1->original(); QVERIFY(TestUtil::checkQImage(image->projection()->convertToQImage(0, imageRect), "group_layer_test", "undo_removal", "1_deleted")); undoStore->undo(); image->waitForDone(); // hey, man! don't sleep! groupLayer1->original(); QVERIFY(TestUtil::checkQImage(image->projection()->convertToQImage(0, imageRect), "group_layer_test", "undo_removal", "2_undone")); } QTEST_MAIN(KisGroupLayerTest) diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp index 035c843720..b4f64c4a85 100644 --- a/libs/image/tests/kis_image_test.cpp +++ b/libs/image/tests/kis_image_test.cpp @@ -1,1241 +1,1239 @@ /* * Copyright (c) 2005 Adrian Page * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_test.h" #include #include #include #include #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "kis_selection.h" #include #include #include "kis_keyframe_channel.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "kis_annotation.h" #include "KisProofingConfiguration.h" #include "kis_undo_stores.h" #define IMAGE_WIDTH 128 #define IMAGE_HEIGHT 128 void KisImageTest::layerTests() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8); image->addNode(layer); QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName()); } void KisImageTest::benchmarkCreation() { const QRect imageRect(0,0,3000,2000); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); QList images; QList stores; QBENCHMARK { for (int i = 0; i < 10; i++) { stores << new KisSurrogateUndoStore(); } for (int i = 0; i < 10; i++) { KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image"); images << image; } } } #include "testutil.h" #include "kis_stroke_strategy.h" #include class ForbiddenLodStrokeStrategy : public KisStrokeStrategy { public: ForbiddenLodStrokeStrategy(std::function lodCallback) : m_lodCallback(lodCallback) { } KisStrokeStrategy* createLodClone(int levelOfDetail) override { Q_UNUSED(levelOfDetail); m_lodCallback(); return 0; } private: std::function m_lodCallback; }; void notifyVar(bool *value) { *value = true; } void KisImageTest::testBlockLevelOfDetail() { TestUtil::MaskParent p; QCOMPARE(p.image->currentLevelOfDetail(), 0); p.image->setDesiredLevelOfDetail(1); p.image->waitForDone(); QCOMPARE(p.image->currentLevelOfDetail(), 0); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } p.image->setLevelOfDetailBlocked(true); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(!lodCreated); } p.image->setLevelOfDetailBlocked(false); p.image->setDesiredLevelOfDetail(1); { bool lodCreated = false; KisStrokeId id = p.image->startStroke( new ForbiddenLodStrokeStrategy( std::bind(¬ifyVar, &lodCreated))); p.image->endStroke(id); p.image->waitForDone(); QVERIFY(lodCreated); } } void KisImageTest::testConvertImageColorSpace() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); KisPaintDeviceSP device1 = new KisPaintDevice(cs8); KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1); KisFilterSP filter = KisFilterRegistry::instance()->value("blur"); Q_ASSERT(filter); KisFilterConfigurationSP configuration = filter->defaultConfiguration(); Q_ASSERT(configuration); KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0); image->addNode(paint1, image->root()); image->addNode(blur1, image->root()); image->refreshGraph(); const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16(); image->lock(); image->convertImageColorSpace(cs16, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->unlock(); QVERIFY(*cs16 == *image->colorSpace()); QVERIFY(*cs16 == *image->root()->colorSpace()); QVERIFY(*cs16 == *paint1->colorSpace()); QVERIFY(*cs16 == *blur1->colorSpace()); QVERIFY(!image->root()->compositeOp()); QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace()); QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace()); image->refreshGraph(); } void KisImageTest::testGlobalSelection() { const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest"); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 0U); KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image)); KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image)); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->setGlobalSelection(selection2); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->reselectGlobalSelection(); QCOMPARE(image->globalSelection(), selection2); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); // mixed deselecting/setting/reselecting image->deselectGlobalSelection(); QCOMPARE(image->globalSelection(), KisSelectionSP(0)); QCOMPARE(image->canReselectGlobalSelection(), true); QCOMPARE(image->root()->childCount(), 0U); image->setGlobalSelection(selection1); QCOMPARE(image->globalSelection(), selection1); QCOMPARE(image->canReselectGlobalSelection(), false); QCOMPARE(image->root()->childCount(), 1U); } void KisImageTest::testCloneImage() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray()); image->addAnnotation(annotation); QVERIFY(image->annotation("mytype")); KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration()); image->setProofingConfiguration(proofing); QVERIFY(image->proofingConfiguration()); const KoColor defaultColor(Qt::green, image->colorSpace()); image->setDefaultProjectionColor(defaultColor); QCOMPARE(image->defaultProjectionColor(), defaultColor); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); QVERIFY(TestUtil::findNode(image->root(), "layer1")); QVERIFY(TestUtil::findNode(image->root(), "layer2")); QUuid uuid1 = layer->uuid(); QUuid uuid2 = layer2->uuid(); { KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() != uuid1); QVERIFY(newLayer2->uuid() != uuid2); KisAnnotationSP newAnnotation = newImage->annotation("mytype"); QVERIFY(newAnnotation); QVERIFY(newAnnotation != annotation); KisProofingConfigurationSP newProofing = newImage->proofingConfiguration(); QVERIFY(newProofing); QVERIFY(newProofing != proofing); QCOMPARE(newImage->defaultProjectionColor(), defaultColor); } { KisImageSP newImage = image->clone(true); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->uuid() == uuid1); QVERIFY(newLayer2->uuid() == uuid2); } } void KisImageTest::testLayerComposition() { KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests"); QVERIFY(image->rootLayer() != 0); QVERIFY(image->rootLayer()->firstChild() == 0); KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8); image->addNode(layer); KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8); image->addNode(layer2); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisLayerComposition comp(image, "comp 1"); comp.store(); layer2->setVisible(false); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); KisLayerComposition comp2(image, "comp 2"); comp2.store(); KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3")); comp3->store(); image->addComposition(comp3); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); comp2.apply(); QVERIFY(layer->visible()); QVERIFY(!layer2->visible()); comp.apply(); QVERIFY(layer->visible()); QVERIFY(layer2->visible()); KisImageSP newImage = image->clone(); KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1"); KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2"); QVERIFY(newLayer1); QVERIFY(newLayer2); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp1(comp, newImage); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); KisLayerComposition newComp2(comp2, newImage); newComp2.apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); newComp1.apply(); QVERIFY(newLayer1->visible()); QVERIFY(newLayer2->visible()); QVERIFY(!newImage->compositions().isEmpty()); KisLayerCompositionSP newComp3 = newImage->compositions().first(); newComp3->apply(); QVERIFY(newLayer1->visible()); QVERIFY(!newLayer2->visible()); } #include "kis_transparency_mask.h" #include "kis_psd_layer_style.h" struct FlattenTestImage { FlattenTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; layer1 = p.layer; layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8); layer5->disableAlphaChannel(true); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); tmask = new KisTransparencyMask(); // check channel flags // make addition composite op group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8); layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8); layer7->setCompositeOpId(COMPOSITE_ADD); layer8->setCompositeOpId(COMPOSITE_ADD); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect tmaskRect(200,200,100,100); QRect rect3(400, 100, 100, 100); QRect rect4(500, 100, 100, 100); QRect rect5(50, 50, 100, 100); QRect rect6(50, 250, 100, 100); QRect rect7(50, 350, 50, 50); QRect rect8(50, 400, 50, 50); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace())); tmask->testingInitSelection(tmaskRect, layer2); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace())); layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace())); layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace())); layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace())); layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace())); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setDistance(10.0); style->dropShadow()->setSpread(80.0); style->dropShadow()->setSize(10); style->dropShadow()->setNoise(0); style->dropShadow()->setKnocksOut(false); style->dropShadow()->setOpacity(80.0); layer2->setLayerStyle(style); layer2->setCompositeOpId(COMPOSITE_ADD); group1->setCompositeOpId(COMPOSITE_ADD); p.image->addNode(layer5); p.image->addNode(layer2); p.image->addNode(tmask, layer2); p.image->addNode(group1); p.image->addNode(layer3, group1); p.image->addNode(layer4, group1); p.image->addNode(layer6); p.image->addNode(layer7); p.image->addNode(layer8); p.image->initialRefreshGraph(); // dbgKrita << ppVar(layer1->exactBounds()); // dbgKrita << ppVar(layer5->exactBounds()); // dbgKrita << ppVar(layer2->exactBounds()); // dbgKrita << ppVar(group1->exactBounds()); // dbgKrita << ppVar(layer3->exactBounds()); // dbgKrita << ppVar(layer4->exactBounds()); TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisTransparencyMaskSP tmask; KisGroupLayerSP group1; KisPaintLayerSP layer3; KisPaintLayerSP layer4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; KisPaintLayerSP layer7; KisPaintLayerSP layer8; }; template KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); //p.image->flattenLayer(layer); KisLayerUtils::flattenLayer(p.image, layer); p.image->waitForDone(); if (nothingHappens) { Q_ASSERT(!spy.count()); return layer; } Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); KisLayerSP newLayer = qobject_cast(newNode.data()); return newLayer; } void KisImageTest::testFlattenLayer() { FlattenTestImage p; TestUtil::ReferenceImageChecker chk("flatten", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.layer2); //KisLayerSP newLayer = p.image->flattenLayer(p.layer2); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); //KisLayerSP newLayer = p.image->flattenLayer(p.group1); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100)); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true); //KisLayerSP newLayer = p.image->flattenLayer(p.layer5); //p.image->waitForDone(); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), true); } } #include template KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer) { KisNodeSP parent = layer->parent(); const int newIndex = parent->index(layer) - 1; p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); //KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); p.image->waitForDone(); KisLayerSP newLayer = qobject_cast(parent->at(newIndex).data()); return newLayer; } void KisImageTest::testMergeDown() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationInheritsAlpha() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_inheritsalpha", "imagetest"); { QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer2); // WARN: this check is suspicious! QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationCustomCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_dst_customop", "imagetest"); { QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer6->alphaChannelDisabled(), false); QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer6); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_ls", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.group1->alphaChannelDisabled(), false); QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer2->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.group1); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } void KisImageTest::testMergeDownDestinationSameCompositeOp() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_sameop_fastpath", "imagetest"); { QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer8->alphaChannelDisabled(), false); QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD); QCOMPARE(p.layer7->alphaChannelDisabled(), false); KisLayerSP newLayer = mergeHelper(p, p.layer8); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD); QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100)); QCOMPARE(newLayer->alphaChannelDisabled(), false); } } #include "kis_image_animation_interface.h" void KisImageTest::testMergeDownMultipleFrames() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergedown_simple", "imagetest"); QSet initialFrames; { KisLayerSP l = p.layer5; l->enableAnimation(); KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); QCOMPARE(channel->keyframeCount(), 4); initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l); QCOMPARE(initialFrames.size(), 4); } { QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.layer5->alphaChannelDisabled(), true); KisLayerSP newLayer = mergeHelper(p, p.layer5); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->alphaChannelDisabled(), false); QVERIFY(newLayer->isAnimated()); QSet newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer); QCOMPARE(newFrames, initialFrames); foreach (int frame, newFrames) { KisImageAnimationInterface *interface = p.image->animationInterface(); int savedSwitchedTime = 0; interface->saveAndResetCurrentTime(frame, &savedSwitchedTime); QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100)); interface->restoreCurrentTime(&savedSwitchedTime); } p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } template KisNodeSP mergeMultipleHelper(ContainerTest &p, QList selectedNodes, KisNodeSP putAfter) { QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP))); p.image->mergeMultipleLayers(selectedNodes, putAfter); //KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter); p.image->waitForDone(); Q_ASSERT(spy.count() == 1); QList arguments = spy.takeFirst(); KisNodeSP newNode = arguments.first().value(); return newNode; } void KisImageTest::testMergeMultiple() { FlattenTestImage p; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); TestUtil::ReferenceImageChecker chk("mergemultiple", "imagetest"); { QList selectedNodes; selectedNodes << p.layer2 << p.group1 << p.layer6; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } p.p.undoStore->undo(); p.image->waitForDone(); // Test reversed order, the result must be the same { QList selectedNodes; selectedNodes << p.layer6 << p.group1 << p.layer2; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); //KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0); //p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250)); } } } void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces) { - QRect refRect; TestUtil::MaskParent p; KisPaintLayerSP layer1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; const KoColorSpace *cs2 = useProjectionColorSpace ? p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16(); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); if (swapSpaces) { std::swap(cs2, cs3); } dbgKrita << "Testing testMergeCrossColorSpaceImpl:"; dbgKrita << " " << ppVar(cs2); dbgKrita << " " << ppVar(cs3); layer1 = p.layer; layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3); QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(250, 250, 200, 200); layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace())); p.image->addNode(layer2); p.image->addNode(layer3); p.image->initialRefreshGraph(); { KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } { layer2->disableAlphaChannel(true); KisLayerSP newLayer = mergeHelper(p, layer3); QCOMPARE(newLayer->colorSpace(), p.image->colorSpace()); p.undoStore->undo(); p.image->waitForDone(); } } void KisImageTest::testMergeCrossColorSpace() { testMergeCrossColorSpaceImpl(true, false); testMergeCrossColorSpaceImpl(true, true); testMergeCrossColorSpaceImpl(false, false); testMergeCrossColorSpaceImpl(false, true); } void KisImageTest::testMergeSelectionMasks() { - QRect refRect; TestUtil::MaskParent p; QRect rect1(100, 100, 100, 100); QRect rect2(150, 150, 150, 150); QRect rect3(50, 50, 100, 100); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace())); p.image->initialRefreshGraph(); KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds()); sel->pixelSelection()->select(rect2, MAX_SELECTED); KisSelectionMaskSP mask1 = new KisSelectionMask(p.image); mask1->initSelection(sel, layer1); p.image->addNode(mask1, layer1); QVERIFY(!layer1->selection()); mask1->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); sel->pixelSelection()->select(rect3, MAX_SELECTED); KisSelectionMaskSP mask2 = new KisSelectionMask(p.image); mask2->initSelection(sel, layer1); p.image->addNode(mask2, layer1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150)); mask2->setActive(true); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); QList selectedNodes; selectedNodes << mask2 << mask1; { KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0); QCOMPARE(newLayer->parent(), KisNodeSP(layer1)); QCOMPARE((int)layer1->childCount(), 1); QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250)); } } void KisImageTest::testFlattenImage() { FlattenTestImage p; KisImageSP image = p.image; TestUtil::ReferenceImageChecker img("flatten", "imagetest"); { KisLayerUtils::flattenImage(p.image, 0); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer5); // flatten with active layer just under the root (not inside any group) p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer2); // flatten with active layer just under the root (not inside any group), but with a mask p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } { KisLayerUtils::flattenImage(p.image, p.layer3); // flatten with active layer inside of a group p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); p.undoStore->undo(); p.image->waitForDone(); QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial")); } } struct FlattenPassThroughTestImage { FlattenPassThroughTestImage() : refRect(0,0,512,512) , p(refRect) { image = p.image; undoStore = p.undoStore; group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8); layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8); group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8); layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8); layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8); QRect rect2(100, 100, 100, 100); QRect rect3(150, 150, 100, 100); QRect rect5(200, 200, 100, 100); QRect rect6(250, 250, 100, 100); group1->setPassThroughMode(true); layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace())); layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace())); group4->setPassThroughMode(true); layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace())); layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace())); p.image->addNode(group1); p.image->addNode(layer2, group1); p.image->addNode(layer3, group1); p.image->addNode(group4); p.image->addNode(layer5, group4); p.image->addNode(layer6, group4); p.image->initialRefreshGraph(); TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); } QRect refRect; TestUtil::MaskParent p; KisImageSP image; KisSurrogateUndoStore *undoStore; KisGroupLayerSP group1; KisPaintLayerSP layer2; KisPaintLayerSP layer3; KisGroupLayerSP group4; KisPaintLayerSP layer5; KisPaintLayerSP layer6; }; void KisImageTest::testFlattenPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergeTwoPassThroughLayers() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER); QVERIFY(newLayer->inherits("KisGroupLayer")); } } void KisImageTest::testMergePaintOverPassThroughLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, newLayer); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } void KisImageTest::testMergePassThroughOverPaintLayer() { FlattenPassThroughTestImage p; TestUtil::ReferenceImageChecker chk("passthrough", "imagetest"); { QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER); QCOMPARE(p.group1->passThroughMode(), true); KisLayerSP newLayer = flattenLayerHelper(p, p.group1); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); newLayer = mergeHelper(p, p.group4); QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial")); QVERIFY(newLayer->inherits("KisPaintLayer")); } } #include "kis_paint_device_debug_utils.h" #include "kis_algebra_2d.h" void KisImageTest::testPaintOverlayMask() { QRect refRect(0, 0, 512, 512); TestUtil::MaskParent p(refRect); QRect fillRect(50, 50, 412, 412); QRect selectionRect(200, 200, 100, 50); KisPaintLayerSP layer1 = p.layer; layer1->paintDevice()->fill(fillRect, KoColor(Qt::yellow, layer1->colorSpace())); KisSelectionMaskSP mask = new KisSelectionMask(p.image); KisSelectionSP selection = new KisSelection(new KisSelectionDefaultBounds(layer1->paintDevice(), p.image)); selection->pixelSelection()->select(selectionRect, 128); selection->pixelSelection()->select(KisAlgebra2D::blowRect(selectionRect,-0.3), 255); mask->setSelection(selection); //mask->setVisible(false); //mask->setActive(false); p.image->addNode(mask, layer1); // a simple layer to disable oblidge child mechanism KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "layer2", OPACITY_OPAQUE_U8); p.image->addNode(layer2); p.image->initialRefreshGraph(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "00_initial", "dd"); p.image->setOverlaySelectionMask(mask); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "01_activated", "dd"); p.image->setOverlaySelectionMask(0); p.image->waitForDone(); KIS_DUMP_DEVICE_2(p.image->projection(), refRect, "02_deactivated", "dd"); } QTEST_MAIN(KisImageTest) diff --git a/libs/image/tests/kis_paint_device_test.cpp b/libs/image/tests/kis_paint_device_test.cpp index acf0149928..2b5f798bfb 100644 --- a/libs/image/tests/kis_paint_device_test.cpp +++ b/libs/image/tests/kis_paint_device_test.cpp @@ -1,2371 +1,2370 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paint_device_test.h" #include #include #include #include #include #include #include "kis_paint_device_writer.h" #include "kis_painter.h" #include "kis_types.h" #include "kis_paint_device.h" #include "kis_layer.h" #include "kis_paint_layer.h" #include "kis_selection.h" #include "kis_datamanager.h" #include "kis_global.h" #include "testutil.h" #include "kis_transaction.h" #include "kis_image.h" #include "config-limit-long-tests.h" #include "kistest.h" class KisFakePaintDeviceWriter : public KisPaintDeviceWriter { public: KisFakePaintDeviceWriter(KoStore *store) : m_store(store) { } bool write(const QByteArray &data) override { return (m_store->write(data) == data.size()); } bool write(const char* data, qint64 length) override { return (m_store->write(data, length) == length); } KoStore *m_store; }; void KisPaintDeviceTest::testCreation() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->objectName().isEmpty()); dev = new KisPaintDevice(cs); QVERIFY(*dev->colorSpace() == *cs); QVERIFY(dev->x() == 0); QVERIFY(dev->y() == 0); QVERIFY(dev->pixelSize() == cs->pixelSize()); QVERIFY(dev->channelCount() == cs->channelCount()); QVERIFY(dev->dataManager() != 0); KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test"); KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125); dev = new KisPaintDevice(layer.data(), cs); QVERIFY(*dev->colorSpace() == *cs); QVERIFY(dev->x() == 0); QVERIFY(dev->y() == 0); QVERIFY(dev->pixelSize() == cs->pixelSize()); QVERIFY(dev->channelCount() == cs->channelCount()); QVERIFY(dev->dataManager() != 0); // Let the layer go out of scope and see what happens { KisPaintLayerSP l2 = new KisPaintLayer(image, "blabla", 250); dev = new KisPaintDevice(l2.data(), cs); } } void KisPaintDeviceTest::testStore() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KoStore * readStore = KoStore::createStore(QString(FILES_DATA_DIR) + QDir::separator() + "store_test.kra", KoStore::Read); readStore->open("built image/layers/layer0"); QVERIFY(dev->read(readStore->device())); readStore->close(); delete readStore; QVERIFY(dev->exactBounds() == QRect(0, 0, 100, 100)); KoStore * writeStore = KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Write); KisFakePaintDeviceWriter fakeWriter(writeStore); writeStore->open("built image/layers/layer0"); QVERIFY(dev->write(fakeWriter)); writeStore->close(); delete writeStore; KisPaintDeviceSP dev2 = new KisPaintDevice(cs); readStore = KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Read); readStore->open("built image/layers/layer0"); QVERIFY(dev2->read(readStore->device())); readStore->close(); delete readStore; QVERIFY(dev2->exactBounds() == QRect(0, 0, 100, 100)); QPoint pt; if (!TestUtil::comparePaintDevices(pt, dev, dev2)) { QFAIL(QString("Loading a saved image is not pixel perfect, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1()); } } void KisPaintDeviceTest::testGeometry() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, pixel); dev->fill(0, 0, 512, 512, pixel); QCOMPARE(dev->exactBounds(), QRect(0, 0, 512, 512)); QCOMPARE(dev->extent(), QRect(0, 0, 512, 512)); dev->moveTo(10, 10); QCOMPARE(dev->exactBounds(), QRect(10, 10, 512, 512)); QCOMPARE(dev->extent(), QRect(10, 10, 512, 512)); dev->crop(50, 50, 50, 50); QCOMPARE(dev->exactBounds(), QRect(50, 50, 50, 50)); QCOMPARE(dev->extent(), QRect(10, 10, 128, 128)); QColor c; dev->clear(QRect(50, 50, 50, 50)); dev->pixel(80, 80, &c); QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8); dev->fill(0, 0, 512, 512, pixel); dev->pixel(80, 80, &c); QVERIFY(c == Qt::white); QVERIFY(c.alpha() == OPACITY_OPAQUE_U8); dev->clear(); dev->pixel(80, 80, &c); QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8); QVERIFY(dev->extent().isEmpty()); QVERIFY(dev->exactBounds().isEmpty()); } void KisPaintDeviceTest::testClear() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); dev->clear(); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); QRect fillRect1(50, 100, 150, 100); dev->fill(fillRect1, KoColor(Qt::red, cs)); QCOMPARE(dev->extent(), QRect(0, 64, 256, 192)); QCOMPARE(dev->exactBounds(), fillRect1); dev->clear(QRect(100, 100, 100, 100)); QCOMPARE(dev->extent(), QRect(0, 64, 256, 192)); QCOMPARE(dev->exactBounds(), QRect(50, 100, 50, 100)); dev->clear(); QVERIFY(!dev->extent().isValid()); QVERIFY(!dev->exactBounds().isValid()); } void KisPaintDeviceTest::testCrop() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, pixel); dev->fill(-14, 8, 433, 512, pixel); QVERIFY(dev->exactBounds() == QRect(-14, 8, 433, 512)); // Crop inside dev->crop(50, 50, 150, 150); QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150)); // Crop outside, pd should not grow dev->crop(0, 0, 1000, 1000); QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150)); } void KisPaintDeviceTest::testRoundtripReadWrite() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); dev->convertFromQImage(image, 0); quint8* bytes = new quint8[cs->pixelSize() * image.width() * image.height()]; memset(bytes, 0, image.width() * image.height() * dev->pixelSize()); dev->readBytes(bytes, image.rect()); KisPaintDeviceSP dev2 = new KisPaintDevice(cs); dev2->writeBytes(bytes, image.rect()); QVERIFY(dev2->exactBounds() == image.rect()); dev2->convertToQImage(0, 0, 0, image.width(), image.height()).save("readwrite.png"); QPoint pt; if (!TestUtil::comparePaintDevices(pt, dev, dev2)) { QFAIL(QString("Failed round trip using readBytes and writeBytes, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1()); } } void logFailure(const QString & reason, const KoColorSpace * srcCs, const KoColorSpace * dstCs) { QString profile1("no profile"); QString profile2("no profile"); if (srcCs->profile()) profile1 = srcCs->profile()->name(); if (dstCs->profile()) profile2 = dstCs->profile()->name(); QWARN(QString("Failed %1 %2 -> %3 %4 %5") .arg(srcCs->name()) .arg(profile1) .arg(dstCs->name()) .arg(profile2) .arg(reason) .toLatin1()); } void KisPaintDeviceTest::testColorSpaceConversion() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png"); const KoColorSpace* srcCs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace* dstCs = KoColorSpaceRegistry::instance()->lab16(); KisPaintDeviceSP dev = new KisPaintDevice(srcCs); dev->convertFromQImage(image, 0); dev->moveTo(10, 10); // Unalign with tile boundaries KUndo2Command* cmd = new KUndo2Command(); dev->convertTo(dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), cmd); QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height())); QCOMPARE(dev->pixelSize(), dstCs->pixelSize()); QVERIFY(*dev->colorSpace() == *dstCs); cmd->redo(); cmd->undo(); QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height())); QCOMPARE(dev->pixelSize(), srcCs->pixelSize()); QVERIFY(*dev->colorSpace() == *srcCs); delete cmd; } void KisPaintDeviceTest::testRoundtripConversion() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); QImage result = dev->convertToQImage(0, 0, 0, 640, 441); QPoint errpoint; if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_paint_device_test_test_roundtrip_qimage.png"); result.save("kis_paint_device_test_test_roundtrip_result.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testFastBitBlt() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dstDev = new KisPaintDevice(cs); KisPaintDeviceSP srcDev = new KisPaintDevice(cs); srcDev->convertFromQImage(image, 0); QRect cloneRect(100,100,200,200); QPoint errpoint; QVERIFY(dstDev->fastBitBltPossible(srcDev)); dstDev->fastBitBlt(srcDev, cloneRect); QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } // Test Rough version dstDev->clear(); dstDev->fastBitBltRough(srcDev, cloneRect); srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } srcDev->moveTo(10,10); QVERIFY(!dstDev->fastBitBltPossible(srcDev)); } void KisPaintDeviceTest::testMakeClone() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP srcDev = new KisPaintDevice(cs); srcDev->convertFromQImage(image, 0); srcDev->moveTo(10,10); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->lab16(); KisPaintDeviceSP dstDev = new KisPaintDevice(weirdCS); dstDev->moveTo(1000,1000); QVERIFY(!dstDev->fastBitBltPossible(srcDev)); QRect cloneRect(100,100,200,200); QPoint errpoint; dstDev->makeCloneFrom(srcDev, cloneRect); QVERIFY(*dstDev->colorSpace() == *srcDev->colorSpace()); QCOMPARE(dstDev->pixelSize(), srcDev->pixelSize()); QCOMPARE(dstDev->x(), srcDev->x()); QCOMPARE(dstDev->y(), srcDev->y()); QCOMPARE(dstDev->exactBounds(), cloneRect); QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(), cloneRect.width(), cloneRect.height()); if (!TestUtil::compareQImages(errpoint, dstImage, srcImage)) { QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testThumbnail() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); { KisPaintDeviceSP thumb = dev->createThumbnailDevice(50, 50); QRect rc = thumb->exactBounds(); QVERIFY(rc.width() <= 50); QVERIFY(rc.height() <= 50); } { QImage thumb = dev->createThumbnail(50, 50); QVERIFY(!thumb.isNull()); QVERIFY(thumb.width() <= 50); QVERIFY(thumb.height() <= 50); image.save("kis_paint_device_test_test_thumbnail.png"); } } void KisPaintDeviceTest::testThumbnailDeviceWithOffset() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->convertFromQImage(image, 0); dev->setX(10); dev->setY(10); QImage thumb = dev->createThumbnail(640,441,QRect(10,10,640,441)); image.save("oring.png"); thumb.save("thumb.png"); QPoint pt; QVERIFY(TestUtil::compareQImages(pt, thumb, image)); } void KisPaintDeviceTest::testCaching() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* whitePixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, whitePixel); quint8* blackPixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::black, blackPixel); dev->fill(0, 0, 512, 512, whitePixel); QImage thumb1 = dev->createThumbnail(50, 50); QRect exactBounds1 = dev->exactBounds(); dev->fill(0, 0, 768, 768, blackPixel); QImage thumb2 = dev->createThumbnail(50, 50); QRect exactBounds2 = dev->exactBounds(); dev->moveTo(10, 10); QImage thumb3 = dev->createThumbnail(50, 50); QRect exactBounds3 = dev->exactBounds(); dev->crop(50, 50, 50, 50); QImage thumb4 = dev->createThumbnail(50, 50); QRect exactBounds4 = dev->exactBounds(); QVERIFY(thumb1 != thumb2); QVERIFY(thumb2 == thumb3); // Cache miss, but image is the same QVERIFY(thumb3 != thumb4); QVERIFY(thumb4 != thumb1); QCOMPARE(exactBounds1, QRect(0,0,512,512)); QCOMPARE(exactBounds2, QRect(0,0,768,768)); QCOMPARE(exactBounds3, QRect(10,10,768,768)); QCOMPARE(exactBounds4, QRect(50,50,50,50)); } void KisPaintDeviceTest::testRegion() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* whitePixel = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::white, whitePixel); dev->fill(0, 0, 10, 10, whitePixel); dev->fill(70, 70, 10, 10, whitePixel); dev->fill(129, 0, 10, 10, whitePixel); dev->fill(0, 1030, 10, 10, whitePixel); QRegion referenceRegion; referenceRegion += QRect(0,0,64,64); referenceRegion += QRect(64,64,64,64); referenceRegion += QRect(128,0,64,64); referenceRegion += QRect(0,1024,64,64); QCOMPARE(dev->exactBounds(), QRect(0,0,139,1040)); QCOMPARE(dev->region(), referenceRegion); } void KisPaintDeviceTest::testPixel() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QColor c = Qt::red; quint8 opacity = 125; c.setAlpha(opacity); dev->setPixel(5, 5, c); QColor c2; dev->pixel(5, 5, &c2); QVERIFY(c == c2); QVERIFY(opacity == c2.alpha()); } void KisPaintDeviceTest::testPlanarReadWrite() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); quint8* pixel = new quint8[cs->pixelSize()]; cs->fromQColor(QColor(255, 200, 155, 100), pixel); dev->fill(0, 0, 5000, 5000, pixel); delete[] pixel; QColor c1; dev->pixel(5, 5, &c1); QVector planes = dev->readPlanarBytes(500, 500, 100, 100); QVector swappedPlanes; QCOMPARE((int)planes.size(), (int)dev->channelCount()); for (int i = 0; i < 100*100; i++) { // BGRA encoded QVERIFY(planes.at(2)[i] == 255); QVERIFY(planes.at(1)[i] == 200); QVERIFY(planes.at(0)[i] == 155); QVERIFY(planes.at(3)[i] == 100); } for (uint i = 1; i < dev->channelCount() + 1; ++i) { swappedPlanes.append(planes[dev->channelCount() - i]); } dev->writePlanarBytes(swappedPlanes, 0, 0, 100, 100); dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar.png"); dev->pixel(5, 5, &c1); QVERIFY(c1.red() == 200); QVERIFY(c1.green() == 255); QVERIFY(c1.blue() == 100); QVERIFY(c1.alpha() == 155); dev->pixel(75, 50, &c1); QVERIFY(c1.red() == 200); QVERIFY(c1.green() == 255); QVERIFY(c1.blue() == 100); QVERIFY(c1.alpha() == 155); // check if one of the planes is Null. Q_ASSERT(planes.size() == 4); delete [] planes[2]; planes[2] = 0; dev->writePlanarBytes(planes, 0, 0, 100, 100); dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar_noR.png"); dev->pixel(75, 50, &c1); QCOMPARE(c1.red(), 200); QCOMPARE(c1.green(), 200); QCOMPARE(c1.blue(), 155); QCOMPARE(c1.alpha(), 100); QVector::iterator i; for (i = planes.begin(); i != planes.end(); ++i) { delete [] *i; } swappedPlanes.clear(); } void KisPaintDeviceTest::testBltPerformance() { QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP fdev = new KisPaintDevice(cs); fdev->convertFromQImage(image, 0); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data()); QTime t; t.start(); int x; #ifdef LIMIT_LONG_TESTS int steps = 10; #else int steps = 1000; #endif for (x = 0; x < steps; ++x) { KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), fdev, image.rect()); } dbgKrita << x << "blits" << " done in " << t.elapsed() << "ms"; } void KisPaintDeviceTest::testDeviceDuplication() { QRect fillRect(0,0,64,64); quint8 fillPixel[4]={255,255,255,255}; QRect clearRect(10,10,20,20); QImage referenceImage; QImage resultImage; const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP device = new KisPaintDevice(cs); // dbgKrita<<"FILLING"; device->fill(fillRect.left(), fillRect.top(), fillRect.width(), fillRect.height(),fillPixel); referenceImage = device->convertToQImage(0); KisTransaction transaction1(device); // dbgKrita<<"CLEARING"; device->clear(clearRect); transaction1.revert(); resultImage = device->convertToQImage(0); QVERIFY(resultImage == referenceImage); KisPaintDeviceSP clone = new KisPaintDevice(*device); KisTransaction transaction(clone); // dbgKrita<<"CLEARING"; clone->clear(clearRect); transaction.revert(); resultImage = clone->convertToQImage(0); QVERIFY(resultImage == referenceImage); } void KisPaintDeviceTest::testTranslate() { QRect fillRect(0,0,64,64); quint8 fillPixel[4]={255,255,255,255}; const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP device = new KisPaintDevice(cs); device->fill(fillRect.left(), fillRect.top(), fillRect.width(), fillRect.height(),fillPixel); device->setX(-10); device->setY(10); QCOMPARE(device->exactBounds(), QRect(-10,10,64,64)); QCOMPARE(device->extent(), QRect(-10,10,64,64)); } void KisPaintDeviceTest::testOpacity() { // blt a semi-transparent image on a white paint device QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png"); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP fdev = new KisPaintDevice(cs); fdev->convertFromQImage(image, 0); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data()); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), fdev, image.rect()); QImage result = dev->convertToQImage(0, 0, 0, 640, 441); QImage checkResult(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent_result.png"); QPoint errpoint; if (!TestUtil::compareQImages(errpoint, checkResult, result, 1)) { checkResult.save("kis_paint_device_test_test_blt_fixed_opactiy_expected.png"); result.save("kis_paint_device_test_test_blt_fixed_opacity_result.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisPaintDeviceTest::testExactBoundsWeirdNullAlphaCase() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); dev->fill(QRect(10,10,10,10), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(10,10,10,10)); const quint8 weirdPixelData[4] = {0,10,0,0}; KoColor weirdColor(weirdPixelData, cs); dev->setPixel(6,6,weirdColor); // such weird pixels should not change our opinion about // device's size QCOMPARE(dev->exactBounds(), QRect(10,10,10,10)); } void KisPaintDeviceTest::benchmarkExactBoundsNullDefaultPixel() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QRect fillRect(60,60, 1930, 1930); dev->fill(fillRect, KoColor(Qt::white, cs)); QRect measuredRect; QBENCHMARK { // invalidate the cache dev->setDirty(); measuredRect = dev->exactBounds(); } QCOMPARE(measuredRect, fillRect); } void KisPaintDeviceTest::testAmortizedExactBounds() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QRect fillRect(60,60, 833, 833); QRect extent(0,0,896,896); dev->fill(fillRect, KoColor(Qt::white, cs)); QEXPECT_FAIL("", "Expecting the extent, we somehow get the fillrect", Continue); QCOMPARE(dev->exactBounds(), extent); QCOMPARE(dev->extent(), extent); QCOMPARE(dev->exactBoundsAmortized(), fillRect); dev->setDirty(); QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue); QCOMPARE(dev->exactBoundsAmortized(), fillRect); dev->setDirty(); QCOMPARE(dev->exactBoundsAmortized(), extent); QTest::qSleep(1100); QEXPECT_FAIL("", "Expecting the fillRect, we somehow get the extent", Continue); QCOMPARE(dev->exactBoundsAmortized(), fillRect); } void KisPaintDeviceTest::testNonDefaultPixelArea() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); QVERIFY(dev->exactBounds().isEmpty()); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); KoColor defPixel(Qt::red, cs); dev->setDefaultPixel(defPixel); QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); QRect fillRect(10,11,18,14); dev->fill(fillRect, KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QCOMPARE(dev->nonDefaultPixelArea(), fillRect); // non-default pixel variant should also handle weird pixels const quint8 weirdPixelData[4] = {0,10,0,0}; KoColor weirdColor(weirdPixelData, cs); dev->setPixel(100,100,weirdColor); // such weird pixels should not change our opinion about // device's size QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect); QCOMPARE(dev->nonDefaultPixelArea(), fillRect | QRect(100,100,1,1)); } void KisPaintDeviceTest::testExactBoundsNonTransparent() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test"); KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125); KisPaintDeviceSP dev = layer->paintDevice(); QVERIFY(dev); QRect imageRect(0,0,1000,1000); KoColor defPixel(Qt::red, cs); dev->setDefaultPixel(defPixel); QCOMPARE(dev->exactBounds(), imageRect); QVERIFY(dev->nonDefaultPixelArea().isEmpty()); KoColor fillPixel(Qt::white, cs); dev->fill(imageRect, KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), imageRect); QCOMPARE(dev->nonDefaultPixelArea(), imageRect); dev->fill(QRect(1000,0, 1, 1000), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1000)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1000)); dev->fill(QRect(0,1000, 1000, 1), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1001)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1001)); dev->fill(QRect(0,-1, 1000, 1), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(0,-1,1001,1002)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,-1,1001,1002)); dev->fill(QRect(-1,0, 1, 1000), KoColor(Qt::white, cs)); QCOMPARE(dev->exactBounds(), QRect(-1,-1,1002,1002)); QCOMPARE(dev->nonDefaultPixelArea(), QRect(-1,-1,1002,1002)); } KisPaintDeviceSP createWrapAroundPaintDevice(const KoColorSpace *cs) { struct TestingDefaultBounds : public KisDefaultBoundsBase { QRect bounds() const override { return QRect(0,0,20,20); } bool wrapAroundMode() const override { return true; } int currentLevelOfDetail() const override { return 0; } int currentTime() const override { return 0; } bool externalFrameActive() const override { return false; } }; KisPaintDeviceSP dev = new KisPaintDevice(cs); KisDefaultBoundsBaseSP bounds = new TestingDefaultBounds(); dev->setDefaultBounds(bounds); return dev; } void checkReadWriteRoundTrip(KisPaintDeviceSP dev, const QRect &rc) { KisPaintDeviceSP deviceCopy = new KisPaintDevice(*dev.data()); - QRect readRect(10, 10, 20, 20); int bufSize = rc.width() * rc.height() * dev->pixelSize(); QScopedArrayPointer buf1(new quint8[bufSize]); deviceCopy->readBytes(buf1.data(), rc); deviceCopy->clear(); QVERIFY(deviceCopy->extent().isEmpty()); QScopedArrayPointer buf2(new quint8[bufSize]); deviceCopy->writeBytes(buf1.data(), rc); deviceCopy->readBytes(buf2.data(), rc); QVERIFY(!memcmp(buf1.data(), buf2.data(), bufSize)); } void KisPaintDeviceTest::testReadBytesWrapAround() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); const int pixelSize = dev->pixelSize(); { QRect readRect(10, 10, 20, 20); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final1.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check weird case when the read rect is larger than wrap rect QRect readRect(10, 10, 30, 30); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final2.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // even more large QRect readRect(10, 10, 40, 40); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final3.png"); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap rect contains the read rect entirely QRect readRect(1, 1, 10, 10); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final4.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap happens only on vertical side of the rect QRect readRect(1, 1, 29, 10); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final5.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (21 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (22 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } { // check if the wrap happens only on horizontal side of the rect QRect readRect(1, 1, 10, 29); QScopedArrayPointer buf(new quint8[readRect.width() * readRect.height() * pixelSize]); dev->readBytes(buf.data(), readRect); //dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final6.png"); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize)); QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 21) * pixelSize, c1.data(), pixelSize)); QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 22) * pixelSize, c1.data(), pixelSize)); checkReadWriteRoundTrip(dev, readRect); } } #include "kis_random_accessor_ng.h" void KisPaintDeviceTest::testWrappedRandomAccessor() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); const int pixelSize = dev->pixelSize(); int x; int y; x = 3; y = 3; KisRandomAccessorSP dstIt = dev->createRandomAccessorNG(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 23; y = 23; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 3; y = 23; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 23; y = 3; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = -17; y = 3; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = 3; y = -17; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); x = -17; y = -17; dstIt->moveTo(x, y); QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize)); QCOMPARE(dstIt->numContiguousColumns(x), 17); QCOMPARE(dstIt->numContiguousRows(y), 17); } #include "kis_iterator_ng.h" static bool nextRowGeneral(KisHLineIteratorSP it, int y, const QRect &rc) { it->nextRow(); return y < rc.height(); } static bool nextRowGeneral(KisVLineIteratorSP it, int y, const QRect &rc) { it->nextColumn(); return y < rc.width(); } template bool checkXY(const QPoint &pt, const QPoint &realPt) { Q_UNUSED(pt); Q_UNUSED(realPt); return false; } template <> bool checkXY(const QPoint &pt, const QPoint &realPt) { return pt == realPt; } template <> bool checkXY(const QPoint &pt, const QPoint &realPt) { return pt.x() == realPt.y() && pt.y() == realPt.x(); } #include template bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { Q_UNUSED(value); Q_UNUSED(pt); Q_UNUSED(wrappedRect); return false; } template <> bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { int x = KisWrappedRect::xToWrappedX(pt.x(), wrappedRect.wrapRect()); int borderX = wrappedRect.originalRect().x() + wrappedRect.wrapRect().width(); int conseq = x >= borderX ? wrappedRect.wrapRect().right() - x + 1 : borderX - x; conseq = qMin(conseq, wrappedRect.originalRect().right() - pt.x() + 1); return value == conseq; } template <> bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) { Q_UNUSED(pt); Q_UNUSED(wrappedRect); return value == 1; } template IteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { Q_UNUSED(dev); Q_UNUSED(rc); return 0; } template <> KisHLineIteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { return dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); } template <> KisVLineIteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) { return dev->createVLineIteratorNG(rc.x(), rc.y(), rc.height()); } template void testWrappedLineIterator(QString testName, const QRect &rect) { testName = QString("%1_%2_%3_%4_%5") .arg(testName) .arg(rect.x()) .arg(rect.y()) .arg(rect.width()) .arg(rect.height()); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); // test rect fits the wrap rect in both dimensions IteratorSP it = createIterator(dev, rect); int y = 0; do { int x = 0; do { quint8 *data = it->rawData(); data[0] = 10 * x; data[1] = 10 * y; data[2] = 0; data[3] = 255; x++; } while (it->nextPixel()); } while (nextRowGeneral(it, ++y, rect)); QRect rc = dev->defaultBounds()->bounds() | dev->exactBounds(); QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators", testName)); } template void testWrappedLineIterator(const QString &testName) { testWrappedLineIterator(testName, QRect(10,10,20,20)); testWrappedLineIterator(testName, QRect(10,10,10,20)); testWrappedLineIterator(testName, QRect(10,10,20,10)); testWrappedLineIterator(testName, QRect(10,10,10,10)); testWrappedLineIterator(testName, QRect(0,0,20,20)); } void KisPaintDeviceTest::testWrappedHLineIterator() { testWrappedLineIterator("hline_iterator"); } void KisPaintDeviceTest::testWrappedVLineIterator() { testWrappedLineIterator("vline_iterator"); } template void testWrappedLineIteratorReadMoreThanBounds(QString testName) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KisPaintDeviceSP dst = new KisPaintDevice(cs); // fill device with a gradient QRect bounds = dev->defaultBounds()->bounds(); for (int y = bounds.y(); y < bounds.y() + bounds.height(); y++) { for (int x = bounds.x(); x < bounds.x() + bounds.width(); x++) { QColor c((10 * x) % 255, (10 * y) % 255, 0, 255); dev->setPixel(x, y, c); } } // test rect doesn't fit the wrap rect in both dimensions const QRect &rect(bounds.adjusted(-6,-6,8,8)); KisRandomAccessorSP dstIt = dst->createRandomAccessorNG(rect.x(), rect.y()); IteratorSP it = createIterator(dev, rect); for (int y = rect.y(); y < rect.y() + rect.height(); y++) { for (int x = rect.x(); x < rect.x() + rect.width(); x++) { quint8 *data = it->rawData(); QVERIFY(checkConseqPixels(it->nConseqPixels(), QPoint(x, y), KisWrappedRect(rect, bounds))); dstIt->moveTo(x, y); memcpy(dstIt->rawData(), data, cs->pixelSize()); QVERIFY(checkXY(QPoint(it->x(), it->y()), QPoint(x,y))); bool stepDone = it->nextPixel(); QCOMPARE(stepDone, x < rect.x() + rect.width() - 1); } if (!nextRowGeneral(it, y, rect)) break; } testName = QString("%1_%2_%3_%4_%5") .arg(testName) .arg(rect.x()) .arg(rect.y()) .arg(rect.width()) .arg(rect.height()); QRect rc = rect; QImage result = dst->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QImage ref = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators_huge", testName, 1)); } void KisPaintDeviceTest::testWrappedHLineIteratorReadMoreThanBounds() { testWrappedLineIteratorReadMoreThanBounds("hline_iterator"); } void KisPaintDeviceTest::testWrappedVLineIteratorReadMoreThanBounds() { testWrappedLineIteratorReadMoreThanBounds("vline_iterator"); } void KisPaintDeviceTest::testMoveWrapAround() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs); KoColor c1(Qt::red, cs); KoColor c2(Qt::green, cs); dev->setPixel(3, 3, c1); dev->setPixel(18, 18, c2); // QRect rc = dev->defaultBounds()->bounds(); //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move0.png"); QCOMPARE(dev->exactBounds(), QRect(3,3,16,16)); dev->moveTo(QPoint(10,10)); QCOMPARE(dev->exactBounds(), QRect(8,8,6,6)); //dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move1.png"); } #include "kis_lock_free_cache.h" #define NUM_TYPES 3 // high-concurrency #define NUM_CYCLES 500000 #define NUM_THREADS 4 struct TestingCache : KisLockFreeCache { int calculateNewValue() const override { return m_realValue; } QAtomicInt m_realValue; }; class CacheStressJob : public QRunnable { public: CacheStressJob(TestingCache &cache) : m_cache(cache), m_oldValue(0) { } void run() override { for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = i % NUM_TYPES; switch(type) { case 0: m_cache.m_realValue.ref(); m_oldValue = m_cache.m_realValue; m_cache.invalidate(); break; case 1: { int newValue = m_cache.getValue(); Q_ASSERT(newValue >= m_oldValue); Q_UNUSED(newValue); } break; case 3: QTest::qSleep(3); break; } } } private: TestingCache &m_cache; int m_oldValue; }; void KisPaintDeviceTest::testCacheState() { TestingCache cache; QList jobsList; CacheStressJob *job; for(qint32 i = 0; i < NUM_THREADS; i++) { //job = new CacheStressJob(value, cacheValue, cacheState); job = new CacheStressJob(cache); job->setAutoDelete(true); jobsList.append(job); } QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); Q_FOREACH (job, jobsList) { pool.start(job); } pool.waitForDone(); } struct TestingLodDefaultBounds : public KisDefaultBoundsBase { TestingLodDefaultBounds(const QRect &bounds = QRect(0,0,100,100)) : m_lod(0), m_bounds(bounds) {} QRect bounds() const override { return m_bounds; } bool wrapAroundMode() const override { return false; } int currentLevelOfDetail() const override { return m_lod; } int currentTime() const override { return 0; } bool externalFrameActive() const override { return false; } void testingSetLevelOfDetail(int lod) { m_lod = lod; } private: int m_lod; QRect m_bounds; }; void fillGradientDevice(KisPaintDeviceSP dev, const QRect &rect, bool flat = false) { if (flat) { dev->fill(rect, KoColor(Qt::red, dev->colorSpace())); } else { // fill device with a gradient KisSequentialIterator it(dev, rect); while (it.nextPixel()) { QColor c((10 * it.x()) & 0xFF, (10 * it.y()) & 0xFF, 0, 255); KoColor color(c, dev->colorSpace()); memcpy(it.rawData(), color.data(), dev->pixelSize()); } } } #include "kis_lod_transform.h" void KisPaintDeviceTest::testLodTransform() { const int lod = 2; // round to 4 KisLodTransform t(lod); QRect rc1(-16, -16, 8, 8); QRect rc2(-16, -16, 7, 7); QRect rc3(-15, -15, 7, 7); QCOMPARE(t.alignedRect(rc1, lod), rc1); QCOMPARE(t.alignedRect(rc2, lod), rc1); QCOMPARE(t.alignedRect(rc3, lod), rc1); } #include "krita_utils.h" void syncLodCache(KisPaintDeviceSP dev, int levelOfDetail) { KisPaintDevice::LodDataStruct* s = dev->createLodDataStruct(levelOfDetail); QRegion region = dev->regionForLodSyncing(); Q_FOREACH(QRect rect2, KritaUtils::splitRegionIntoPatches(region, KritaUtils::optimalPatchSize())) { dev->updateLodDataStruct(s, rect2); } dev->uploadLodDataStruct(s); } void KisPaintDeviceTest::testLodDevice() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(); dev->setDefaultBounds(bounds); // fill device with a gradient // QRect rect = dev->defaultBounds()->bounds(); // fillGradientDevice(dev, rect); fillGradientDevice(dev, QRect(50,50,30,30)); QCOMPARE(dev->exactBounds(), QRect(50,50,30,30)); QImage result; qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "initial")); bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); QCOMPARE(dev->exactBounds(), QRect(25,25,15,15)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1")); bounds->testingSetLevelOfDetail(2); QCOMPARE(dev->exactBounds(), QRect(25,25,15,15)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1")); syncLodCache(dev, 2); QCOMPARE(dev->exactBounds(), QRect(12,12,8,8)); qDebug() << ppVar(dev->exactBounds()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod2")); bounds->testingSetLevelOfDetail(0); dev->setX(20); dev->setY(10); bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); QCOMPARE(dev->exactBounds(), QRect(35,30,15,15)); qDebug() << ppVar(dev->exactBounds()) << ppVar(dev->x()) << ppVar(dev->y()); result = dev->convertToQImage(0,0,0,100,100); /*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test", "lod", "lod1-offset-6-14")); } void KisPaintDeviceTest::benchmarkLod1Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(1); syncLodCache(dev, 1); } } void KisPaintDeviceTest::benchmarkLod2Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(2); syncLodCache(dev, 2); } } void KisPaintDeviceTest::benchmarkLod3Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(3); syncLodCache(dev, 3); } } void KisPaintDeviceTest::benchmarkLod4Generation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000)); dev->setDefaultBounds(bounds); // fill device with a gradient QRect rect = dev->defaultBounds()->bounds(); fillGradientDevice(dev, rect, true); QBENCHMARK { bounds->testingSetLevelOfDetail(4); syncLodCache(dev, 4); } } #include "kis_keyframe_channel.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "testing_timed_default_bounds.h" void KisPaintDeviceTest::testFramesLeaking() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data is kept separate o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add keyframe at position 10 channel->addKeyframe(10); // two frames, m_data has a default empty value o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); // add keyframe at position 20 channel->addKeyframe(20); // three frames, m_data is default, current frame is 0 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 3); QVERIFY(o.m_currentData == o.m_frames[0]); // switch to frame 10 bounds->testingSetTime(10); // three frames, m_data is default, current frame is 10 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[1]); QCOMPARE(o.m_frames.size(), 3); // switch to frame 20 bounds->testingSetTime(20); // three frames, m_data is default, current frame is 20 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[2]); QCOMPARE(o.m_frames.size(), 3); // switch to frame 15 bounds->testingSetTime(15); // three frames, m_data is default, current frame is 10 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QVERIFY(o.m_currentData == o.m_frames[1]); QCOMPARE(o.m_frames.size(), 3); KisKeyframeSP key; // deletion of frame 0 is forbidden key = channel->keyframeAt(0); QVERIFY(key); QVERIFY(channel->deleteKeyframe(key)); // delete keyframe at position 11 key = channel->activeKeyframeAt(11); QVERIFY(key); QCOMPARE(key->time(), 10); QVERIFY(channel->deleteKeyframe(key)); // two frames, m_data is default, current frame is 0 o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 2); // deletion of frame 0 is forbidden key = channel->activeKeyframeAt(11); QVERIFY(key); QCOMPARE(key->time(), 0); QVERIFY(channel->deleteKeyframe(key)); // nothing changed o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 2); // delete keyframe at position 20 key = channel->activeKeyframeAt(20); QVERIFY(key); QCOMPARE(key->time(), 20); QVERIFY(channel->deleteKeyframe(key)); // one keyframe is left at position 0, m_data is default o = i->testingGetDataObjects(); QVERIFY(o.m_data); QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); //QVERIFY(o.m_currentData == o.m_frames[0]); QCOMPARE(o.m_frames.size(), 1); // ensure all the objects in the list of all objects are unique QList allObjects = i->testingGetDataObjectsList(); QSet uniqueObjects; Q_FOREACH (KisPaintDeviceData *obj, allObjects) { if (!obj) continue; QVERIFY(!uniqueObjects.contains(obj)); uniqueObjects.insert(obj); } } void KisPaintDeviceTest::testFramesUndoRedo() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; int frameId = -1; const int time = 1; channel->addKeyframe(time, &cmdAdd); frameId = channel->frameIdAt(time); //int frameId = i->createFrame(false, 0, QPoint(), &cmdAdd); QCOMPARE(frameId, 1); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.undo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.redo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdRemove; KisKeyframeSP keyframe = channel->keyframeAt(time); QVERIFY(keyframe); channel->deleteKeyframe(keyframe, &cmdRemove); //i->deleteFrame(1, &cmdRemove); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.redo(); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); } void KisPaintDeviceTest::testFramesUndoRedoWithChannel() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.undo(); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdAdd.redo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdRemove; channel->deleteKeyframe(frame, &cmdRemove); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.redo(); QVERIFY(!channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); cmdRemove.undo(); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); KUndo2Command cmdMove; channel->moveKeyframe(frame, 12, &cmdMove); QVERIFY(!channel->keyframeAt(10)); QVERIFY(channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdMove.undo(); QVERIFY(channel->keyframeAt(10)); QVERIFY(!channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); cmdMove.redo(); QVERIFY(!channel->keyframeAt(10)); QVERIFY(channel->keyframeAt(12)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // default m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); QVERIFY(o.m_currentData == o.m_frames[0]); } void fillRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds) { KUndo2Command parentCommand; KisRasterKeyframeChannel *channel = dev->keyframeChannel(); KisKeyframeSP frame = channel->addKeyframe(time, &parentCommand); const int oldTime = bounds->currentTime(); bounds->testingSetTime(time); KoColor color(Qt::red, dev->colorSpace()); dev->fill(rc, color); bounds->testingSetTime(oldTime); } bool checkRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds) { const int oldTime = bounds->currentTime(); bounds->testingSetTime(time); bool result = dev->exactBounds() == rc; if (!result) { qDebug() << "Failed to check frame:" << ppVar(time) << ppVar(rc) << ppVar(dev->exactBounds()); } bounds->testingSetTime(oldTime); return result; } void testCrossDeviceFrameCopyImpl(bool useChannel) { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev1 = new KisPaintDevice(cs); KisPaintDeviceSP dev2 = new KisPaintDevice(cs); const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16(); KisPaintDeviceSP dev3 = new KisPaintDevice(cs3); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev1->setDefaultBounds(bounds); dev2->setDefaultBounds(bounds); dev3->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel1 = dev1->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i1 = dev1->framesInterface(); QVERIFY(channel1); QVERIFY(i1); KisRasterKeyframeChannel *channel2 = dev2->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i2 = dev2->framesInterface(); QVERIFY(channel2); QVERIFY(i2); KisRasterKeyframeChannel *channel3 = dev3->createKeyframeChannel(KisKeyframeChannel::Content); KisPaintDeviceFramesInterface *i3 = dev3->framesInterface(); QVERIFY(channel3); QVERIFY(i3); fillRect(dev1, 10, QRect(100,100,100,100), bounds); fillRect(dev2, 20, QRect(200,200,100,100), bounds); fillRect(dev3, 30, QRect(300,300,100,100), bounds); QCOMPARE(dev1->exactBounds(), QRect()); const int dstFrameId1 = channel1->frameIdAt(10); const int srcFrameId2 = channel2->frameIdAt(20); const int srcFrameId3 = channel3->frameIdAt(30); KUndo2Command cmd1; if (!useChannel) { dev1->framesInterface()->uploadFrame(srcFrameId2, dstFrameId1, dev2); } else { KisKeyframeSP k = channel1->copyExternalKeyframe(channel2, 20, 10, &cmd1); } QCOMPARE(dev1->exactBounds(), QRect()); QVERIFY(checkRect(dev1, 10, QRect(200,200,100,100), bounds)); if (useChannel) { cmd1.undo(); QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds)); } KUndo2Command cmd2; if (!useChannel) { dev1->framesInterface()->uploadFrame(srcFrameId3, dstFrameId1, dev3); } else { KisKeyframeSP k = channel1->copyExternalKeyframe(channel3, 30, 10, &cmd2); } QCOMPARE(dev1->exactBounds(), QRect()); QVERIFY(checkRect(dev1, 10, QRect(300,300,100,100), bounds)); if (useChannel) { cmd2.undo(); QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds)); } } void KisPaintDeviceTest::testCrossDeviceFrameCopyDirect() { testCrossDeviceFrameCopyImpl(false); } void KisPaintDeviceTest::testCrossDeviceFrameCopyChannel() { testCrossDeviceFrameCopyImpl(true); } #include "kis_surrogate_undo_adapter.h" void KisPaintDeviceTest::testLazyFrameCreation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); bounds->testingSetTime(10); QCOMPARE(i->frames().size(), 1); KisSurrogateUndoAdapter undoAdapter; { KisTransaction transaction1(dev); transaction1.commit(&undoAdapter); } QCOMPARE(i->frames().size(), 2); undoAdapter.undoAll(); QCOMPARE(i->frames().size(), 1); undoAdapter.redoAll(); QCOMPARE(i->frames().size(), 2); } void KisPaintDeviceTest::testCopyPaintDeviceWithFrames() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content); QVERIFY(channel); KisPaintDeviceFramesInterface *i = dev->framesInterface(); QVERIFY(i); QCOMPARE(i->frames().size(), 1); KisPaintDeviceFramesInterface::TestingDataObjects o; // Itinial state: one frame, m_data shared o = i->testingGetDataObjects(); QVERIFY(o.m_data); // m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 1); QVERIFY(o.m_currentData == o.m_frames[0]); // add a keyframe KUndo2Command cmdAdd; KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd); QVERIFY(channel->keyframeAt(10)); o = i->testingGetDataObjects(); QVERIFY(o.m_data); // m_data should always be present QVERIFY(!o.m_lodData); QVERIFY(!o.m_externalFrameData); QCOMPARE(o.m_frames.size(), 2); //QVERIFY(o.m_currentData == o.m_frames[0]); KisPaintDeviceSP newDev = new KisPaintDevice(*dev, KritaUtils::CopyAllFrames); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(10)); } #include #include #include #include #include #include #include #include "KoCompositeOpRegistry.h" using namespace boost::accumulators; accumulator_set > accum; void KisPaintDeviceTest::testCompositionAssociativity() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); qsrand(500); boost::mt11213b _rnd0(qrand()); boost::mt11213b _rnd1(qrand()); boost::mt11213b _rnd2(qrand()); boost::mt11213b _rnd3(qrand()); boost::uniform_smallint rnd0(0, 255); boost::uniform_smallint rnd1(0, 255); boost::uniform_smallint rnd2(0, 255); boost::uniform_smallint rnd3(0, 255); QList allCompositeOps = cs->compositeOps(); Q_FOREACH (const KoCompositeOp *op, allCompositeOps) { accumulator_set > accum; const int numIterations = 10000; for (int j = 0; j < numIterations; j++) { KoColor c1(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor c2(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor c3(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); //KoColor c4(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); //KoColor c5(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs); KoColor r1(QColor(Qt::transparent), cs); KoColor r2(QColor(Qt::transparent), cs); KoColor r3(QColor(Qt::transparent), cs); op->composite(r1.data(), 0, c1.data(), 0, 0,0, 1,1, 255); op->composite(r1.data(), 0, c2.data(), 0, 0,0, 1,1, 255); op->composite(r1.data(), 0, c3.data(), 0, 0,0, 1,1, 255); //op->composite(r1.data(), 0, c4.data(), 0, 0,0, 1,1, 255); //op->composite(r1.data(), 0, c5.data(), 0, 0,0, 1,1, 255); op->composite(r3.data(), 0, c2.data(), 0, 0,0, 1,1, 255); op->composite(r3.data(), 0, c3.data(), 0, 0,0, 1,1, 255); //op->composite(r3.data(), 0, c4.data(), 0, 0,0, 1,1, 255); //op->composite(r3.data(), 0, c5.data(), 0, 0,0, 1,1, 255); op->composite(r2.data(), 0, c1.data(), 0, 0,0, 1,1, 255); op->composite(r2.data(), 0, r3.data(), 0, 0,0, 1,1, 255); const quint8 *p1 = r1.data(); const quint8 *p2 = r2.data(); if (memcmp(p1, p2, 4) != 0) { for (int i = 0; i < 4; i++) { accum(qAbs(p1[i] - p2[i])); } } } qDebug("Errors for op %25s err rate %7.2f var %7.2f max %7.2f", op->id().toLatin1().data(), (qreal(count(accum)) / (4 * numIterations)), variance(accum), count(accum) > 0 ? (max)(accum) : 0); } } #include struct FillWorker : public QRunnable { FillWorker(KisPaintDeviceSP dev, const QRect &fillRect, bool clear) : m_dev(dev), m_fillRect(fillRect), m_clear(clear) { setAutoDelete(true); } void run() { if (m_clear) { m_dev->clear(m_fillRect); } else { const KoColor fillColor(Qt::red, m_dev->colorSpace()); const int pixelSize = m_dev->colorSpace()->pixelSize(); KisSequentialIterator it(m_dev, m_fillRect); while (it.nextPixel()) { memcpy(it.rawData(), fillColor.data(), pixelSize); } } } private: KisPaintDeviceSP m_dev; QRect m_fillRect; bool m_clear; }; #ifdef Q_OS_LINUX #include #endif void KisPaintDeviceTest::stressTestMemoryFragmentation() { const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KUndo2Stack undoStack; #ifdef LIMIT_LONG_TESTS const int numCycles = 3; undoStack.setUndoLimit(1); #else const int numCycles = 200; undoStack.setUndoLimit(10); #endif const int numThreads = 16; const int desiredWidth = 10000; const int patchSize = 81; const int numSidePatches = desiredWidth / patchSize; QThreadPool pool; pool.setMaxThreadCount(numThreads); for (int i = 0; i < numCycles; i++) { qDebug() << "iteration"<< i; // KisTransaction t(dev); for (int y = 0; y < numSidePatches; y++) { for (int x = 0; x < numSidePatches; x++) { const QRect workerRect(x * patchSize, y * patchSize, patchSize, patchSize); pool.start(new FillWorker(dev, workerRect, (i + x + y) & 0x1)); } } pool.waitForDone(); // undoStack.push(t.endAndTake()); qDebug() << "Iteration:" << i; #ifdef Q_OS_LINUX struct mallinfo info = mallinfo(); qDebug() << "Allocated on heap:" << (info.arena >> 20) << "MiB"; qDebug() << "Mmaped regions:" << info.hblks << (info.hblkhd >> 20) << "MiB"; qDebug() << "Free fastbin chunks:" << info.smblks << (info.fsmblks >> 10) << "KiB"; qDebug() << "Allocated in ordinary blocks" << (info.uordblks >> 20) << "MiB"; qDebug() << "Free in ordinary blockes" << info.ordblks << (info.fordblks >> 20) << "MiB"; #endif qDebug() << "========================================"; } undoStack.clear(); } KISTEST_MAIN(KisPaintDeviceTest) diff --git a/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp b/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp index 96c399a7ec..4820f3b862 100644 --- a/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp +++ b/libs/image/tiles3/tests/kis_tiled_data_manager_test.cpp @@ -1,947 +1,946 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tiled_data_manager_test.h" #include #include "tiles3/kis_tiled_data_manager.h" #include "tiles_test_utils.h" #include "config-limit-long-tests.h" bool KisTiledDataManagerTest::checkHole(quint8* buffer, quint8 holeColor, QRect holeRect, quint8 backgroundColor, QRect backgroundRect) { for(qint32 y = backgroundRect.y(); y <= backgroundRect.bottom(); y++) { for(qint32 x = backgroundRect.x(); x <= backgroundRect.right(); x++) { quint8 expectedColor = holeRect.contains(x,y) ? holeColor : backgroundColor; if(*buffer != expectedColor) { qDebug() << "Expected" << expectedColor << "but found" << *buffer; return false; } buffer++; } } return true; } bool KisTiledDataManagerTest::checkTilesShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect) { for(qint32 row = tilesRect.y(); row <= tilesRect.bottom(); row++) { for(qint32 col = tilesRect.x(); col <= tilesRect.right(); col++) { KisTileSP srcTile = takeOldSrc ? srcDM->getOldTile(col, row) : srcDM->getTile(col, row, false); KisTileSP dstTile = takeOldDst ? dstDM->getOldTile(col, row) : dstDM->getTile(col, row, false); if(srcTile->tileData() != dstTile->tileData()) { qDebug() << "Expected tile data (" << col << row << ")" << srcTile->extent() << srcTile->tileData() << "but found" << dstTile->tileData(); qDebug() << "Expected" << srcTile->data()[0] << "but found" << dstTile->data()[0]; return false; } } } return true; } bool KisTiledDataManagerTest::checkTilesNotShared(KisTiledDataManager *srcDM, KisTiledDataManager *dstDM, bool takeOldSrc, bool takeOldDst, QRect tilesRect) { for(qint32 row = tilesRect.y(); row <= tilesRect.bottom(); row++) { for(qint32 col = tilesRect.x(); col <= tilesRect.right(); col++) { KisTileSP srcTile = takeOldSrc ? srcDM->getOldTile(col, row) : srcDM->getTile(col, row, false); KisTileSP dstTile = takeOldDst ? dstDM->getOldTile(col, row) : dstDM->getTile(col, row, false); if(srcTile->tileData() == dstTile->tileData()) { qDebug() << "Expected tiles not be shared:"<< srcTile->extent(); return false; } } } return true; } void KisTiledDataManagerTest::testUndoingNewTiles() { // "growing extent bug" const QRect nullRect(qint32_MAX,qint32_MAX,0,0); quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTileSP emptyTile = srcDM.getTile(0, 0, false); QCOMPARE(srcDM.extent(), nullRect); KisMementoSP memento0 = srcDM.getMemento(); KisTileSP createdTile = srcDM.getTile(0, 0, true); srcDM.commit(); QCOMPARE(srcDM.extent(), QRect(0,0,64,64)); srcDM.rollback(memento0); QCOMPARE(srcDM.extent(), nullRect); } void KisTiledDataManagerTest::testPurgedAndEmptyTransactions() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); quint8 oddPixel1 = 128; QRect rect(0,0,512,512); QRect clearRect1(50,50,100,100); QRect clearRect2(150,50,100,100); quint8 *buffer = new quint8[rect.width()*rect.height()]; // purged transaction KisMementoSP memento0 = srcDM.getMemento(); srcDM.clear(clearRect1, &oddPixel1); srcDM.purgeHistory(memento0); memento0 = 0; srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1, defaultPixel, rect)); // one more purged transaction KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(clearRect2, &oddPixel1); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); srcDM.purgeHistory(memento1); memento1 = 0; srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); // empty one KisMementoSP memento2 = srcDM.getMemento(); srcDM.commit(); srcDM.rollback(memento2); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); // now check that everything works still KisMementoSP memento3 = srcDM.getMemento(); srcDM.setExtent(clearRect2); srcDM.commit(); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect2, defaultPixel, rect)); srcDM.rollback(memento3); srcDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, clearRect1 | clearRect2, defaultPixel, rect)); } void KisTiledDataManagerTest::testUnversionedBitBlt() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); srcDM.clear(rect, &oddPixel1); dstDM.clear(rect, &oddPixel2); dstDM.bitBlt(&srcDM, cloneRect); quint8 *buffer = new quint8[rect.width()*rect.height()]; dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, oddPixel2, rect)); delete[] buffer; // Test whether tiles became shared QVERIFY(checkTilesShared(&srcDM, &dstDM, false, false, tilesRect)); } void KisTiledDataManagerTest::testVersionedBitBlt() { quint8 defaultPixel = 0; KisTiledDataManager srcDM1(1, &defaultPixel); KisTiledDataManager srcDM2(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; quint8 oddPixel4 = 131; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect tilesRect(2,2,3,3); KisMementoSP memento1 = srcDM1.getMemento(); srcDM1.clear(rect, &oddPixel1); srcDM2.clear(rect, &oddPixel2); dstDM.clear(rect, &oddPixel3); KisMementoSP memento2 = dstDM.getMemento(); dstDM.bitBlt(&srcDM1, cloneRect); QVERIFY(checkTilesShared(&srcDM1, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.commit(); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); KisMementoSP memento3 = srcDM2.getMemento(); srcDM2.clear(rect, &oddPixel4); KisMementoSP memento4 = dstDM.getMemento(); dstDM.bitBlt(&srcDM2, cloneRect); QVERIFY(checkTilesShared(&srcDM2, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM2, &srcDM2, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.commit(); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); dstDM.rollback(memento4); QVERIFY(checkTilesShared(&srcDM1, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); dstDM.rollforward(memento4); QVERIFY(checkTilesShared(&srcDM2, &dstDM, false, false, tilesRect)); QVERIFY(checkTilesShared(&dstDM, &dstDM, true, false, tilesRect)); QVERIFY(checkTilesNotShared(&srcDM1, &srcDM1, true, false, tilesRect)); } void KisTiledDataManagerTest::testBitBltOldData() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); - QRect tilesRect(2,2,3,3); quint8 *buffer = new quint8[rect.width()*rect.height()]; KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel1); srcDM.commit(); dstDM.bitBltOldData(&srcDM, cloneRect); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, defaultPixel, rect)); KisMementoSP memento2 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel2); dstDM.bitBltOldData(&srcDM, cloneRect); srcDM.commit(); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, cloneRect, defaultPixel, rect)); delete[] buffer; } void KisTiledDataManagerTest::testBitBltRough() { quint8 defaultPixel = 0; KisTiledDataManager srcDM(1, &defaultPixel); KisTiledDataManager dstDM(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; QRect rect(0,0,512,512); QRect cloneRect(81,80,250,250); QRect actualCloneRect(64,64,320,320); QRect tilesRect(1,1,4,4); srcDM.clear(rect, &oddPixel1); dstDM.clear(rect, &oddPixel2); dstDM.bitBltRough(&srcDM, cloneRect); quint8 *buffer = new quint8[rect.width()*rect.height()]; dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, actualCloneRect, oddPixel2, rect)); // Test whether tiles became shared QVERIFY(checkTilesShared(&srcDM, &dstDM, false, false, tilesRect)); // check bitBltRoughOldData KisMementoSP memento1 = srcDM.getMemento(); srcDM.clear(rect, &oddPixel3); dstDM.bitBltRoughOldData(&srcDM, cloneRect); srcDM.commit(); dstDM.readBytes(buffer, rect.x(), rect.y(), rect.width(), rect.height()); QVERIFY(checkHole(buffer, oddPixel1, actualCloneRect, oddPixel2, rect)); delete[] buffer; } void KisTiledDataManagerTest::testTransactions() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; KisTileSP tile00; KisTileSP oldTile00; // Create a named transaction: versioning is enabled KisMementoSP memento1 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel1); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; // Create an anonymous transaction: versioning is disabled dm.commit(); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.clear(0, 0, 64, 64, &oddPixel2); // Versioning is disabled, i said! >:) tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; // And the last round: named transaction: KisMementoSP memento2 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel3); tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel3, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; } void KisTiledDataManagerTest::testPurgeHistory() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; quint8 oddPixel3 = 130; quint8 oddPixel4 = 131; KisMementoSP memento1 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel1); dm.commit(); KisMementoSP memento2 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel2); KisTileSP tile00; KisTileSP oldTile00; tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.purgeHistory(memento1); /** * Nothing nas changed in the visible state of the data manager */ tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel1, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; dm.commit(); dm.purgeHistory(memento2); /** * We've removed all the history of the device, so it * became "unversioned". * NOTE: the return value for getOldTile() when there is no * history present is a subject for change */ tile00 = dm.getTile(0, 0, false); oldTile00 = dm.getOldTile(0, 0); QVERIFY(memoryIsFilled(oddPixel2, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, oldTile00->data(), TILESIZE)); tile00 = oldTile00 = 0; /** * Just test we won't crash when the memento is not * present in history anymore */ KisMementoSP memento3 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel3); dm.commit(); KisMementoSP memento4 = dm.getMemento(); dm.clear(0, 0, 64, 64, &oddPixel4); dm.commit(); dm.rollback(memento4); dm.purgeHistory(memento3); dm.purgeHistory(memento4); } void KisTiledDataManagerTest::testUndoSetDefaultPixel() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); quint8 oddPixel1 = 128; quint8 oddPixel2 = 129; QRect fillRect(0,0,64,64); KisTileSP tile00; KisTileSP tile10; tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(defaultPixel, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); KisMementoSP memento1 = dm.getMemento(); dm.clear(fillRect, &oddPixel1); dm.commit(); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); KisMementoSP memento2 = dm.getMemento(); dm.setDefaultPixel(&oddPixel2); dm.commit(); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, tile10->data(), TILESIZE)); dm.rollback(memento2); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollback(memento1); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(defaultPixel, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollforward(memento1); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(defaultPixel, tile10->data(), TILESIZE)); dm.rollforward(memento2); tile00 = dm.getTile(0, 0, false); tile10 = dm.getTile(1, 0, false); QVERIFY(memoryIsFilled(oddPixel1, tile00->data(), TILESIZE)); QVERIFY(memoryIsFilled(oddPixel2, tile10->data(), TILESIZE)); } //#include void KisTiledDataManagerTest::benchmarkReadOnlyTileLazy() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); /* * See KisTileHashTableTraits2 for more details */ const qint32 numTilesToTest = 0x7fff; //CALLGRIND_START_INSTRUMENTATION; QBENCHMARK_ONCE { for(qint32 i = 0; i < numTilesToTest; i++) { KisTileSP tile = dm.getTile(i, i, false); } } //CALLGRIND_STOP_INSTRUMENTATION; } class KisSimpleClass : public KisShared { qint64 m_int; public: KisSimpleClass() { Q_UNUSED(m_int); } }; typedef KisSharedPtr KisSimpleClassSP; void KisTiledDataManagerTest::benchmarkSharedPointers() { const qint32 numIterations = 2 * 1000000; //CALLGRIND_START_INSTRUMENTATION; QBENCHMARK_ONCE { for(qint32 i = 0; i < numIterations; i++) { KisSimpleClassSP pointer = new KisSimpleClass; pointer = 0; } } //CALLGRIND_STOP_INSTRUMENTATION; } void KisTiledDataManagerTest::benchmarkCOWImpl() { const int pixelSize = 8; quint8 defaultPixel[pixelSize]; memset(defaultPixel, 1, pixelSize); KisTiledDataManager dm(pixelSize, defaultPixel); KisMementoSP memento1 = dm.getMemento(); /** * Imagine a regular image of 4096x2048 pixels * (64x32 tiles) */ for (int i = 0; i < 32; i++) { for (int j = 0; j < 64; j++) { KisTileSP tile = dm.getTile(j, i, true); tile->lockForWrite(); tile->unlockForWrite(); } } dm.commit(); QTest::qSleep(200); KisMementoSP memento2 = dm.getMemento(); QTest::qSleep(200); QBENCHMARK_ONCE { for (int i = 0; i < 32; i++) { for (int j = 0; j < 64; j++) { KisTileSP tile = dm.getTile(j, i, true); tile->lockForWrite(); tile->unlockForWrite(); } } } dm.commit(); } void KisTiledDataManagerTest::benchmarkCOWNoPooler() { KisTileDataStore::instance()->testingSuspendPooler(); QTest::qSleep(200); benchmarkCOWImpl(); KisTileDataStore::instance()->testingResumePooler(); QTest::qSleep(200); } void KisTiledDataManagerTest::benchmarkCOWWithPooler() { benchmarkCOWImpl(); } /******************* Stress job ***********************/ #ifdef LIMIT_LONG_TESTS #define NUM_CYCLES 10000 #else #define NUM_CYCLES 100000 #endif #define NUM_TYPES 12 #define TILE_DIMENSION 64 /** * The data manager has partial guarantees of reentrancy. That is * you can call any arbitrary number of methods concurrently as long * as their access areas do not intersect. * * Though the rule can be quite tricky -- some of the methods always * use entire image as their access area, so they cannot be called * concurrently in any circumstances. * The examples are: clear(), commit(), rollback() and etc... */ #define run_exclusive(lock, _i) for(_i = 0, (lock).lockForWrite(); _i < 1; _i++, (lock).unlock()) #define run_concurrent(lock, _i) for(_i = 0, (lock).lockForRead(); _i < 1; _i++, (lock).unlock()) //#define run_exclusive(lock, _i) while(0) //#define run_concurrent(lock, _i) while(0) class KisStressJob : public QRunnable { public: KisStressJob(KisTiledDataManager &dataManager, QRect rect, QReadWriteLock &_lock) : m_accessRect(rect), dm(dataManager), lock(_lock) { } void run() override { qsrand(QTime::currentTime().msec()); for(qint32 i = 0; i < NUM_CYCLES; i++) { qint32 type = qrand() % NUM_TYPES; qint32 t; switch(type) { case 0: run_concurrent(lock,t) { quint8 *buf; buf = new quint8[dm.pixelSize()]; memcpy(buf, dm.defaultPixel(), dm.pixelSize()); dm.setDefaultPixel(buf); delete[] buf; } break; case 1: case 2: run_concurrent(lock,t) { KisTileSP tile; tile = dm.getTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION, false); tile->lockForRead(); tile->unlockForRead(); tile = dm.getTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION, true); tile->lockForWrite(); tile->unlockForWrite(); tile = dm.getOldTile(m_accessRect.x() / TILE_DIMENSION, m_accessRect.y() / TILE_DIMENSION); tile->lockForRead(); tile->unlockForRead(); } break; case 3: run_concurrent(lock,t) { QRect newRect = dm.extent(); Q_UNUSED(newRect); } break; case 4: run_concurrent(lock,t) { dm.clear(m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height(), 4); } break; case 5: run_concurrent(lock,t) { quint8 *buf; buf = new quint8[m_accessRect.width() * m_accessRect.height() * dm.pixelSize()]; dm.readBytes(buf, m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height()); dm.writeBytes(buf, m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height()); delete[] buf; } break; case 6: run_concurrent(lock,t) { quint8 oddPixel = 13; KisTiledDataManager srcDM(1, &oddPixel); dm.bitBlt(&srcDM, m_accessRect); } break; case 7: case 8: run_exclusive(lock,t) { m_memento = dm.getMemento(); dm.clear(m_accessRect.x(), m_accessRect.y(), m_accessRect.width(), m_accessRect.height(), 2); dm.commit(); dm.rollback(m_memento); dm.rollforward(m_memento); dm.purgeHistory(m_memento); m_memento = 0; } break; case 9: run_exclusive(lock,t) { bool b = dm.hasCurrentMemento(); Q_UNUSED(b); } break; case 10: run_exclusive(lock,t) { dm.clear(); } break; case 11: run_exclusive(lock,t) { dm.setExtent(m_accessRect); } break; } } } private: KisMementoSP m_memento; QRect m_accessRect; KisTiledDataManager &dm; QReadWriteLock &lock; }; void KisTiledDataManagerTest::stressTest() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); QReadWriteLock lock; #ifdef LIMIT_LONG_TESTS const int numThreads = 8; const int numWorkers = 8; #else const int numThreads = 16; const int numWorkers = 48; #endif QThreadPool pool; pool.setMaxThreadCount(numThreads); QRect accessRect(0,0,512,512); for(qint32 i = 0; i < numWorkers; i++) { KisStressJob *job = new KisStressJob(dm, accessRect, lock); pool.start(job); accessRect.translate(512, 0); } pool.waitForDone(); } template void applyToRect(const QRect &rc, Func func) { for (int y = rc.y(); y < rc.y() + rc.height(); y += KisTileData::HEIGHT) { for (int x = rc.x(); x < rc.x() + rc.width(); x += KisTileData::WIDTH) { const int col = x / KisTileData::WIDTH; const int row = y / KisTileData::HEIGHT; func(col, row); } } } class LazyCopyingStressJob : public QRunnable { public: LazyCopyingStressJob(KisTiledDataManager &dataManager, const QRect &rect, QReadWriteLock &dmExclusiveLock, QReadWriteLock &tileExclusiveLock, int numCycles, bool isWriter) : m_accessRect(rect), dm(dataManager), m_dmExclusiveLock(dmExclusiveLock), m_tileExclusiveLock(tileExclusiveLock), m_numCycles(numCycles), m_isWriter(isWriter) { } void run() override { for(qint32 i = 0; i < m_numCycles; i++) { //const int epoch = i % 100; int t; if (m_isWriter && 0) { } else { const bool shouldClear = i % 5 <= 1; // 40% of requests are clears const bool shouldWrite = i % 5 <= 3; // other 40% of requests are writes run_concurrent(m_dmExclusiveLock, t) { if (shouldClear) { QWriteLocker locker(&m_tileExclusiveLock); dm.clear(m_accessRect, 4); } else { auto readFunc = [this] (int col, int row) { KisTileSP tile = dm.getTile(col, row, false); tile->lockForRead(); tile->unlockForRead(); }; auto writeFunc = [this] (int col, int row) { KisTileSP tile = dm.getTile(col, row, true); tile->lockForWrite(); tile->unlockForWrite(); }; auto readOldFunc = [this] (int col, int row) { KisTileSP tile = dm.getOldTile(col, row); tile->lockForRead(); tile->unlockForRead(); }; applyToRect(m_accessRect, readFunc); if (shouldWrite) { QReadLocker locker(&m_tileExclusiveLock); applyToRect(m_accessRect, writeFunc); } applyToRect(m_accessRect, readOldFunc); } } } } } private: KisMementoSP m_memento; QRect m_accessRect; KisTiledDataManager &dm; QReadWriteLock &m_dmExclusiveLock; QReadWriteLock &m_tileExclusiveLock; const int m_numCycles; const bool m_isWriter; }; void KisTiledDataManagerTest::stressTestLazyCopying() { quint8 defaultPixel = 0; KisTiledDataManager dm(1, &defaultPixel); QReadWriteLock dmLock; QReadWriteLock tileLock; #ifdef LIMIT_LONG_TESTS const int numCycles = 10000; const int numThreads = 8; const int numWorkers = 8; #else const int numThreads = 16; const int numWorkers = 32; const int numCycles = 100000; #endif QThreadPool pool; pool.setMaxThreadCount(numThreads); const QRect accessRect(0,0,512,256); for(qint32 i = 0; i < numWorkers; i++) { const bool isWriter = i == 0; LazyCopyingStressJob *job = new LazyCopyingStressJob(dm, accessRect, dmLock, tileLock, numCycles, isWriter); pool.start(job); } pool.waitForDone(); } QTEST_MAIN(KisTiledDataManagerTest) diff --git a/libs/pigment/resources/KoStopGradient.h b/libs/pigment/resources/KoStopGradient.h index e9a4f31ab4..4c4d653809 100644 --- a/libs/pigment/resources/KoStopGradient.h +++ b/libs/pigment/resources/KoStopGradient.h @@ -1,97 +1,104 @@ /* Copyright (c) 2007 Sven Langkamp 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.1 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOSTOPGRADIENT_H #define KOSTOPGRADIENT_H #include #include #include "KoColor.h" #include #include #include #include typedef QPair KoGradientStop; +struct KoGradientStopValueSort +{ + inline bool operator() (const KoGradientStop& a, const KoGradientStop& b) { + return (a.second.toQColor().valueF() < b.second.toQColor().valueF()); + } +}; + /** * Resource for colorstop based gradients like SVG gradients */ class KRITAPIGMENT_EXPORT KoStopGradient : public KoAbstractGradient, public boost::equality_comparable { public: explicit KoStopGradient(const QString &filename = QString()); ~KoStopGradient() override; bool operator==(const KoStopGradient &rhs) const; KoAbstractGradient* clone() const override; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice* dev) const override; /// reimplemented QGradient* toQGradient() const override; /// Find stops surrounding position, returns false if position outside gradient bool stopsAt(KoGradientStop& leftStop, KoGradientStop& rightStop, qreal t) const; /// reimplemented void colorAt(KoColor&, qreal t) const override; /// Creates KoStopGradient from a QGradient static KoStopGradient * fromQGradient(const QGradient * gradient); /// Sets the gradient stops void setStops(QList stops); QList stops() const; /// reimplemented QString defaultFileExtension() const override; /** * @brief toXML * Convert the gradient to an XML string. */ void toXML(QDomDocument& doc, QDomElement& gradientElt) const; /** * @brief fromXML * convert a gradient from xml. * @return a gradient. */ static KoStopGradient fromXML(const QDomElement& elt); protected: QList m_stops; QPointF m_start; QPointF m_stop; QPointF m_focalPoint; private: void loadSvgGradient(QIODevice *file); void parseSvgGradient(const QDomElement& element); void parseSvgColor(QColor &color, const QString &s); }; #endif // KOSTOPGRADIENT_H diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 8a24cee187..03b4bbc9e1 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,2180 +1,2186 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" #include // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , importExportManager(new KisImportExportManager(q)) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , savingLock(&savingMutex) { copyFromImpl(rhs, q, CONSTRUCT); } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; QList paletteList; bool ownsPaletteList = false; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } void copyFrom(const Private &rhs, KisDocument *q); void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy); /// clones the palette list oldList /// the ownership of the returned KoColorSet * belongs to the caller QList clonePaletteList(const QList &oldList); class StrippedSafeSavingLocker; }; void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q) { copyFromImpl(rhs, q, KisDocument::REPLACE); } void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy) { if (policy == REPLACE) { delete docInfo; } docInfo = (new KoDocumentInfo(*rhs.docInfo, q)); unit = rhs.unit; mimeType = rhs.mimeType; outputMimeType = rhs.outputMimeType; if (policy == REPLACE) { q->setGuidesConfig(rhs.guidesConfig); q->setMirrorAxisConfig(rhs.mirrorAxisConfig); q->setModified(rhs.modified); q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants)); q->setGridConfig(rhs.gridConfig); } else { // in CONSTRUCT mode, we cannot use the functions of KisDocument // because KisDocument does not yet have a pointer to us. guidesConfig = rhs.guidesConfig; mirrorAxisConfig = rhs.mirrorAxisConfig; modified = rhs.modified; assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants); gridConfig = rhs.gridConfig; } m_bAutoDetectedMime = rhs.m_bAutoDetectedMime; m_url = rhs.m_url; m_file = rhs.m_file; readwrite = rhs.readwrite; firstMod = rhs.firstMod; lastMod = rhs.lastMod; // XXX: the display properties will be shared between different snapshots globalAssistantsColor = rhs.globalAssistantsColor; if (policy == REPLACE) { QList newPaletteList = clonePaletteList(rhs.paletteList); q->setPaletteList(newPaletteList, /* emitSignal = */ true); // we still do not own palettes if we did not } else { paletteList = rhs.paletteList; } batchMode = rhs.batchMode; } QList KisDocument::Private::clonePaletteList(const QList &oldList) { QList newList; Q_FOREACH (KoColorSet *palette, oldList) { newList << new KoColorSet(*palette); } return newList; } class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); - QApplication::processEvents(); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { copyFromDocumentImpl(rhs, CONSTRUCT); } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } if (d->ownsPaletteList) { qDeleteAll(d->paletteList); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); //return ImportExportCodes::NoAccessToWrite; return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { KBackup::simpleBackupFile(job.filePath, backupDir, suffix); } else if (numOfBackupsKept > 2) { KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept); } } //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); if (job.mimeType.isEmpty()) { KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; slotCompleteSavingDocument(job, error, error.errorMessage()); return false; } const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); bool started = initiateSavingInBackground(actionName, this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)), job, exportConfiguration); if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; if (!filter->convert(this, &buffer).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning - QApplication::processEvents(); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } KisDocument *KisDocument::lockAndCreateSnapshot() { KisDocument *doc = lockAndCloneForSaving(); if (doc) { // clone palette list doc->d->paletteList = doc->d->clonePaletteList(doc->d->paletteList); doc->d->ownsPaletteList = true; } return doc; } void KisDocument::copyFromDocument(const KisDocument &rhs) { copyFromDocumentImpl(rhs, REPLACE); } void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy) { if (policy == REPLACE) { d->copyFrom(*(rhs.d), this); d->undoStack->clear(); } else { // in CONSTRUCT mode, d should be already initialized connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); d->shapeController = new KisShapeController(this, d->nserver); d->koShapeController = new KoShapeController(0, d->shapeController); d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController); } setObjectName(rhs.objectName()); slotConfigChanged(); if (rhs.d->image) { if (policy == REPLACE) { d->image->barrierLock(/* readOnly = */ false); rhs.d->image->barrierLock(/* readOnly = */ true); d->image->copyFromImage(*(rhs.d->image)); d->image->unlock(); rhs.d->image->unlock(); setCurrentImage(d->image, /* forceInitialUpdate = */ true); } else { // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false); } } if (rhs.d->preActivatedNode) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(d->image->root(), [&linearizedNodes, &rhs, this](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); if (rhs.d->preActivatedNode.data() == refNode.data()) { d->preActivatedNode = node; } }); } KisNodeSP foundNode = KisLayerUtils::recursiveFindNode(image()->rootLayer(), [](KisNodeSP node) -> bool { return dynamic_cast(node.data()); }); KisReferenceImagesLayer *refLayer = dynamic_cast(foundNode.data()); setReferenceImagesLayer(refLayer, /* updateImage = */ false); if (policy == REPLACE) { setModified(true); } } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** * The caller guarantees that noone else uses the document (usually, * it is a temporary docuent created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; return status.isOk(); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)), this, SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage) { - KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { + KIS_ASSERT_RECOVER_RETURN(isSaving()); + + KIS_ASSERT_RECOVER(d->backgroundSaveDocument) { d->savingMutex.unlock(); return; } - KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); - if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); - d->savingMutex.unlock(); - KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); + KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) { + d->savingMutex.unlock(); + return; + } + const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); + // unlock at the very end + d->savingMutex.unlock(); + KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK")); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); if (!status.isOk()) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage()); return false; } typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, ""); return; } KisImportExportErrorCode status = d->childSavingFuture.result(); const QString errorMessage = status.errorMessage(); d->savingImage.clear(); d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (ret) { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } QString msg = status.errorMessage(); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } void KisDocument::autoSaveOnPause() { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); bool started = initiateSavingSynchronously( KritaUtils::ExportFileJob( autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode)); // FIXME?: when the document is pushed to background while it is being // drawn on, it doesn't get saved. One possible solution could be to block // and emit signal when it finishes. if (started) { d->modifiedAfterAutosave = false; } else { qDebug() << "Could not auto-save when paused"; } } bool KisDocument::initiateSavingSynchronously(const KritaUtils::ExportFileJob& job) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false) QUrl url("file:/" + job.filePath); bool started = exportDocumentSync(url, job.mimeType); if (!started) { d->savingMutex.unlock(); // KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); // KisStrokeId strokeId = d->image->startStroke(stroke); // d->image->endStroke(strokeId); // d->image->unlock(); // try again started = exportDocumentSync(url, job.mimeType); } return started; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { QFile::remove(autosaveBaseName); } } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); if (d->undoStack->undoLimit() != cfg.undoStackLimit()) { if (!d->undoStack->isClean()) { d->undoStack->clear(); } d->undoStack->setUndoLimit(cfg.undoStackLimit()); } d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { if (d->gridConfig != config) { d->gridConfig = config; emit sigGridConfigChanged(config); } } QList &KisDocument::paletteList() { return d->paletteList; } void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal) { if (d->paletteList != paletteList) { QList oldPaletteList = d->paletteList; d->paletteList = paletteList; if (emitSignal) { emit sigPaletteListChanged(oldPaletteList, paletteList); } } } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) { return false; } if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) { return false; } d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8" , name , width, height , imageResolution * 72.0 , image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name() , image->colorSpace()->profile()->name() , numberOfLayers)); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { if (d->assistants != value) { d->assistants = value; emit sigAssistantsChanged(); } } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer == layer) { return; } if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } emit sigReferenceImagesLayerChanged(layer); } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { // we only set as modified if undo stack is not at clean state setModified(!d->undoStack->isClean()); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) { return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp index 65eaf8dedc..baabd4875c 100644 --- a/libs/ui/KisImportExportFilter.cpp +++ b/libs/ui/KisImportExportFilter.cpp @@ -1,344 +1,344 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisImportExportFilter.h" #include #include #include #include #include "KisImportExportManager.h" #include #include #include #include #include "KoUpdater.h" #include #include "kis_config.h" #include #include const QString KisImportExportFilter::ImageContainsTransparencyTag = "ImageContainsTransparency"; const QString KisImportExportFilter::ColorModelIDTag = "ColorModelID"; const QString KisImportExportFilter::ColorDepthIDTag = "ColorDepthID"; const QString KisImportExportFilter::sRGBTag = "sRGB"; class Q_DECL_HIDDEN KisImportExportFilter::Private { public: QPointer updater; QByteArray mime; QString filename; QString realFilename; bool batchmode; QMap capabilities; Private() : updater(0), mime("") , batchmode(false) {} ~Private() { qDeleteAll(capabilities); } }; KisImportExportFilter::KisImportExportFilter(QObject *parent) : QObject(parent) , d(new Private) { } KisImportExportFilter::~KisImportExportFilter() { if (d->updater) { d->updater->setProgress(100); } delete d; } QString KisImportExportFilter::filename() const { return d->filename; } QString KisImportExportFilter::realFilename() const { return d->realFilename; } bool KisImportExportFilter::batchMode() const { return d->batchmode; } void KisImportExportFilter::setBatchMode(bool batchmode) { d->batchmode = batchmode; } void KisImportExportFilter::setFilename(const QString &filename) { d->filename = filename; } void KisImportExportFilter::setRealFilename(const QString &filename) { d->realFilename = filename; } void KisImportExportFilter::setMimeType(const QString &mime) { d->mime = mime.toLatin1(); } QByteArray KisImportExportFilter::mimeType() const { return d->mime; } QString KisImportExportFilter::conversionStatusString(ConversionStatus status) { QString msg; switch (status) { case OK: break; case FilterCreationError: msg = i18n("Krita does not support this file format"); break; case CreationError: msg = i18n("Could not create the output document"); break; case FileNotFound: msg = i18n("File not found"); break; case StorageCreationError: msg = i18n("Cannot create storage"); break; case BadMimeType: msg = i18n("Bad MIME type"); break; case WrongFormat: msg = i18n("Format not recognized"); break; case NotImplemented: msg = i18n("Not implemented"); break; case ParsingError: msg = i18n("Parsing error"); break; case InvalidFormat: msg = i18n("Invalid file format"); break; case InternalError: case UsageError: msg = i18n("Internal error"); break; case ProgressCancelled: msg = i18n("Cancelled by user"); break; case BadConversionGraph: msg = i18n("Unknown file type"); break; case UnsupportedVersion: msg = i18n("Unsupported file version"); break; case UserCancelled: // intentionally we do not prompt the error message here break; default: msg = i18n("Unknown error"); break; } return msg; } KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const { KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to); const QString filterConfig = KisConfig(true).exportConfigurationXML(to); if (cfg && !filterConfig.isEmpty()) { cfg->fromXML(filterConfig, false); } return cfg; } KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } QMap KisImportExportFilter::exportChecks() { qDeleteAll(d->capabilities); initializeCapabilities(); return d->capabilities; } QString KisImportExportFilter::verify(const QString &fileName) const { QFileInfo fi(fileName); if (!fi.exists()) { - return i18n("%1 does not exit after writing. Try saving again under a different name, in another location.", fileName); + return i18n("%1 does not exist after writing. Try saving again under a different name, in another location.", fileName); } if (!fi.isReadable()) { return i18n("%1 is not readable", fileName); } if (fi.size() < 10) { return i18n("%1 is smaller than 10 bytes, it must be corrupt. Try saving again under a different name, in another location.", fileName); } QFile f(fileName); f.open(QFile::ReadOnly); QByteArray ba = f.read(std::min(f.size(), (qint64)1000)); bool found = false; for(int i = 0; i < ba.size(); ++i) { if (ba.at(i) > 0) { found = true; break; } } if (!found) { return i18n("%1 has only zero bytes in the first 1000 bytes, it's probably corrupt. Try saving again under a different name, in another location.", fileName); } return QString(); } void KisImportExportFilter::setUpdater(QPointer updater) { d->updater = updater; } void KisImportExportFilter::setProgress(int value) { if (d->updater) { d->updater->setValue(value); } } void KisImportExportFilter::initializeCapabilities() { // XXX: Initialize everything to fully supported? } void KisImportExportFilter::addCapability(KisExportCheckBase *capability) { d->capabilities[capability->id()] = capability; } void KisImportExportFilter::addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level) { Q_ASSERT(level != KisExportCheckBase::SUPPORTED); QString layerMessage; QString imageMessage; QList allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorModelID, allColorModels) { QList allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorDepthID, allColorDepths) { KisExportCheckFactory *colorModelCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id()); KisExportCheckFactory *colorModelPerLayerCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id()); if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) { qWarning() << "No factory for" << colorModelID << colorDepthID; continue; } if (supportedColorModels.contains(QPair(colorModelID, colorDepthID))) { addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED)); addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED)); } else { if (level == KisExportCheckBase::PARTIALLY) { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will be converted." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be converted or skipped." ,name, colorModelID.name(), colorDepthID.name()); } else { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will not be saved." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be skipped." , name, colorModelID.name(), colorDepthID.name()); } addCapability(colorModelCheckFactory->create(level, imageMessage)); addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage)); } } } } QString KisImportExportFilter::verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const { QScopedPointer store(KoStore::createStore(fileName, KoStore::Read, KIS_MIME_TYPE, KoStore::Zip)); if (!store || store->bad()) { return i18n("Could not open the saved file %1. Please try to save again in a different location.", fileName); } Q_FOREACH(const QString &file, filesToCheck) { if (!store->hasFile(file)) { return i18n("File %1 is missing in %2 and is broken. Please try to save again in a different location.", file, fileName); } } return QString(); } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 6977484d8a..759c8eb128 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2660 +1,2671 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In ths case we should clear oll the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { + if (hackIsSaving()) { + e->setAccepted(false); + return; + } + if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { + // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. + // After the background saving job is initiated, KisDocument blocks closing + // while it saves itself. + if (hackIsSaving()) { + return; + } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } #include diff --git a/libs/ui/KisPaletteEditor.cpp b/libs/ui/KisPaletteEditor.cpp index 8cfe9e1056..926af79d97 100644 --- a/libs/ui/KisPaletteEditor.cpp +++ b/libs/ui/KisPaletteEditor.cpp @@ -1,663 +1,663 @@ /* * Copyright (c) 2018 Michael Zhou * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPaletteEditor.h" struct KisPaletteEditor::PaletteInfo { QString name; QString filename; int columnCount; bool isGlobal; bool isReadOnly; QHash groups; }; struct KisPaletteEditor::Private { bool isGlobalModified {false}; bool isNameModified {false}; bool isFilenameModified {false}; bool isColumnCountModified {false}; QSet modifiedGroupNames; // key is original group name QSet newGroupNames; QSet keepColorGroups; QSet pathsToRemove; QString groupBeingRenamed; QPointer model; QPointer view; PaletteInfo modified; QPointer query; KoResourceServer *rServer; QPalette normalPalette; QPalette warnPalette; }; KisPaletteEditor::KisPaletteEditor(QObject *parent) : QObject(parent) , m_d(new Private) { m_d->rServer = KoResourceServerProvider::instance()->paletteServer(); m_d->warnPalette.setColor(QPalette::Text, Qt::red); } KisPaletteEditor::~KisPaletteEditor() { } void KisPaletteEditor::setPaletteModel(KisPaletteModel *model) { if (!model) { return; } m_d->model = model; slotPaletteChanged(); connect(model, SIGNAL(sigPaletteChanged()), SLOT(slotPaletteChanged())); connect(model, SIGNAL(sigPaletteModified()), SLOT(slotSetDocumentModified())); } void KisPaletteEditor::setView(KisViewManager *view) { m_d->view = view; } void KisPaletteEditor::addPalette() { if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoDialog dlg; QFormLayout layout; dlg.mainWidget()->setLayout(&layout); QLabel lbl(i18nc("Label for line edit to set a palette name.","Name")); QLineEdit le(i18nc("Default name for a new palette","New Palette")); layout.addRow(&lbl, &le); if (dlg.exec() != QDialog::Accepted) { return; } KoColorSet *newColorSet = new KoColorSet(newPaletteFileName(false)); newColorSet->setPaletteType(KoColorSet::KPL); newColorSet->setIsGlobal(false); newColorSet->setIsEditable(true); newColorSet->setValid(true); newColorSet->setName(le.text()); m_d->rServer->addResource(newColorSet); m_d->rServer->removeFromBlacklist(newColorSet); uploadPaletteList(); } void KisPaletteEditor::importPalette() { KoFileDialog dialog(0, KoFileDialog::OpenFile, "Open Palette"); dialog.setDefaultDir(QDir::homePath()); dialog.setMimeTypeFilters(QStringList() << "krita/x-colorset" << "application/x-gimp-color-palette"); QString filename = dialog.filename(); if (filename.isEmpty()) { return; } if (duplicateExistsFilename(filename, false)) { QMessageBox message; message.setWindowTitle(i18n("Can't Import Palette")); message.setText(i18n("Can't import palette: there's already imported with the same filename")); message.exec(); return; } KoColorSet *colorSet = new KoColorSet(filename); colorSet->load(); QString name = filenameFromPath(colorSet->filename()); if (duplicateExistsFilename(name, false)) { colorSet->setFilename(newPaletteFileName(false)); } else { colorSet->setFilename(name); } colorSet->setIsGlobal(false); m_d->rServer->addResource(colorSet); m_d->rServer->removeFromBlacklist(colorSet); uploadPaletteList(); } void KisPaletteEditor::removePalette(KoColorSet *cs) { if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!cs || !cs->isEditable()) { return; } if (cs->isGlobal()) { m_d->rServer->removeResourceAndBlacklist(cs); QFile::remove(cs->filename()); return; } m_d->rServer->removeResourceFromServer(cs); uploadPaletteList(); } int KisPaletteEditor::rowNumberOfGroup(const QString &oriName) const { if (!m_d->modified.groups.contains(oriName)) { return 0; } return m_d->modified.groups[oriName].rowCount(); } bool KisPaletteEditor::duplicateExistsGroupName(const QString &name) const { if (name == m_d->groupBeingRenamed) { return false; } Q_FOREACH (const KisSwatchGroup &g, m_d->modified.groups.values()) { if (name == g.name()) { return true; } } return false; } bool KisPaletteEditor::duplicateExistsOriginalGroupName(const QString &name) const { return m_d->modified.groups.contains(name); } QString KisPaletteEditor::oldNameFromNewName(const QString &newName) const { Q_FOREACH (const QString &oldGroupName, m_d->modified.groups.keys()) { if (m_d->modified.groups[oldGroupName].name() == newName) { return oldGroupName; } } return QString(); } void KisPaletteEditor::rename(const QString &newName) { if (newName.isEmpty()) { return; } m_d->isNameModified = true; m_d->modified.name = newName; } void KisPaletteEditor::changeFilename(const QString &newName) { if (newName.isEmpty()) { return; } m_d->isFilenameModified = true; m_d->pathsToRemove.insert(m_d->modified.filename); if (m_d->modified.isGlobal) { m_d->modified.filename = m_d->rServer->saveLocation() + newName; } else { m_d->modified.filename = newName; } } void KisPaletteEditor::changeColCount(int newCount) { m_d->isColumnCountModified = true; m_d->modified.columnCount = newCount; } QString KisPaletteEditor::addGroup() { KoDialog dlg; m_d->query = &dlg; QVBoxLayout layout(&dlg); dlg.mainWidget()->setLayout(&layout); QLabel lblName(i18n("Name"), &dlg); layout.addWidget(&lblName); QLineEdit leName(&dlg); leName.setText(newGroupName()); connect(&leName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString))); layout.addWidget(&leName); QLabel lblRowCount(i18n("Row count"), &dlg); layout.addWidget(&lblRowCount); QSpinBox spxRow(&dlg); spxRow.setValue(20); layout.addWidget(&spxRow); if (dlg.exec() != QDialog::Accepted) { return QString(); } if (duplicateExistsGroupName(leName.text())) { return QString(); } QString realName = leName.text(); QString name = realName; if (duplicateExistsOriginalGroupName(name)) { name = newGroupName(); } m_d->modified.groups[name] = KisSwatchGroup(); KisSwatchGroup &newGroup = m_d->modified.groups[name]; newGroup.setName(realName); m_d->newGroupNames.insert(name); newGroup.setRowCount(spxRow.value()); return realName; } bool KisPaletteEditor::removeGroup(const QString &name) { KoDialog window; window.setWindowTitle(i18nc("@title:window", "Removing Group")); QFormLayout editableItems(&window); QCheckBox chkKeep(&window); window.mainWidget()->setLayout(&editableItems); editableItems.addRow(i18nc("Shows up when deleting a swatch group", "Keep the Colors"), &chkKeep); if (window.exec() != KoDialog::Accepted) { return false; } m_d->modified.groups.remove(name); m_d->newGroupNames.remove(name); if (chkKeep.isChecked()) { m_d->keepColorGroups.insert(name); } return true; } QString KisPaletteEditor::renameGroup(const QString &oldName) { if (oldName.isEmpty() || oldName == KoColorSet::GLOBAL_GROUP_NAME) { return QString(); } KoDialog dlg; m_d->query = &dlg; m_d->groupBeingRenamed = m_d->modified.groups[oldName].name(); QFormLayout form(&dlg); dlg.mainWidget()->setLayout(&form); QLineEdit leNewName; connect(&leNewName, SIGNAL(textChanged(QString)), SLOT(slotGroupNameChanged(QString))); leNewName.setText(m_d->modified.groups[oldName].name()); form.addRow(i18nc("Renaming swatch group", "New name"), &leNewName); if (dlg.exec() != KoDialog::Accepted) { return QString(); } if (leNewName.text().isEmpty()) { return QString(); } if (duplicateExistsGroupName(leNewName.text())) { return QString(); } m_d->modified.groups[oldName].setName(leNewName.text()); m_d->modifiedGroupNames.insert(oldName); return leNewName.text(); } void KisPaletteEditor::slotGroupNameChanged(const QString &newName) { QLineEdit *leGroupName = qobject_cast(sender()); if (duplicateExistsGroupName(newName) || newName == QString()) { leGroupName->setPalette(m_d->warnPalette); if (m_d->query->button(KoDialog::Ok)) { m_d->query->button(KoDialog::Ok)->setEnabled(false); } return; } leGroupName->setPalette(m_d->normalPalette); if (m_d->query->button(KoDialog::Ok)) { m_d->query->button(KoDialog::Ok)->setEnabled(true); } } void KisPaletteEditor::changeGroupRowCount(const QString &name, int newRowCount) { if (!m_d->modified.groups.contains(name)) { return; } m_d->modified.groups[name].setRowCount(newRowCount); m_d->modifiedGroupNames.insert(name); } void KisPaletteEditor::setGlobal(bool isGlobal) { m_d->isGlobalModified = true; m_d->modified.isGlobal = isGlobal; } void KisPaletteEditor::setEntry(const KoColor &color, const QModelIndex &index) { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KisSwatch c = KisSwatch(color); c.setId(QString::number(m_d->model->colorSet()->colorCount() + 1)); c.setName(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1))); m_d->model->setEntry(c, index); } void KisPaletteEditor::slotSetDocumentModified() { m_d->view->document()->setModified(true); } void KisPaletteEditor::removeEntry(const QModelIndex &index) { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { removeGroup(qvariant_cast(index.data(KisPaletteModel::GroupNameRole))); updatePalette(); } else { m_d->model->removeEntry(index, false); } if (m_d->model->colorSet()->isGlobal()) { m_d->model->colorSet()->save(); return; } } void KisPaletteEditor::modifyEntry(const QModelIndex &index) { if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoDialog dlg; dlg.setCaption(i18nc("@title:window", "Add a Color")); QFormLayout *editableItems = new QFormLayout(&dlg); dlg.mainWidget()->setLayout(editableItems); QString groupName = qvariant_cast(index.data(Qt::DisplayRole)); if (qvariant_cast(index.data(KisPaletteModel::IsGroupNameRole))) { renameGroup(groupName); updatePalette(); } else { QLineEdit *lnIDName = new QLineEdit(&dlg); QLineEdit *lnGroupName = new QLineEdit(&dlg); KisColorButton *bnColor = new KisColorButton(&dlg); QCheckBox *chkSpot = new QCheckBox(&dlg); chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); KisSwatch entry = m_d->model->getEntry(index); editableItems->addRow(i18n("ID"), lnIDName); editableItems->addRow(i18nc("Name for a swatch group", "Swatch group name"), lnGroupName); editableItems->addRow(i18n("Color"), bnColor); - editableItems->addRow(i18n("Spot"), chkSpot); + editableItems->addRow(i18n("Spot color"), chkSpot); lnGroupName->setText(entry.name()); lnIDName->setText(entry.id()); bnColor->setColor(entry.color()); chkSpot->setChecked(entry.spotColor()); if (dlg.exec() == KoDialog::Accepted) { entry.setName(lnGroupName->text()); entry.setId(lnIDName->text()); entry.setColor(bnColor->color()); entry.setSpotColor(chkSpot->isChecked()); m_d->model->setEntry(entry, index); } } } void KisPaletteEditor::addEntry(const KoColor &color) { Q_ASSERT(m_d->model); if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!m_d->model->colorSet()->isEditable()) { return; } KoDialog window; window.setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry")); QFormLayout editableItems(&window); window.mainWidget()->setLayout(&editableItems); QComboBox cmbGroups(&window); cmbGroups.addItems(m_d->model->colorSet()->getGroupNames()); QLineEdit lnIDName(&window); QLineEdit lnName(&window); KisColorButton bnColor(&window); QCheckBox chkSpot(&window); chkSpot.setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color.")); editableItems.addRow(i18n("Group"), &cmbGroups); editableItems.addRow(i18n("ID"), &lnIDName); editableItems.addRow(i18n("Name"), &lnName); editableItems.addRow(i18n("Color"), &bnColor); editableItems.addRow(i18nc("Spot color", "Spot"), &chkSpot); cmbGroups.setCurrentIndex(0); lnName.setText(i18nc("Default name for a color swatch","Color %1", QString::number(m_d->model->colorSet()->colorCount()+1))); lnIDName.setText(QString::number(m_d->model->colorSet()->colorCount() + 1)); bnColor.setColor(color); chkSpot.setChecked(false); if (window.exec() != KoDialog::Accepted) { return; } QString groupName = cmbGroups.currentText(); KisSwatch newEntry; newEntry.setColor(bnColor.color()); newEntry.setName(lnName.text()); newEntry.setId(lnIDName.text()); newEntry.setSpotColor(chkSpot.isChecked()); m_d->model->addEntry(newEntry, groupName); if (m_d->model->colorSet()->isGlobal()) { m_d->model->colorSet()->save(); return; } m_d->modifiedGroupNames.insert(groupName); m_d->modified.groups[groupName].addEntry(newEntry); } void KisPaletteEditor::updatePalette() { Q_ASSERT(m_d->model); Q_ASSERT(m_d->model->colorSet()); if (!m_d->model->colorSet()->isEditable()) { return; } if (!m_d->view) { return; } if (!m_d->view->document()) { return; } KoColorSet *palette = m_d->model->colorSet(); PaletteInfo &modified = m_d->modified; if (m_d->isColumnCountModified) { palette->setColumnCount(modified.columnCount); } if (m_d->isNameModified) { palette->setName(modified.name); } if (m_d->isFilenameModified) { QString originalPath = palette->filename(); palette->setFilename(modified.filename); if (palette->isGlobal()) { if (!palette->save()) { palette->setFilename(newPaletteFileName(true)); palette->save(); } QFile::remove(originalPath); } } if (m_d->isGlobalModified) { palette->setIsGlobal(modified.isGlobal); if (modified.isGlobal) { setGlobal(); } else { setNonGlobal(); } } Q_FOREACH (const QString &groupName, palette->getGroupNames()) { if (!modified.groups.contains(groupName)) { m_d->model->removeGroup(groupName, m_d->keepColorGroups.contains(groupName)); } } m_d->keepColorGroups.clear(); Q_FOREACH (const QString &groupName, palette->getGroupNames()) { if (m_d->modifiedGroupNames.contains(groupName)) { m_d->model->setRowNumber(groupName, modified.groups[groupName].rowCount()); if (groupName != modified.groups[groupName].name()) { m_d->model->renameGroup(groupName, modified.groups[groupName].name()); modified.groups[modified.groups[groupName].name()] = modified.groups[groupName]; modified.groups.remove(groupName); } } } m_d->modifiedGroupNames.clear(); Q_FOREACH (const QString &newGroupName, m_d->newGroupNames) { m_d->model->addGroup(modified.groups[newGroupName]); } m_d->newGroupNames.clear(); if (m_d->model->colorSet()->isGlobal()) { m_d->model->colorSet()->save(); } } void KisPaletteEditor::slotPaletteChanged() { Q_ASSERT(m_d->model); if (!m_d->model->colorSet()) { return; } KoColorSet *palette = m_d->model->colorSet(); m_d->modified.groups.clear(); m_d->keepColorGroups.clear(); m_d->newGroupNames.clear(); m_d->modifiedGroupNames.clear(); m_d->modified.name = palette->name(); m_d->modified.filename = palette->filename(); m_d->modified.columnCount = palette->columnCount(); m_d->modified.isGlobal = palette->isGlobal(); m_d->modified.isReadOnly = !palette->isEditable(); Q_FOREACH (const QString &groupName, palette->getGroupNames()) { KisSwatchGroup *cs = palette->getGroup(groupName); m_d->modified.groups[groupName] = KisSwatchGroup(*cs); } } void KisPaletteEditor::setGlobal() { Q_ASSERT(m_d->model); if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!m_d->model->colorSet()) { return; } KoColorSet *colorSet = m_d->model->colorSet(); QString saveLocation = m_d->rServer->saveLocation(); QString name = filenameFromPath(colorSet->filename()); QFileInfo fileInfo(saveLocation + name); colorSet->setFilename(fileInfo.filePath()); colorSet->setIsGlobal(true); m_d->rServer->removeFromBlacklist(colorSet); if (!colorSet->save()) { QMessageBox message; message.setWindowTitle(i18n("Saving palette failed")); message.setText(i18n("Failed to save global palette file. Please set it to non-global, or you will lose the file when you close Krita")); message.exec(); } uploadPaletteList(); } bool KisPaletteEditor::duplicateExistsFilename(const QString &filename, bool global) const { QString prefix; if (global) { prefix = m_d->rServer->saveLocation(); } Q_FOREACH (const KoResource *r, KoResourceServerProvider::instance()->paletteServer()->resources()) { if (r->filename() == prefix + filename && r != m_d->model->colorSet()) { return true; } } return false; } QString KisPaletteEditor::relativePathFromSaveLocation() const { return filenameFromPath(m_d->modified.filename); } void KisPaletteEditor::setNonGlobal() { Q_ASSERT(m_d->model); if (!m_d->view) { return; } if (!m_d->view->document()) { return; } if (!m_d->model->colorSet()) { return; } KoColorSet *colorSet = m_d->model->colorSet(); QString name = filenameFromPath(colorSet->filename()); QFile::remove(colorSet->filename()); if (duplicateExistsFilename(name, false)) { colorSet->setFilename(newPaletteFileName(false)); } else { colorSet->setFilename(name); } colorSet->setIsGlobal(false); uploadPaletteList(); } QString KisPaletteEditor::newPaletteFileName(bool isGlobal) { QSet nameSet; Q_FOREACH (const KoResource *r, m_d->rServer->resources()) { nameSet.insert(r->filename()); } KoColorSet tmpColorSet; QString result = "new_palette_"; if (isGlobal) { result = m_d->rServer->saveLocation() + result; } int i = 0; while (nameSet.contains(result + QString::number(i) + tmpColorSet.defaultFileExtension())) { i++; } result = result + QString::number(i) + tmpColorSet.defaultFileExtension(); return result; } QString KisPaletteEditor::newGroupName() const { int i = 1; QString groupname = i18nc("Default new group name", "New Group %1", QString::number(i)); while (m_d->modified.groups.contains(groupname)) { i++; groupname = i18nc("Default new group name", "New Group %1", QString::number(i)); } return groupname; } void KisPaletteEditor::uploadPaletteList() const { QList list; Q_FOREACH (KoResource * paletteResource, m_d->rServer->resources()) { KoColorSet *palette = static_cast(paletteResource); Q_ASSERT(palette); if (!palette->isGlobal()) { list.append(palette); } } m_d->view->document()->setPaletteList(list); } QString KisPaletteEditor::filenameFromPath(const QString &path) const { return QDir::fromNativeSeparators(path).section('/', -1, -1); } diff --git a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp index 9a199e7636..9fe9df4760 100644 --- a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp @@ -1,193 +1,193 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationFramesSaveDialog.h" #include #include #include #include "kis_properties_configuration.h" #include "KisMimeDatabase.h" #include #include #include struct KisAsyncAnimationFramesSaveDialog::Private { Private(KisImageSP _image, const KisTimeRange &_range, const QString &baseFilename, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration) : originalImage(_image), range(_range), sequenceNumberingOffset(_sequenceNumberingOffset), exportConfiguration(_exportConfiguration) { int baseLength = baseFilename.lastIndexOf("."); if (baseLength > -1) { filenamePrefix = baseFilename.left(baseLength); filenameSuffix = baseFilename.right(baseFilename.length() - baseLength); } else { filenamePrefix = baseFilename; } outputMimeType = KisMimeDatabase::mimeTypeForFile(baseFilename, false).toLatin1(); } KisImageSP originalImage; KisTimeRange range; QString filenamePrefix; QString filenameSuffix; QByteArray outputMimeType; int sequenceNumberingOffset; KisPropertiesConfigurationSP exportConfiguration; }; KisAsyncAnimationFramesSaveDialog::KisAsyncAnimationFramesSaveDialog(KisImageSP originalImage, const KisTimeRange &range, const QString &baseFilename, int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration) - : KisAsyncAnimationRenderDialogBase("Saving frames...", originalImage, 0), + : KisAsyncAnimationRenderDialogBase(i18n("Saving frames..."), originalImage, 0), m_d(new Private(originalImage, range, baseFilename, sequenceNumberingOffset, exportConfiguration)) { } KisAsyncAnimationFramesSaveDialog::~KisAsyncAnimationFramesSaveDialog() { } KisAsyncAnimationRenderDialogBase::Result KisAsyncAnimationFramesSaveDialog::regenerateRange(KisViewManager *viewManager) { QFileInfo info(savedFilesMaskWildcard()); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); QStringList filesList = dir.entryList({ info.fileName() }); if (!filesList.isEmpty()) { if (batchMode()) { return RenderFailed; } QStringList filesWithinRange; const int numberOfDigits = 4; Q_FOREACH(const QString &filename, filesList) { // Counting based on suffix, since prefix may include the path while filename doesn't int digitsPosition = filename.length() - m_d->filenameSuffix.length() - numberOfDigits; int fileNumber = filename.midRef(digitsPosition, numberOfDigits).toInt(); auto frameNumber = fileNumber - m_d->sequenceNumberingOffset; if (m_d->range.contains(frameNumber)) { filesWithinRange.append(filename); } } filesList = filesWithinRange; QStringList truncatedList = filesList; while (truncatedList.size() > 3) { truncatedList.takeLast(); } QString exampleFiles = truncatedList.join(", "); if (truncatedList.size() != filesList.size()) { exampleFiles += QString(", ..."); } QMessageBox::StandardButton result = QMessageBox::warning(0, i18n("Delete old frames?"), i18n("Frames with the same naming " "scheme exist in the destination " "directory. They are going to be " "deleted, continue?\n\n" "Directory: %1\n" "Files: %2", info.absolutePath(), exampleFiles), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (result == QMessageBox::Yes) { Q_FOREACH (const QString &file, filesList) { if (!dir.remove(file)) { QMessageBox::critical(0, i18n("Failed to delete"), i18n("Failed to delete an old frame file:\n\n" "%1\n\n" "Rendering cancelled.", dir.absoluteFilePath(file))); return RenderFailed; } } } else { return RenderCancelled; } } return KisAsyncAnimationRenderDialogBase::regenerateRange(viewManager); } QList KisAsyncAnimationFramesSaveDialog::calcDirtyFrames() const { // TODO: optimize! QList result; for (int i = m_d->range.start(); i <= m_d->range.end(); i++) { result.append(i); } return result; } KisAsyncAnimationRendererBase *KisAsyncAnimationFramesSaveDialog::createRenderer(KisImageSP image) { return new KisAsyncAnimationFramesSavingRenderer(image, m_d->filenamePrefix, m_d->filenameSuffix, m_d->outputMimeType, m_d->range, m_d->sequenceNumberingOffset, m_d->exportConfiguration); } void KisAsyncAnimationFramesSaveDialog::initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) { Q_UNUSED(renderer); Q_UNUSED(image); Q_UNUSED(frame); } QString KisAsyncAnimationFramesSaveDialog::savedFilesMask() const { return m_d->filenamePrefix + "%04d" + m_d->filenameSuffix; } QString KisAsyncAnimationFramesSaveDialog::savedFilesMaskWildcard() const { return m_d->filenamePrefix + "????" + m_d->filenameSuffix; } diff --git a/libs/ui/dialogs/KisSessionManagerDialog.cpp b/libs/ui/dialogs/KisSessionManagerDialog.cpp index 6382254a2a..0412c01417 100644 --- a/libs/ui/dialogs/KisSessionManagerDialog.cpp +++ b/libs/ui/dialogs/KisSessionManagerDialog.cpp @@ -1,150 +1,151 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "KisSessionManagerDialog.h" KisSessionManagerDialog::KisSessionManagerDialog(QWidget *parent) : QDialog(parent) { setupUi(this); updateSessionList(); connect(btnNew, SIGNAL(clicked()), this, SLOT(slotNewSession())); connect(btnRename, SIGNAL(clicked()), this, SLOT(slotRenameSession())); connect(btnSwitchTo, SIGNAL(clicked()), this, SLOT(slotSwitchSession())); connect(btnDelete, SIGNAL(clicked()), this, SLOT(slotDeleteSession())); connect(btnClose, SIGNAL(clicked()), this, SLOT(slotClose())); connect(lstSessions, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotSessionDoubleClicked(QListWidgetItem*))); } void KisSessionManagerDialog::updateSessionList() { KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); lstSessions->clear(); Q_FOREACH(KisSessionResource *session, server->resources()) { lstSessions->addItem(session->name()); } } void KisSessionManagerDialog::slotNewSession() { QString name = QInputDialog::getText(this, i18n("Create session"), i18n("Session name:"), QLineEdit::Normal ); if (name.isNull() || name.isEmpty()) return; auto *session = new KisSessionResource(QString()); KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); QString saveLocation = server->saveLocation(); QFileInfo fileInfo(saveLocation + name + session->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + session->defaultFileExtension()); i++; } session->setFilename(fileInfo.filePath()); session->setName(name); session->storeCurrentWindows(); server->addResource(session); KisPart::instance()->setCurrentSession(session); updateSessionList(); } void KisSessionManagerDialog::slotRenameSession() { QString name = QInputDialog::getText(this, i18n("Rename session"), i18n("New name:"), QLineEdit::Normal ); if (name.isNull() || name.isEmpty()) return; KisSessionResource *session = getSelectedSession(); if (!session) return; session->setName(name); session->save(); updateSessionList(); } void KisSessionManagerDialog::slotSessionDoubleClicked(QListWidgetItem* /*item*/) { slotSwitchSession(); slotClose(); } void KisSessionManagerDialog::slotSwitchSession() { KisSessionResource *session = getSelectedSession(); if (session) { bool closed = KisPart::instance()->closeSession(true); if (closed) { session->restore(); } } } KisSessionResource *KisSessionManagerDialog::getSelectedSession() const { QListWidgetItem *item = lstSessions->currentItem(); if (item) { KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); return server->resourceByName(item->text()); } return nullptr; } void KisSessionManagerDialog::slotDeleteSession() { KisSessionResource *session = getSelectedSession(); if (!session) return; if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), QString(i18n("Permanently delete session %1?", session->name())), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + KisPart::instance()->setCurrentSession(0); const QString filename = session->filename(); KoResourceServer *server = KisResourceServerProvider::instance()->sessionServer(); server->removeResourceFromServer(session); QFile file(filename); file.remove(); updateSessionList(); } } void KisSessionManagerDialog::slotClose() { hide(); } diff --git a/libs/ui/dialogs/kis_about_application.cpp b/libs/ui/dialogs/kis_about_application.cpp index 2403e5874a..456c0fec9a 100644 --- a/libs/ui/dialogs/kis_about_application.cpp +++ b/libs/ui/dialogs/kis_about_application.cpp @@ -1,215 +1,214 @@ /* * Copyright (c) 2014 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_about_application.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../krita/data/splash/splash_screen.xpm" #include "../../krita/data/splash/splash_holidays.xpm" #include "../../krita/data/splash/splash_screen_x2.xpm" #include "../../krita/data/splash/splash_holidays_x2.xpm" #include "kis_splash_screen.h" KisAboutApplication::KisAboutApplication(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("About Krita")); QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->setMargin(0); QTabWidget *wdgTab = new QTabWidget; vlayout->addWidget(wdgTab); KisSplashScreen *splash = 0; QDate currentDate = QDate::currentDate(); if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(qApp->applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(qApp->applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } splash->setWindowFlags(Qt::Widget); splash->displayLinks(true); splash->setFixedSize(splash->sizeHint()); wdgTab->addTab(splash, i18n("About")); setMinimumSize(wdgTab->sizeHint()); QTextEdit *lblAuthors = new QTextEdit(); lblAuthors->setReadOnly(true); QString authors = i18n("" "" "" "

Created By

" "

"); QFile fileDevelopers(":/developers.txt"); Q_ASSERT(fileDevelopers.exists()); fileDevelopers.open(QIODevice::ReadOnly); Q_FOREACH (const QByteArray &author, fileDevelopers.readAll().split('\n')) { authors.append(QString::fromUtf8(author)); authors.append(", "); } authors.chop(2); authors.append(".

"); lblAuthors->setText(authors); wdgTab->addTab(lblAuthors, i18n("Authors")); QTextEdit *lblKickstarter = new QTextEdit(); lblKickstarter->setReadOnly(true); QString backers = i18n("" "" "" "

Backed By

" "

"); QFile fileBackers(":/backers.txt"); Q_ASSERT(fileBackers.exists()); - fileBackers.open(QIODevice::ReadOnly); - Q_FOREACH (const QByteArray &backer, fileBackers.readAll().split('\n')) { - backers.append(QString::fromUtf8(backer)); - backers.append(", "); - } + fileBackers.open(QIODevice::ReadOnly | QIODevice::Text); + QTextStream backersText(&fileBackers); + backersText.setCodec("UTF-8"); + backers.append(backersText.readAll().replace("\n", ", ")); backers.chop(2); backers.append(i18n(".

Thanks! You were all awesome!

")); lblKickstarter->setText(backers); wdgTab->addTab(lblKickstarter, i18n("Backers")); QTextEdit *lblCredits = new QTextEdit(); lblCredits->setReadOnly(true); QString credits = i18n("" "" "" "

Thanks To

" "

"); QFile fileCredits(":/credits.txt"); Q_ASSERT(fileCredits.exists()); fileCredits.open(QIODevice::ReadOnly); Q_FOREACH (const QString &credit, QString::fromUtf8(fileCredits.readAll()).split('\n', QString::SkipEmptyParts)) { if (credit.contains(":")) { QList creditSplit = credit.split(':'); credits.append(creditSplit.at(0)); credits.append(" (" + creditSplit.at(1) + ")"); credits.append(", "); } } credits.chop(2); credits.append(i18n(".

For supporting Krita development with advice, icons, brush sets and more.

")); lblCredits->setText(credits); wdgTab->addTab(lblCredits, i18n("Also Thanks To")); QTextEdit *lblLicense = new QTextEdit(); lblLicense->setReadOnly(true); QString license = i18n("" "" "" "

Your Rights

" "

Krita is released under the GNU General Public License (version 3 or any later version).

" "

This license grants people a number of freedoms:

" "
    " "
  • You are free to use Krita, for any purpose
  • " "
  • You are free to distribute Krita
  • " "
  • You can study how Krita works and change it
  • " "
  • You can distribute changed versions of Krita
  • " "
" "

The Krita Foundation and its projects on krita.org are committed to preserving Krita as free software.

" "

Your artwork

" "

What you create with Krita is your sole property. All your artwork is free for you to use as you like.

" "

That means that Krita can be used commercially, for any purpose. There are no restrictions whatsoever.

" "

Krita’s GNU GPL license guarantees you this freedom. Nobody is ever permitted to take it away, in contrast " "to trial or educational versions of commercial software that will forbid your work in commercial situations.

" "

");
 
     QFile licenseFile(":/LICENSE");
     Q_ASSERT(licenseFile.exists());
     licenseFile.open(QIODevice::ReadOnly);
     QByteArray ba = licenseFile.readAll();
     license.append(QString::fromUtf8(ba));
     license.append("
"); lblLicense->setText(license); wdgTab->addTab(lblLicense, i18n("License")); QTextBrowser *lblThirdParty = new QTextBrowser(); lblThirdParty->setOpenExternalLinks(true); QFile thirdPartyFile(":/libraries.txt"); if (thirdPartyFile.open(QIODevice::ReadOnly)) { ba = thirdPartyFile.readAll(); QString thirdPartyHtml = i18n("" "" "" "

Third-party Libraries used by Krita

" "

Krita is built on the following free software libraries:

    "); Q_FOREACH(const QString &lib, QString::fromUtf8(ba).split('\n')) { if (!lib.startsWith("#")) { QStringList parts = lib.split(','); if (parts.size() >= 3) { thirdPartyHtml.append(QString("
  • %1: %3
  • ").arg(parts[0], parts[1], parts[2])); } } } thirdPartyHtml.append("

      "); lblThirdParty->setText(thirdPartyHtml); } wdgTab->addTab(lblThirdParty, i18n("Third-party libraries")); QPushButton *bnClose = new QPushButton(i18n("Close")); connect(bnClose, SIGNAL(clicked()), SLOT(close())); QHBoxLayout *hlayout = new QHBoxLayout; hlayout->setMargin(10); hlayout->addStretch(10); hlayout->addWidget(bnClose); vlayout->addLayout(hlayout); } diff --git a/libs/ui/dialogs/kis_delayed_save_dialog.cpp b/libs/ui/dialogs/kis_delayed_save_dialog.cpp index e955121fa1..c7290649f8 100644 --- a/libs/ui/dialogs/kis_delayed_save_dialog.cpp +++ b/libs/ui/dialogs/kis_delayed_save_dialog.cpp @@ -1,127 +1,127 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_delayed_save_dialog.h" #include "ui_kis_delayed_save_dialog.h" #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_composite_progress_proxy.h" struct KisDelayedSaveDialog::Private { Private(KisImageSP _image, int _busyWait, Type _type) : image(_image), busyWait(_busyWait), type(_type) {} KisImageSP image; QTimer updateTimer; int busyWait; bool checkImageIdle() { const bool allowLocked = type != SaveDialog; return image->isIdle(allowLocked); } private: Type type; }; KisDelayedSaveDialog::KisDelayedSaveDialog(KisImageSP image, Type type, int busyWait, QWidget *parent) : QDialog(parent), ui(new Ui::KisDelayedSaveDialog), m_d(new Private(image, busyWait, type)) { KIS_ASSERT_RECOVER_NOOP(image); ui->setupUi(this); if (type == SaveDialog) { connect(ui->bnDontWait, SIGNAL(clicked()), SLOT(slotIgnoreRequested())); connect(ui->bnCancel, SIGNAL(clicked()), SLOT(slotCancelRequested())); } else { ui->bnDontSave->setText(i18n("Cancel")); ui->bnDontWait->setVisible(false); ui->bnCancel->setVisible(false); if (type == ForcedDialog) { ui->bnDontSave->setVisible(false); } } connect(ui->bnDontSave, SIGNAL(clicked()), SLOT(reject())); connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(slotTimerTimeout())); m_d->image->compositeProgressProxy()->addProxy(ui->progressBar); } KisDelayedSaveDialog::~KisDelayedSaveDialog() { m_d->image->compositeProgressProxy()->removeProxy(ui->progressBar); delete ui; } void KisDelayedSaveDialog::blockIfImageIsBusy() { if (m_d->checkImageIdle()) { setResult(Accepted); return; } m_d->image->requestStrokeEnd(); QElapsedTimer t; t.start(); while (t.elapsed() < m_d->busyWait) { - QApplication::processEvents(); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); if (m_d->checkImageIdle()) { setResult(Accepted); return; } QThread::yieldCurrentThread(); } m_d->updateTimer.start(200); exec(); m_d->updateTimer.stop(); } void KisDelayedSaveDialog::slotTimerTimeout() { if (m_d->checkImageIdle()) { accept(); } } void KisDelayedSaveDialog::slotCancelRequested() { m_d->image->requestStrokeCancellation(); } void KisDelayedSaveDialog::slotIgnoreRequested() { done(Ignored); } diff --git a/libs/ui/input/kis_input_profile_manager.cpp b/libs/ui/input/kis_input_profile_manager.cpp index 7b863bf4bd..34b612d20e 100644 --- a/libs/ui/input/kis_input_profile_manager.cpp +++ b/libs/ui/input/kis_input_profile_manager.cpp @@ -1,379 +1,379 @@ /* * This file is part of the KDE project * Copyright (C) 2013 Arjen Hiemstra * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_profile_manager.h" #include "kis_input_profile.h" #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_alternate_invocation_action.h" #include "kis_change_primary_setting_action.h" #include "kis_pan_action.h" #include "kis_rotate_canvas_action.h" #include "kis_show_palette_action.h" #include "kis_tool_invocation_action.h" #include "kis_zoom_action.h" #include "kis_shortcut_configuration.h" #include "kis_select_layer_action.h" #include "kis_gamma_exposure_action.h" #include "kis_change_frame_action.h" #include "kis_zoom_and_rotate_action.h" -#define PROFILE_VERSION 3 +#define PROFILE_VERSION 4 class Q_DECL_HIDDEN KisInputProfileManager::Private { public: Private() : currentProfile(0) { } void createActions(); QString profileFileName(const QString &profileName); KisInputProfile *currentProfile; QMap profiles; QList actions; }; Q_GLOBAL_STATIC(KisInputProfileManager, inputProfileManager) KisInputProfileManager *KisInputProfileManager::instance() { return inputProfileManager; } QList< KisInputProfile * > KisInputProfileManager::profiles() const { return d->profiles.values(); } QStringList KisInputProfileManager::profileNames() const { return d->profiles.keys(); } KisInputProfile *KisInputProfileManager::profile(const QString &name) const { if (d->profiles.contains(name)) { return d->profiles.value(name); } return 0; } KisInputProfile *KisInputProfileManager::currentProfile() const { return d->currentProfile; } void KisInputProfileManager::setCurrentProfile(KisInputProfile *profile) { if (profile && profile != d->currentProfile) { d->currentProfile = profile; emit currentProfileChanged(); } } KisInputProfile *KisInputProfileManager::addProfile(const QString &name) { if (d->profiles.contains(name)) { return d->profiles.value(name); } KisInputProfile *profile = new KisInputProfile(this); profile->setName(name); d->profiles.insert(name, profile); emit profilesChanged(); return profile; } void KisInputProfileManager::removeProfile(const QString &name) { if (d->profiles.contains(name)) { QString currentProfileName = d->currentProfile->name(); delete d->profiles.value(name); d->profiles.remove(name); //Delete the settings file for the removed profile, if it exists QDir userDir(KoResourcePaths::saveLocation("data", "input/")); if (userDir.exists(d->profileFileName(name))) { userDir.remove(d->profileFileName(name)); } if (currentProfileName == name) { d->currentProfile = d->profiles.begin().value(); emit currentProfileChanged(); } emit profilesChanged(); } } bool KisInputProfileManager::renameProfile(const QString &oldName, const QString &newName) { if (!d->profiles.contains(oldName)) { return false; } KisInputProfile *profile = d->profiles.value(oldName); d->profiles.remove(oldName); profile->setName(newName); d->profiles.insert(newName, profile); emit profilesChanged(); return true; } void KisInputProfileManager::duplicateProfile(const QString &name, const QString &newName) { if (!d->profiles.contains(name) || d->profiles.contains(newName)) { return; } KisInputProfile *newProfile = new KisInputProfile(this); newProfile->setName(newName); d->profiles.insert(newName, newProfile); KisInputProfile *profile = d->profiles.value(name); QList shortcuts = profile->allShortcuts(); Q_FOREACH(KisShortcutConfiguration * shortcut, shortcuts) { newProfile->addShortcut(new KisShortcutConfiguration(*shortcut)); } emit profilesChanged(); } QList< KisAbstractInputAction * > KisInputProfileManager::actions() { return d->actions; } struct ProfileEntry { QString name; QString fullpath; int version; }; void KisInputProfileManager::loadProfiles() { //Remove any profiles that already exist d->currentProfile = 0; qDeleteAll(d->profiles); d->profiles.clear(); //Look up all profiles (this includes those installed to $prefix as well as the user's local data dir) QStringList profiles = KoResourcePaths::findAllResources("data", "input/*.profile", KoResourcePaths::Recursive); dbgKrita << "profiles" << profiles; QMap > profileEntries; // Get only valid entries... Q_FOREACH(const QString & p, profiles) { ProfileEntry entry; entry.fullpath = p; KConfig config(p, KConfig::SimpleConfig); if (!config.hasGroup("General") || !config.group("General").hasKey("name") || !config.group("General").hasKey("version")) { //Skip if we don't have the proper settings. continue; } // Only entries of exactly the right version can be considered entry.version = config.group("General").readEntry("version", 0); if (entry.version != PROFILE_VERSION) { continue; } entry.name = config.group("General").readEntry("name"); if (!profileEntries.contains(entry.name)) { profileEntries[entry.name] = QList(); } if (p.contains(".kde") || p.contains(".krita")) { // It's the user define one, drop the others profileEntries[entry.name].clear(); profileEntries[entry.name].append(entry); break; } else { profileEntries[entry.name].append(entry); } } QStringList profilePaths; Q_FOREACH(const QString & profileName, profileEntries.keys()) { if (profileEntries[profileName].isEmpty()) { continue; } // we have one or more entries for this profile name. We'll take the first, // because that's the most local one. ProfileEntry entry = profileEntries[profileName].first(); QString path(QFileInfo(entry.fullpath).dir().absolutePath()); if (!profilePaths.contains(path)) { profilePaths.append(path); } KConfig config(entry.fullpath, KConfig::SimpleConfig); KisInputProfile *newProfile = addProfile(entry.name); Q_FOREACH(KisAbstractInputAction * action, d->actions) { if (!config.hasGroup(action->id())) { continue; } KConfigGroup grp = config.group(action->id()); //Read the settings for the action and create the appropriate shortcuts. Q_FOREACH(const QString & entry, grp.entryMap()) { KisShortcutConfiguration *shortcut = new KisShortcutConfiguration; shortcut->setAction(action); if (shortcut->unserialize(entry)) { newProfile->addShortcut(shortcut); } else { delete shortcut; } } } } // QString profilePathsStr(profilePaths.join("' AND '")); // qDebug() << "input profiles were read from '" << qUtf8Printable(profilePathsStr) << "'."; KisConfig cfg(true); QString currentProfile = cfg.currentInputProfile(); if (d->profiles.size() > 0) { if (currentProfile.isEmpty() || !d->profiles.contains(currentProfile)) { d->currentProfile = d->profiles.begin().value(); } else { d->currentProfile = d->profiles.value(currentProfile); } } if (d->currentProfile) { emit currentProfileChanged(); } } void KisInputProfileManager::saveProfiles() { QString storagePath = KoResourcePaths::saveLocation("data", "input/", true); Q_FOREACH(KisInputProfile * p, d->profiles) { QString fileName = d->profileFileName(p->name()); KConfig config(storagePath + fileName, KConfig::SimpleConfig); config.group("General").writeEntry("name", p->name()); config.group("General").writeEntry("version", PROFILE_VERSION); Q_FOREACH(KisAbstractInputAction * action, d->actions) { KConfigGroup grp = config.group(action->id()); grp.deleteGroup(); //Clear the group of any existing shortcuts. int index = 0; QList shortcuts = p->shortcutsForAction(action); Q_FOREACH(KisShortcutConfiguration * shortcut, shortcuts) { grp.writeEntry(QString("%1").arg(index++), shortcut->serialize()); } } config.sync(); } KisConfig config(false); config.setCurrentInputProfile(d->currentProfile->name()); //Force a reload of the current profile in input manager and whatever else uses the profile. emit currentProfileChanged(); } void KisInputProfileManager::resetAll() { QString kdeHome = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QStringList profiles = KoResourcePaths::findAllResources("data", "input/*", KoResourcePaths::Recursive); Q_FOREACH (const QString &profile, profiles) { if(profile.contains(kdeHome)) { //This is a local file, remove it. QFile::remove(profile); } } //Load the profiles again, this should now only load those shipped with Krita. loadProfiles(); emit profilesChanged(); } KisInputProfileManager::KisInputProfileManager(QObject *parent) : QObject(parent), d(new Private()) { d->createActions(); } KisInputProfileManager::~KisInputProfileManager() { qDeleteAll(d->profiles); qDeleteAll(d->actions); delete d; } void KisInputProfileManager::Private::createActions() { //TODO: Make this plugin based //Note that the ordering here determines how things show up in the UI actions.append(new KisToolInvocationAction()); actions.append(new KisAlternateInvocationAction()); actions.append(new KisChangePrimarySettingAction()); actions.append(new KisPanAction()); actions.append(new KisRotateCanvasAction()); actions.append(new KisZoomAction()); actions.append(new KisShowPaletteAction()); actions.append(new KisSelectLayerAction()); actions.append(new KisGammaExposureAction()); actions.append(new KisChangeFrameAction()); actions.append(new KisZoomAndRotateAction()); } QString KisInputProfileManager::Private::profileFileName(const QString &profileName) { return profileName.toLower().remove(QRegExp("[^a-z0-9]")).append(".profile"); } diff --git a/libs/ui/input/kis_pan_action.h b/libs/ui/input/kis_pan_action.h index 20d251167b..1f7f12be10 100644 --- a/libs/ui/input/kis_pan_action.h +++ b/libs/ui/input/kis_pan_action.h @@ -1,67 +1,67 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAN_ACTION_H #define KIS_PAN_ACTION_H #include "kis_abstract_input_action.h" /** * \brief Pan Canvas implementation of KisAbstractInputAction. * * The Pan Canvas action pans the canvas. */ class KisPanAction : public KisAbstractInputAction { public: /** * The different behaviours for this action. */ enum Shortcut { PanModeShortcut, ///< Toggle the pan mode. PanLeftShortcut, ///< Pan left by a fixed amount. PanRightShortcut, ///< Pan right by a fixed amount. PanUpShortcut, ///< Pan up by a fixed amount. PanDownShortcut ///< Pan down by a fixed amount. }; explicit KisPanAction(); ~KisPanAction() override; int priority() const override; void activate(int shortcut) override; void deactivate(int shortcut) override; void begin(int shortcut, QEvent *event) override; void end(QEvent *event) override; void inputEvent(QEvent* event) override; void cursorMovedAbsolute(const QPointF &lastPos, const QPointF &pos) override; bool isShortcutRequired(int shortcut) const override; KisInputActionGroup inputActionGroup(int shortcut) const override; - bool supportsHiResInputEvents() const; + bool supportsHiResInputEvents() const override; private: class Private; Private * const d; }; #endif // KIS_PAN_ACTION_H diff --git a/libs/ui/input/kis_rotate_canvas_action.h b/libs/ui/input/kis_rotate_canvas_action.h index d3e949b17b..9fb37077a7 100644 --- a/libs/ui/input/kis_rotate_canvas_action.h +++ b/libs/ui/input/kis_rotate_canvas_action.h @@ -1,63 +1,63 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ROTATE_CANVAS_ACTION_H #define KIS_ROTATE_CANVAS_ACTION_H #include "kis_abstract_input_action.h" /** * \brief Rotate Canvas implementation of KisAbstractInputAction. * * The Rotate Canvas action rotates the canvas. */ class KisRotateCanvasAction : public KisAbstractInputAction { public: /** * The different behaviours for this action. */ enum Shortcut { RotateModeShortcut, ///< Toggle Rotate mode. DiscreteRotateModeShortcut, ///< Toggle Discrete Rotate mode. RotateLeftShortcut, ///< Rotate left by a fixed amount. RotateRightShortcut, ///< Rotate right by a fixed amount. RotateResetShortcut ///< Reset the rotation to 0. }; explicit KisRotateCanvasAction(); ~KisRotateCanvasAction() override; int priority() const override; void activate(int shortcut) override; void deactivate(int shortcut) override; void begin(int shortcut, QEvent *event) override; - void cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos); + void cursorMovedAbsolute(const QPointF &startPos, const QPointF &pos) override; void inputEvent(QEvent* event) override; KisInputActionGroup inputActionGroup(int shortcut) const override; - bool supportsHiResInputEvents() const; + bool supportsHiResInputEvents() const override; private: class Private; Private * const d; }; #endif // KIS_ROTATE_CANVAS_ACTION_H diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp index 183149d8e7..8510899e2d 100644 --- a/libs/ui/input/kis_shortcut_matcher.cpp +++ b/libs/ui/input/kis_shortcut_matcher.cpp @@ -1,690 +1,829 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shortcut_matcher.h" #include #include #include #include "kis_assert.h" #include "kis_abstract_input_action.h" #include "kis_stroke_shortcut.h" #include "kis_touch_shortcut.h" #include "kis_native_gesture_shortcut.h" #include "kis_config.h" +//#define DEBUG_MATCHER #ifdef DEBUG_MATCHER #include -#define DEBUG_ACTION(text) dbgInput << __FUNCTION__ << "-" << text; -#define DEBUG_SHORTCUT(text, shortcut) dbgInput << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name(); -#define DEBUG_KEY(text) dbgInput << __FUNCTION__ << "-" << text << "keys:" << m_d->keys; -#define DEBUG_BUTTON_ACTION(text, button) dbgInput << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys; -#define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInput << __FUNCTION__ << "-" << text << "type:" << event->type();} +#define DEBUG_ACTION(text) qDebug() << __FUNCTION__ << "-" << text; +#define DEBUG_SHORTCUT(text, shortcut) qDebug() << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name(); +#define DEBUG_KEY(text) qDebug() << __FUNCTION__ << "-" << text << "keys:" << m_d->keys; +#define DEBUG_BUTTON_ACTION(text, button) qDebug() << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys; +#define DEBUG_EVENT_ACTION(text, event) if (event) {qDebug() << __FUNCTION__ << "-" << text << "type:" << event->type();} #else #define DEBUG_ACTION(text) #define DEBUG_KEY(text) #define DEBUG_SHORTCUT(text, shortcut) #define DEBUG_BUTTON_ACTION(text, button) #define DEBUG_EVENT_ACTION(text, event) #endif class Q_DECL_HIDDEN KisShortcutMatcher::Private { public: Private() : runningShortcut(0) , readyShortcut(0) , touchShortcut(0) , nativeGestureShortcut(0) , actionGroupMask([] () { return AllActionGroup; }) , suppressAllActions(false) , cursorEntered(false) , usingTouch(false) , usingNativeGesture(false) {} ~Private() { qDeleteAll(singleActionShortcuts); qDeleteAll(strokeShortcuts); qDeleteAll(touchShortcuts); } QList singleActionShortcuts; QList strokeShortcuts; QList touchShortcuts; QList nativeGestureShortcuts; QSet keys; // Model of currently pressed keys QSet buttons; // Model of currently pressed buttons KisStrokeShortcut *runningShortcut; KisStrokeShortcut *readyShortcut; QList candidateShortcuts; KisTouchShortcut *touchShortcut; KisNativeGestureShortcut *nativeGestureShortcut; std::function actionGroupMask; bool suppressAllActions; bool cursorEntered; bool usingTouch; bool usingNativeGesture; + int recursiveCounter = 0; + int brokenByRecursion = 0; + + + struct RecursionNotifier { + RecursionNotifier(KisShortcutMatcher *_q) + : q(_q) + { + q->m_d->recursiveCounter++; + q->m_d->brokenByRecursion++; + } + + ~RecursionNotifier() { + q->m_d->recursiveCounter--; + } + + bool isInRecursion() const { + return q->m_d->recursiveCounter > 1; + } + + KisShortcutMatcher *q; + }; + + struct RecursionGuard { + RecursionGuard(KisShortcutMatcher *_q) + : q(_q) + { + q->m_d->brokenByRecursion = 0; + } + + ~RecursionGuard() { + } + + bool brokenByRecursion() const { + return q->m_d->brokenByRecursion > 0; + } + + KisShortcutMatcher *q; + }; + inline bool actionsSuppressed() const { return (suppressAllActions || !cursorEntered) && KisConfig(true).disableTouchOnCanvas(); } inline bool actionsSuppressedIgnoreFocus() const { return suppressAllActions; } // only for touch events with touchPoints count >= 2 inline bool isUsingTouch() const { return usingTouch || usingNativeGesture; } }; KisShortcutMatcher::KisShortcutMatcher() : m_d(new Private) {} KisShortcutMatcher::~KisShortcutMatcher() { delete m_d; } bool KisShortcutMatcher::hasRunningShortcut() const { return m_d->runningShortcut; } void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut) { m_d->singleActionShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut) { m_d->strokeShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut ) { m_d->touchShortcuts.append(shortcut); } void KisShortcutMatcher::addShortcut(KisNativeGestureShortcut *shortcut) { m_d->nativeGestureShortcuts.append(shortcut); } bool KisShortcutMatcher::supportsHiResInputEvents() { return m_d->runningShortcut && m_d->runningShortcut->action() && m_d->runningShortcut->action()->supportsHiResInputEvents(); } bool KisShortcutMatcher::keyPressed(Qt::Key key) { + Private::RecursionNotifier notifier(this); + bool retval = false; if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); } - if (!m_d->runningShortcut) { + if (!m_d->runningShortcut && !notifier.isInRecursion()) { retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys); } m_d->keys.insert(key); DEBUG_KEY("Pressed"); - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key) { + Private::RecursionNotifier notifier(this); + + bool retval = false; if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); } - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { // Autorepeated key should not be included in the shortcut QSet filteredKeys = m_d->keys; filteredKeys.remove(key); retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys); } return retval; } bool KisShortcutMatcher::keyReleased(Qt::Key key) { + Private::RecursionNotifier notifier(this); + if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed"); else m_d->keys.remove(key); - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return false; } bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event) { + Private::RecursionNotifier notifier(this); DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); } - if (!hasRunningShortcut()) { + if (!m_d->runningShortcut && !notifier.isInRecursion()) { prepareReadyShortcuts(); retval = tryRunReadyShortcut(button, event); } m_d->buttons.insert(button); - if (!hasRunningShortcut()) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event) { + Private::RecursionNotifier notifier(this); DEBUG_BUTTON_ACTION("entered", button); bool retval = false; if (m_d->isUsingTouch()) { return retval; } - if (m_d->runningShortcut && !m_d->readyShortcut) { + if (m_d->runningShortcut) { + KIS_SAFE_ASSERT_RECOVER_NOOP(!notifier.isInRecursion()); + retval = tryEndRunningShortcut(button, event); DEBUG_BUTTON_ACTION("ended", button); } if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed"); else m_d->buttons.remove(button); - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } return retval; } bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { - if (m_d->runningShortcut || m_d->isUsingTouch()) { + Private::RecursionNotifier notifier(this); + + + if (m_d->runningShortcut || m_d->isUsingTouch() || notifier.isInRecursion()) { DEBUG_ACTION("Wheel event canceled."); return false; } return tryRunWheelShortcut(wheelAction, event); } bool KisShortcutMatcher::pointerMoved(QEvent *event) { - if (m_d->isUsingTouch() || !m_d->runningShortcut) { + Private::RecursionNotifier notifier(this); + + + if (m_d->isUsingTouch() || !m_d->runningShortcut || notifier.isInRecursion()) { return false; } m_d->runningShortcut->action()->inputEvent(event); return true; } void KisShortcutMatcher::enterEvent() { + Private::RecursionNotifier notifier(this); + m_d->cursorEntered = true; - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::leaveEvent() { + Private::RecursionNotifier notifier(this); + m_d->cursorEntered = false; - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event ) { Q_UNUSED(event) - return true; + + Private::RecursionNotifier notifier(this); + + return !notifier.isInRecursion(); } bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event ) { bool retval = false; if (m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) { retval = tryEndTouchShortcut( event ); } if (!m_d->touchShortcut ) { retval = tryRunTouchShortcut( event ); } else { m_d->touchShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event ) { m_d->usingTouch = false; // we need to say we are done because qt will not send further event // we should try and end the shortcut too (it might be that there is none? (sketch)) if (tryEndTouchShortcut(event)) { return true; } return false; } bool KisShortcutMatcher::nativeGestureBeginEvent(QNativeGestureEvent *event) { Q_UNUSED(event) - return true; + + Private::RecursionNotifier notifier(this); + + return !notifier.isInRecursion(); } bool KisShortcutMatcher::nativeGestureEvent(QNativeGestureEvent *event) { bool retval = false; if ( !m_d->nativeGestureShortcut ) { retval = tryRunNativeGestureShortcut( event ); } else { m_d->nativeGestureShortcut->action()->inputEvent( event ); retval = true; } return retval; } bool KisShortcutMatcher::nativeGestureEndEvent(QNativeGestureEvent *event) { if ( m_d->nativeGestureShortcut && !m_d->nativeGestureShortcut->match( event ) ) { tryEndNativeGestureShortcut( event ); } m_d->usingNativeGesture = false; return true; } Qt::MouseButtons listToFlags(const QList &list) { Qt::MouseButtons flags; Q_FOREACH (Qt::MouseButton b, list) { flags |= b; } return flags; } void KisShortcutMatcher::reinitialize() { + Private::RecursionNotifier notifier(this); + + reset("reinitialize"); - if (!m_d->runningShortcut) { + + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } } void KisShortcutMatcher::recoveryModifiersWithoutFocus(const QVector &keys) { + Private::RecursionNotifier notifier(this); + + Q_FOREACH (Qt::Key key, m_d->keys) { if (!keys.contains(key)) { keyReleased(key); } } Q_FOREACH (Qt::Key key, keys) { if (!m_d->keys.contains(key)) { keyPressed(key); } } - if (!m_d->runningShortcut) { + if (notifier.isInRecursion()) { + forceDeactivateAllActions(); + } else if (!m_d->runningShortcut) { prepareReadyShortcuts(); tryActivateReadyShortcut(); } DEBUG_ACTION("recoverySyncModifiers"); } void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos) { if (m_d->runningShortcut) { forceEndRunningShortcut(localPos); } } void KisShortcutMatcher::reset() { m_d->keys.clear(); m_d->buttons.clear(); DEBUG_ACTION("reset!"); } void KisShortcutMatcher::reset(QString msg) { m_d->keys.clear(); m_d->buttons.clear(); Q_UNUSED(msg); DEBUG_ACTION(msg); } void KisShortcutMatcher::suppressAllActions(bool value) { m_d->suppressAllActions = value; } void KisShortcutMatcher::clearShortcuts() { reset("Clearing shortcuts"); qDeleteAll(m_d->singleActionShortcuts); m_d->singleActionShortcuts.clear(); qDeleteAll(m_d->strokeShortcuts); qDeleteAll(m_d->touchShortcuts); m_d->strokeShortcuts.clear(); m_d->candidateShortcuts.clear(); m_d->touchShortcuts.clear(); m_d->runningShortcut = 0; m_d->readyShortcut = 0; } void KisShortcutMatcher::setInputActionGroupsMaskCallback(std::function func) { m_d->actionGroupMask = func; } bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event) { return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys); } // Note: sometimes event can be zero!! template bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState) { if (m_d->actionsSuppressedIgnoreFocus()) { DEBUG_EVENT_ACTION("Event suppressed", event) return false; } KisSingleActionShortcut *goodCandidate = 0; Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) { if(s->isAvailable(m_d->actionGroupMask()) && s->match(keysState, param) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { - DEBUG_EVENT_ACTION("Beginning action for event", event) + DEBUG_EVENT_ACTION("Beginning action for event", event); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); goodCandidate->action()->end(0); } else { DEBUG_EVENT_ACTION("Could not match a candidate for event", event) } return goodCandidate; } void KisShortcutMatcher::prepareReadyShortcuts() { m_d->candidateShortcuts.clear(); if (m_d->actionsSuppressed()) return; Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) { if (s->matchReady(m_d->keys, m_d->buttons)) { m_d->candidateShortcuts.append(s); } } } bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ) { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (s->isAvailable(m_d->actionGroupMask()) && s->matchBegin(button) && (!goodCandidate || s->priority() > goodCandidate->priority())) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut) { if (m_d->readyShortcut != goodCandidate) { m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } m_d->readyShortcut = 0; } else { DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); } DEBUG_SHORTCUT("Starting new action", goodCandidate); - goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); - m_d->runningShortcut = goodCandidate; + + { + m_d->runningShortcut = goodCandidate; + Private::RecursionGuard guard(this); + goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); + + // the tool migh have opened some dialog, which could break our event loop + if (guard.brokenByRecursion()) { + goodCandidate->action()->end(event); + m_d->runningShortcut = 0; + + forceDeactivateAllActions(); + } + } } - return goodCandidate; + return m_d->runningShortcut; } void KisShortcutMatcher::tryActivateReadyShortcut() { KisStrokeShortcut *goodCandidate = 0; Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) { if (!goodCandidate || s->priority() > goodCandidate->priority()) { goodCandidate = s; } } if (goodCandidate) { if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) { DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } if (!m_d->readyShortcut) { DEBUG_SHORTCUT("Preparing new ready action", goodCandidate); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); m_d->readyShortcut = goodCandidate; } } else if (m_d->readyShortcut) { DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut); m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); m_d->readyShortcut = 0; } } bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ) { - Q_ASSERT(m_d->runningShortcut); - Q_ASSERT(!m_d->readyShortcut); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->runningShortcut, true); + KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) { + // it shouldn't have happened, running and ready shortcuts + // at the same time should not be possible + forceDeactivateAllActions(); + } if (m_d->runningShortcut->matchBegin(button)) { // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_EVENT_ACTION("Ending running shortcut at event", event); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); action->end(event); action->deactivate(shortcutIndex); } } return !m_d->runningShortcut; } void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->runningShortcut); - KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->readyShortcut); + KIS_SAFE_ASSERT_RECOVER(!m_d->readyShortcut) { + // it shouldn't have happened, running and ready shortcuts + // at the same time should not be possible + forceDeactivateAllActions(); + } // first reset running shortcut to avoid infinite recursion via end() KisStrokeShortcut *runningShortcut = m_d->runningShortcut; m_d->runningShortcut = 0; if (runningShortcut->action()) { DEBUG_ACTION("Forced ending running shortcut at event"); KisAbstractInputAction* action = runningShortcut->action(); int shortcutIndex = runningShortcut->shortcutIndex(); QMouseEvent event = runningShortcut->fakeEndEvent(localPos); action->end(&event); action->deactivate(shortcutIndex); } } +void KisShortcutMatcher::forceDeactivateAllActions() +{ + if (m_d->readyShortcut) { + DEBUG_SHORTCUT("Forcefully deactivating action", m_d->readyShortcut); + m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex()); + m_d->readyShortcut = 0; + } +} + bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event ) { KisTouchShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) { if (shortcut->isAvailable(m_d->actionGroupMask()) && shortcut->match( event ) && (!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) { goodCandidate = shortcut; } } if( goodCandidate ) { if( m_d->runningShortcut ) { QTouchEvent touchEvent(QEvent::TouchEnd, event->device(), event->modifiers(), Qt::TouchPointReleased, event->touchPoints()); tryEndRunningShortcut(Qt::LeftButton, &touchEvent); } - goodCandidate->action()->activate(goodCandidate->shortcutIndex()); - goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); m_d->touchShortcut = goodCandidate; m_d->usingTouch = true; + + Private::RecursionGuard guard(this); + goodCandidate->action()->activate(goodCandidate->shortcutIndex()); + goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); + + // the tool migh have opened some dialog, which could break our event loop + if (guard.brokenByRecursion()) { + goodCandidate->action()->end(event); + m_d->touchShortcut = 0; + + forceDeactivateAllActions(); + } } - return goodCandidate; + return m_d->touchShortcut; } bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event ) { if(m_d->touchShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisTouchShortcut *touchShortcut = m_d->touchShortcut; touchShortcut->action()->end(event); touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex()); m_d->touchShortcut = 0; // empty it out now that we are done with it return true; } return false; } bool KisShortcutMatcher::tryRunNativeGestureShortcut(QNativeGestureEvent* event) { KisNativeGestureShortcut *goodCandidate = 0; if (m_d->actionsSuppressed()) return false; Q_FOREACH (KisNativeGestureShortcut* shortcut, m_d->nativeGestureShortcuts) { if (shortcut->match(event) && (!goodCandidate || shortcut->priority() > goodCandidate->priority())) { goodCandidate = shortcut; } } if (goodCandidate) { + m_d->nativeGestureShortcut = goodCandidate; + m_d->usingNativeGesture = true; + + Private::RecursionGuard guard(this); goodCandidate->action()->activate(goodCandidate->shortcutIndex()); goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event); - m_d->nativeGestureShortcut = goodCandidate; - m_d->usingNativeGesture = true; + // the tool migh have opened some dialog, which could break our event loop + if (guard.brokenByRecursion()) { + goodCandidate->action()->end(event); + m_d->nativeGestureShortcut = 0; - return true; + forceDeactivateAllActions(); + } } - return false; + return m_d->nativeGestureShortcut; } bool KisShortcutMatcher::tryEndNativeGestureShortcut(QNativeGestureEvent* event) { if (m_d->nativeGestureShortcut) { // first reset running shortcut to avoid infinite recursion via end() KisNativeGestureShortcut *nativeGestureShortcut = m_d->nativeGestureShortcut; nativeGestureShortcut->action()->end(event); nativeGestureShortcut->action()->deactivate(m_d->nativeGestureShortcut->shortcutIndex()); m_d->nativeGestureShortcut = 0; // empty it out now that we are done with it return true; } return false; } diff --git a/libs/ui/input/kis_shortcut_matcher.h b/libs/ui/input/kis_shortcut_matcher.h index 58611cb093..2890165019 100644 --- a/libs/ui/input/kis_shortcut_matcher.h +++ b/libs/ui/input/kis_shortcut_matcher.h @@ -1,264 +1,265 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SHORTCUT_MATCHER_H #define __KIS_SHORTCUT_MATCHER_H #include #include "kis_single_action_shortcut.h" #include "KisInputActionGroup.h" #include class QEvent; class QWheelEvent; class QTouchEvent; class QNativeGestureEvent; class QString; class QPointF; class KisStrokeShortcut; class KisTouchShortcut; class KisNativeGestureShortcut; /** * The class that manages connections between shortcuts and actions. * * It processes input events and generates state transitions for the * actions basing on the data, represented by the shortcuts. * * The class works with two types of actions: long running * (represented by KisStrokeShortcuts) and "atomic" * (KisSingleActionShortcut). The former one invole some long * interaction with the user by means of a mouse cursor or a tablet, * the latter one simple action like "Zoom 100%" or "Reset Rotation". * * The single action shortcuts are handled quite easily. The matcher * listens to the events coming, manages two lists of the pressed keys * and buttons and when their content corresponds to some single * action shortcut it just runs this shortcut once. * * The strategy for handling the stroke shortcuts is a bit more * complex. Each such action may be in one of the three states: * * Idle <-> Ready <-> Running * * In "Idle" state the action is completely inactive and has no access * to the user * * When the action is in "Ready" state, it means that all the * modifiers for the action are already pressed and we are only * waiting for a user to press the mouse button and start a stroke. In * this state the action can show the user its Cursor to notify the user * what is going to happen next. * * In the "Running" state, the action has full access to the user * input and is considered to perform all the work it was created for. * * To implement such state transitions for the actions, * KisShortcutMatcher first forms a list of the actions which can be * moved to a ready state (m_d->readyShortcuts), then chooses the one * with the highest priority to be the only shortcut in the "Ready" * state and activates it (m_d->readyShortcut). Then when the user * presses the mouse button, the matcher looks through the list of * ready shortcuts, chooses which will be running now, deactivates (if * needed) currently activated action and starts the chosen one. * * \see KisSingleActionShortcut * \see KisStrokeShortcut */ class KRITAUI_EXPORT KisShortcutMatcher { public: KisShortcutMatcher(); ~KisShortcutMatcher(); bool hasRunningShortcut() const; void addShortcut(KisSingleActionShortcut *shortcut); void addShortcut(KisStrokeShortcut *shortcut); void addShortcut(KisTouchShortcut *shortcut); void addShortcut(KisNativeGestureShortcut *shortcut); /** * Returns true if the currently running shortcut supports * processing hi resolution flow of events from the tablet * device. In most of the cases (except of the painting itself) * too many events make the execution of the action too slow, so * the action can decide whether it needs it. */ bool supportsHiResInputEvents(); /** * Handles a key press event. * No autorepeat events should be passed to this method. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool keyPressed(Qt::Key key); /** * Handles a key press event that has been generated by the * autorepeat. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool autoRepeatedKeyPressed(Qt::Key key); /** * Handles a key release event. * No autorepeat events should be passed to this method. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool keyReleased(Qt::Key key); /** * Handles button presses from a tablet or mouse. * * \param event the event that caused this call. * Must be of type QTabletEvent or QMouseEvent. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool buttonPressed(Qt::MouseButton button, QEvent *event); /** * Handles the mouse button release event * * \param event the event that caused this call. * Must be of type QTabletEvent or QMouseEvent. * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool buttonReleased(Qt::MouseButton button, QEvent *event); /** * Handles the mouse wheel event * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event); /** * Handles tablet and mouse move events. * * \param event the event that caused this call * * \return whether the event has been handled successfully and * should be eaten by the events filter */ bool pointerMoved(QEvent *event); /** * Handle cursor's Enter event. * We never eat it because it might be used by someone else */ void enterEvent(); /** * Handle cursor's Leave event. * We never eat it because it might be used by someone else */ void leaveEvent(); bool touchBeginEvent(QTouchEvent *event); bool touchUpdateEvent(QTouchEvent *event); bool touchEndEvent(QTouchEvent *event); bool nativeGestureBeginEvent(QNativeGestureEvent *event); bool nativeGestureEvent(QNativeGestureEvent *event); bool nativeGestureEndEvent(QNativeGestureEvent *event); /** * Resets the internal state of the matcher and activates the * prepared action if possible. * * This should be done when the window has lost the focus for * some time, so that several events could be lost */ void reinitialize(); /** * Resets the internal state of the matcher, tries to resync it to the state * passed via argument and activates the prepared action if possible. * * This synchronization should happen when the user hovers Krita windows, * **without** having keyboard focus set to it (therefore matcher cannot * get key press and release events). */ void recoveryModifiersWithoutFocus(const QVector &keys); /** * Kirta lost focus, it means that all the running actions should be ended * forcefully. */ void lostFocusEvent(const QPointF &localPos); /** * Disables the start of any actions. * * WARNING: the actions that has been started before this call * will *not* be ended. They will be ended in their usual way, * when the mouse button will be released. */ void suppressAllActions(bool value); /** * Remove all shortcuts that have been registered. */ void clearShortcuts(); void setInputActionGroupsMaskCallback(std::function func); private: friend class KisInputManagerTest; void reset(); void reset(QString msg); bool tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event); template bool tryRunSingleActionShortcutImpl(T param, U *event, const QSet &keysState); void prepareReadyShortcuts(); bool tryRunReadyShortcut( Qt::MouseButton button, QEvent* event ); void tryActivateReadyShortcut(); bool tryEndRunningShortcut( Qt::MouseButton button, QEvent* event ); void forceEndRunningShortcut(const QPointF &localPos); + void forceDeactivateAllActions(); bool tryRunTouchShortcut(QTouchEvent *event); bool tryEndTouchShortcut(QTouchEvent *event); bool tryRunNativeGestureShortcut(QNativeGestureEvent *event); bool tryEndNativeGestureShortcut(QNativeGestureEvent *event); private: class Private; Private * const m_d; }; #endif /* __KIS_SHORTCUT_MATCHER_H */ diff --git a/libs/ui/input/kis_tool_invocation_action.cpp b/libs/ui/input/kis_tool_invocation_action.cpp index a7b501f98d..092297a541 100644 --- a/libs/ui/input/kis_tool_invocation_action.cpp +++ b/libs/ui/input/kis_tool_invocation_action.cpp @@ -1,188 +1,197 @@ /* This file is part of the KDE project * Copyright (C) 2012 Arjen Hiemstra * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_invocation_action.h" #include #include #include #include #include #include #include "kis_tool.h" #include "kis_input_manager.h" #include "kis_image.h" class KisToolInvocationAction::Private { public: Private() : active(false), lineToolActivated(false) { } bool active; bool lineToolActivated; + QPointer activatedToolProxy; + QPointer runningToolProxy; }; KisToolInvocationAction::KisToolInvocationAction() : KisAbstractInputAction("Tool Invocation") , d(new Private) { setName(i18n("Tool Invocation")); setDescription(i18n("The Tool Invocation action invokes the current tool, for example, using the brush tool, it will start painting.")); QHash indexes; indexes.insert(i18n("Activate"), ActivateShortcut); indexes.insert(i18n("Confirm"), ConfirmShortcut); indexes.insert(i18n("Cancel"), CancelShortcut); indexes.insert(i18n("Activate Line Tool"), LineToolShortcut); setShortcutIndexes(indexes); } KisToolInvocationAction::~KisToolInvocationAction() { delete d; } void KisToolInvocationAction::activate(int shortcut) { Q_UNUSED(shortcut); if (!inputManager()) return; if (shortcut == LineToolShortcut) { KoToolManager::instance()->switchToolTemporaryRequested("KritaShape/KisToolLine"); d->lineToolActivated = true; } - inputManager()->toolProxy()->activateToolAction(KisTool::Primary); + d->activatedToolProxy = inputManager()->toolProxy(); + d->activatedToolProxy->activateToolAction(KisTool::Primary); } void KisToolInvocationAction::deactivate(int shortcut) { Q_UNUSED(shortcut); if (!inputManager()) return; - inputManager()->toolProxy()->deactivateToolAction(KisTool::Primary); + KIS_SAFE_ASSERT_RECOVER_NOOP(d->activatedToolProxy); + if (d->activatedToolProxy) { + d->activatedToolProxy->deactivateToolAction(KisTool::Primary); + d->activatedToolProxy.clear(); + } if (shortcut == LineToolShortcut && d->lineToolActivated) { d->lineToolActivated = false; KoToolManager::instance()->switchBackRequested(); } } int KisToolInvocationAction::priority() const { return 0; } bool KisToolInvocationAction::canIgnoreModifiers() const { return true; } void KisToolInvocationAction::begin(int shortcut, QEvent *event) { if (shortcut == ActivateShortcut || shortcut == LineToolShortcut) { + d->runningToolProxy = inputManager()->toolProxy(); d->active = - inputManager()->toolProxy()->forwardEvent( + d->runningToolProxy->forwardEvent( KisToolProxy::BEGIN, KisTool::Primary, event, event); } else if (shortcut == ConfirmShortcut) { QKeyEvent pressEvent(QEvent::KeyPress, Qt::Key_Return, 0); inputManager()->toolProxy()->keyPressEvent(&pressEvent); QKeyEvent releaseEvent(QEvent::KeyRelease, Qt::Key_Return, 0); inputManager()->toolProxy()->keyReleaseEvent(&releaseEvent); /** * All the tools now have a KisTool::requestStrokeEnd() method, * so they should use this instead of direct filtering Enter key * press. Until all the tools support it, we just duplicate the * key event and the method call */ inputManager()->canvas()->image()->requestStrokeEnd(); /** * Some tools would like to distinguish automated requestStrokeEnd() * calls from explicit user actions. Just let them do it! * * Please note that this call should happen **after** * requestStrokeEnd(). Some of the tools will switch to another * tool on this request, and this (next) tool does not expect to * get requestStrokeEnd() right after switching in. */ inputManager()->toolProxy()->explicitUserStrokeEndRequest(); } else if (shortcut == CancelShortcut) { /** * The tools now have a KisTool::requestStrokeCancellation() method, * so just request it. */ inputManager()->canvas()->image()->requestStrokeCancellation(); } } void KisToolInvocationAction::end(QEvent *event) { if (d->active) { // It might happen that the action is still running, while the // canvas has been removed, which kills the toolProxy. - if (inputManager() && inputManager()->toolProxy()) { - inputManager()->toolProxy()-> - forwardEvent(KisToolProxy::END, KisTool::Primary, event, event); + KIS_SAFE_ASSERT_RECOVER_NOOP(d->runningToolProxy); + if (d->runningToolProxy) { + d->runningToolProxy-> + forwardEvent(KisToolProxy::END, KisTool::Primary, event, event); + d->runningToolProxy.clear(); } d->active = false; } KisAbstractInputAction::end(event); } void KisToolInvocationAction::inputEvent(QEvent* event) { if (!d->active) return; - if (!inputManager()) return; - if (!inputManager()->toolProxy()) return; + if (!d->runningToolProxy) return; - inputManager()->toolProxy()-> + d->runningToolProxy-> forwardEvent(KisToolProxy::CONTINUE, KisTool::Primary, event, event); } void KisToolInvocationAction::processUnhandledEvent(QEvent* event) { bool savedState = d->active; d->active = true; inputEvent(event); d->active = savedState; } bool KisToolInvocationAction::supportsHiResInputEvents() const { return inputManager()->toolProxy()->primaryActionSupportsHiResEvents(); } bool KisToolInvocationAction::isShortcutRequired(int shortcut) const { //These really all are pretty important for basic user interaction. Q_UNUSED(shortcut) return true; } diff --git a/libs/ui/kis_async_action_feedback.cpp b/libs/ui/kis_async_action_feedback.cpp index e5af581bc2..56fbd6196c 100644 --- a/libs/ui/kis_async_action_feedback.cpp +++ b/libs/ui/kis_async_action_feedback.cpp @@ -1,87 +1,87 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_async_action_feedback.h" #include #include struct KisAsyncActionFeedback::Private { QScopedPointer progress; }; KisAsyncActionFeedback::KisAsyncActionFeedback(const QString &message, QWidget *parent) : m_d(new Private) { m_d->progress.reset(new QProgressDialog(message, "", 0, 0, parent)); m_d->progress->setWindowModality(Qt::ApplicationModal); m_d->progress->setCancelButton(0); m_d->progress->setMinimumDuration(1000); m_d->progress->setValue(0); // disable close button m_d->progress->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); } KisAsyncActionFeedback::~KisAsyncActionFeedback() { } template T runActionImpl(std::function func) { QFuture result = QtConcurrent::run(func); QFutureWatcher watcher; watcher.setFuture(result); while (watcher.isRunning()) { - qApp->processEvents(); + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } watcher.waitForFinished(); return watcher.result(); } KisImportExportErrorCode KisAsyncActionFeedback::runAction(std::function func) { return runActionImpl(func); } void KisAsyncActionFeedback::runVoidAction(std::function func) { QFuture result = QtConcurrent::run(func); QFutureWatcher watcher; watcher.setFuture(result); while (watcher.isRunning()) { - qApp->processEvents(); + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } watcher.waitForFinished(); } void KisAsyncActionFeedback::waitForMutex(QMutex *mutex) { while (!mutex->tryLock()) { - qApp->processEvents(); + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } mutex->unlock(); } diff --git a/libs/ui/kis_stopgradient_editor.cpp b/libs/ui/kis_stopgradient_editor.cpp index bdfbdd47a9..91acaab7a4 100644 --- a/libs/ui/kis_stopgradient_editor.cpp +++ b/libs/ui/kis_stopgradient_editor.cpp @@ -1,197 +1,257 @@ /* * Copyright (c) 2004 Cyrille Berger * 2016 Sven Langkamp * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_stopgradient_editor.h" #include #include #include +#include +#include #include #include #include "kis_debug.h" #include /****************************** KisStopGradientEditor ******************************/ KisStopGradientEditor::KisStopGradientEditor(QWidget *parent) : QWidget(parent), m_gradient(0) { setupUi(this); connect(gradientSlider, SIGNAL(sigSelectedStop(int)), this, SLOT(stopChanged(int))); connect(nameedit, SIGNAL(editingFinished()), this, SLOT(nameChanged())); connect(colorButton, SIGNAL(changed(KoColor)), SLOT(colorChanged(KoColor))); opacitySlider->setPrefix(i18n("Opacity: ")); opacitySlider->setRange(0.0, 1.0, 2); connect(opacitySlider, SIGNAL(valueChanged(qreal)), this, SLOT(opacityChanged(qreal))); buttonReverse->setIcon(KisIconUtils::loadIcon("view-refresh")); buttonReverse->setToolTip(i18n("Flip Gradient")); KisIconUtils::updateIcon(buttonReverse); connect(buttonReverse, SIGNAL(pressed()), SLOT(reverse())); buttonReverseSecond->setIcon(KisIconUtils::loadIcon("view-refresh")); buttonReverseSecond->setToolTip(i18n("Flip Gradient")); KisIconUtils::updateIcon(buttonReverseSecond); connect(buttonReverseSecond, SIGNAL(clicked()), SLOT(reverse())); + this->setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(showContextMenu(const QPoint &))); + setCompactMode(false); setGradient(0); stopChanged(-1); } KisStopGradientEditor::KisStopGradientEditor(KoStopGradient* gradient, QWidget *parent, const char* name, const QString& caption) : KisStopGradientEditor(parent) { setObjectName(name); setWindowTitle(caption); setGradient(gradient); } void KisStopGradientEditor::setCompactMode(bool value) { lblName->setVisible(!value); buttonReverse->setVisible(!value); nameedit->setVisible(!value); buttonReverseSecond->setVisible(value); } void KisStopGradientEditor::setGradient(KoStopGradient *gradient) { m_gradient = gradient; setEnabled(m_gradient); if (m_gradient) { gradientSlider->setGradientResource(m_gradient); nameedit->setText(gradient->name()); stopChanged(gradientSlider->selectedStop()); } emit sigGradientChanged(); } void KisStopGradientEditor::notifyGlobalColorChanged(const KoColor &color) { if (colorButton->isEnabled() && color != colorButton->color()) { colorButton->setColor(color); } } boost::optional KisStopGradientEditor::currentActiveStopColor() const { if (!colorButton->isEnabled()) return boost::none; return colorButton->color(); } void KisStopGradientEditor::stopChanged(int stop) { if (!m_gradient) return; const bool hasStopSelected = stop >= 0; opacitySlider->setEnabled(hasStopSelected); colorButton->setEnabled(hasStopSelected); stopLabel->setEnabled(hasStopSelected); if (hasStopSelected) { KoColor color = m_gradient->stops()[stop].second; opacitySlider->setValue(color.opacityF()); color.setOpacity(1.0); colorButton->setColor(color); } emit sigGradientChanged(); } void KisStopGradientEditor::colorChanged(const KoColor& color) { if (!m_gradient) return; QList stops = m_gradient->stops(); int currentStop = gradientSlider->selectedStop(); double t = stops[currentStop].first; KoColor c(color, stops[currentStop].second.colorSpace()); c.setOpacity(stops[currentStop].second.opacityU8()); stops.removeAt(currentStop); stops.insert(currentStop, KoGradientStop(t, c)); m_gradient->setStops(stops); gradientSlider->update(); emit sigGradientChanged(); } void KisStopGradientEditor::opacityChanged(qreal value) { if (!m_gradient) return; QList stops = m_gradient->stops(); int currentStop = gradientSlider->selectedStop(); double t = stops[currentStop].first; KoColor c = stops[currentStop].second; c.setOpacity(value); stops.removeAt(currentStop); stops.insert(currentStop, KoGradientStop(t, c)); m_gradient->setStops(stops); gradientSlider->update(); emit sigGradientChanged(); } void KisStopGradientEditor::nameChanged() { if (!m_gradient) return; m_gradient->setName(nameedit->text()); emit sigGradientChanged(); } void KisStopGradientEditor::reverse() { if (!m_gradient) return; QList stops = m_gradient->stops(); QList reversedStops; for(const KoGradientStop& stop : stops) { reversedStops.push_front(KoGradientStop(1 - stop.first, stop.second)); } m_gradient->setStops(reversedStops); gradientSlider->setSelectedStop(stops.size() - 1 - gradientSlider->selectedStop()); emit sigGradientChanged(); } +void KisStopGradientEditor::sortByValue( SortFlags flags = SORT_ASCENDING ) +{ + if (!m_gradient) return; + + bool ascending = (flags & SORT_ASCENDING) > 0; + bool evenDistribution = (flags & EVEN_DISTRIBUTION) > 0; + + QList stops = m_gradient->stops(); + const int stopCount = stops.size(); + + QList sortedStops; + std::sort(stops.begin(), stops.end(), KoGradientStopValueSort()); + + int stopIndex = 0; + for (const KoGradientStop& stop : stops) { + const float value = evenDistribution ? (float)stopIndex / (float)(stopCount - 1) : stop.second.toQColor().valueF(); + const float position = ascending ? value : 1.f - value; + + if (ascending) { + sortedStops.push_back(KoGradientStop(position, stop.second)); + } else { + sortedStops.push_front(KoGradientStop(position, stop.second)); + } + + stopIndex++; + } + + m_gradient->setStops(sortedStops); + gradientSlider->setSelectedStop(stopCount - 1); + gradientSlider->update(); + + emit sigGradientChanged(); +} + +void KisStopGradientEditor::showContextMenu(const QPoint &origin) +{ + QMenu contextMenu(i18n("Options"), this); + + QAction reverseValues(i18n("Reverse Values"), this); + connect(&reverseValues, &QAction::triggered, this, &KisStopGradientEditor::reverse); + + QAction sortAscendingValues(i18n("Sort by Value"), this); + connect(&sortAscendingValues, &QAction::triggered, this, [this]{ this->sortByValue(SORT_ASCENDING); } ); + QAction sortAscendingDistributed(i18n("Sort by Value (Even Distribution)"), this); + connect(&sortAscendingDistributed, &QAction::triggered, this, [this]{ this->sortByValue(SORT_ASCENDING | EVEN_DISTRIBUTION);} ); + + contextMenu.addAction(&reverseValues); + contextMenu.addSeparator(); + contextMenu.addAction(&sortAscendingValues); + contextMenu.addAction(&sortAscendingDistributed); + + contextMenu.exec(mapToGlobal(origin)); +} + diff --git a/libs/ui/kis_stopgradient_editor.h b/libs/ui/kis_stopgradient_editor.h index 65dfb4d335..7560e5feda 100644 --- a/libs/ui/kis_stopgradient_editor.h +++ b/libs/ui/kis_stopgradient_editor.h @@ -1,58 +1,68 @@ /* * Copyright (c) 2004 Cyrille Berger * 2016 Sven Langkamp * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_STOPGRADIENT_EDITOR_H_ #define _KIS_STOPGRADIENT_EDITOR_H_ #include "kritaui_export.h" #include "ui_wdgstopgradienteditor.h" #include class KoStopGradient; class KRITAUI_EXPORT KisStopGradientEditor : public QWidget, public Ui::KisWdgStopGradientEditor { Q_OBJECT public: + enum SortFlag { + SORT_ASCENDING = 1 << 0, + EVEN_DISTRIBUTION = 1 << 1 + }; + Q_DECLARE_FLAGS( SortFlags, SortFlag); + + KisStopGradientEditor(QWidget *parent); KisStopGradientEditor(KoStopGradient* gradient, QWidget *parent, const char* name, const QString& caption); void setCompactMode(bool value); void setGradient(KoStopGradient* gradient); void notifyGlobalColorChanged(const KoColor &color); boost::optional currentActiveStopColor() const; Q_SIGNALS: void sigGradientChanged(); private: KoStopGradient* m_gradient; private Q_SLOTS: void stopChanged(int stop); void colorChanged(const KoColor& color); void opacityChanged(qreal value); void nameChanged(); void reverse(); + void sortByValue(SortFlags flags); + void showContextMenu( const class QPoint& origin ); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(KisStopGradientEditor::SortFlags); #endif diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc index acd985627d..c1d8640d2e 100644 --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -1,674 +1,676 @@ /* * Copyright (c) 2006, 2010 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl_canvas2.h" #include "kis_canvas_resource_provider.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "filter/kis_filter_configuration.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include #include "kis_resources_snapshot.h" #include #include "kis_action_registry.h" #include "kis_tool_utils.h" struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. // From the canvas resources KoPattern* currentPattern{0}; KoAbstractGradient* currentGradient{0}; KoColor currentFgColor; KoColor currentBgColor; float currentExposure{1.0}; KisFilterConfigurationSP currentGenerator; QWidget* optionWidget{0}; ToolMode m_mode{HOVER_MODE}; bool m_isActive{false}; }; KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor) : KoToolBase(canvas) , d(new Private) { d->cursor = cursor; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle())); connect(this, SIGNAL(isActiveChanged(bool)), SLOT(resetCursorStyle())); } KisTool::~KisTool() { delete d; } void KisTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); resetCursorStyle(); if (!canvas()) return; if (!canvas()->resourceManager()) return; d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceProvider::ForegroundColor).value(); d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceProvider::BackgroundColor).value(); if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) { d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) { d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value(); } KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && preset->settings()) { preset->settings()->activate(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) { d->currentExposure = static_cast(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble()); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) { d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); } d->m_isActive = true; emit isActiveChanged(true); } void KisTool::deactivate() { d->m_isActive = false; emit isActiveChanged(false); KoToolBase::deactivate(); } void KisTool::canvasResourceChanged(int key, const QVariant & v) { QString formattedBrushName; if (key == KisCanvasResourceProvider::CurrentPaintOpPreset) { formattedBrushName = v.value()->name().replace("_", " "); } switch (key) { case(KoCanvasResourceProvider::ForegroundColor): d->currentFgColor = v.value(); break; case(KoCanvasResourceProvider::BackgroundColor): d->currentBgColor = v.value(); break; case(KisCanvasResourceProvider::CurrentPattern): d->currentPattern = v.value(); break; case(KisCanvasResourceProvider::CurrentGradient): d->currentGradient = static_cast(v.value()); break; case(KisCanvasResourceProvider::HdrExposure): d->currentExposure = static_cast(v.toDouble()); break; case(KisCanvasResourceProvider::CurrentGeneratorConfiguration): d->currentGenerator = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentPaintOpPreset): emit statusTextChanged(formattedBrushName); break; case(KisCanvasResourceProvider::CurrentKritaNode): resetCursorStyle(); break; default: break; // Do nothing }; } void KisTool::updateSettingsViews() { } QPointF KisTool::widgetCenterInWidgetPixels() { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); return converter->flakeToWidget(converter->flakeCenterPoint()); } QPointF KisTool::convertDocumentToWidget(const QPointF& pt) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->documentToWidget(pt); } QPointF KisTool::convertToPixelCoord(KoPointerEvent *e) { if (!image()) return e->point; return image()->documentToPixel(e->point); } QPointF KisTool::convertToPixelCoord(const QPointF& pt) { if (!image()) return pt; return image()->documentToPixel(pt); } QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!image()) return e->point; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return image()->documentToPixel(pos); } QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset) { if (!image()) return pt; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return image()->documentToPixel(pos); } QPoint KisTool::convertToImagePixelCoordFloored(KoPointerEvent *e) { if (!image()) return e->point.toPoint(); return image()->documentToImagePixelFloored(e->point); } QPointF KisTool::viewToPixel(const QPointF &viewCoord) const { if (!image()) return viewCoord; return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord)); } QRectF KisTool::convertToPt(const QRectF &rect) { if (!image()) return rect; QRectF r; //We add 1 in the following to the extreme coords because a pixel always has size r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(), int(rect.right()) / image()->xRes(), int( rect.bottom()) / image()->yRes()); return r; } qreal KisTool::convertToPt(qreal value) { const qreal avgResolution = 0.5 * (image()->xRes() + image()->yRes()); return value / avgResolution; } QPointF KisTool::pixelToView(const QPoint &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QPointF KisTool::pixelToView(const QPointF &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QRectF KisTool::pixelToView(const QRectF &pixelRect) const { if (!image()) return pixelRect; QPointF topLeft = pixelToView(pixelRect.topLeft()); QPointF bottomRight = pixelToView(pixelRect.bottomRight()); return QRectF(topLeft, bottomRight); } QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPolygon); } QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPath); } void KisTool::updateCanvasPixelRect(const QRectF &pixelRect) { canvas()->updateCanvas(convertToPt(pixelRect)); } void KisTool::updateCanvasViewRect(const QRectF &viewRect) { canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect)); } KisImageWSP KisTool::image() const { // For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1 KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (kisCanvas) { return kisCanvas->currentImage(); } return 0; } QCursor KisTool::cursor() const { return d->cursor; } -void KisTool::notifyModified() const -{ - if (image()) { - image()->setModified(); - } -} - KoPattern * KisTool::currentPattern() { return d->currentPattern; } KoAbstractGradient * KisTool::currentGradient() { return d->currentGradient; } KisPaintOpPresetSP KisTool::currentPaintOpPreset() { return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); } KisNodeSP KisTool::currentNode() const { KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return node; } KisNodeList KisTool::selectedNodes() const { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->nodeManager()->selectedNodes(); } KoColor KisTool::currentFgColor() { return d->currentFgColor; } KoColor KisTool::currentBgColor() { return d->currentBgColor; } KisImageWSP KisTool::currentImage() { return image(); } KisFilterConfigurationSP KisTool::currentGenerator() { return d->currentGenerator; } void KisTool::setMode(ToolMode mode) { d->m_mode = mode; } KisTool::ToolMode KisTool::mode() const { return d->m_mode; } void KisTool::setCursor(const QCursor &cursor) { d->cursor = cursor; } KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) { KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary); return (AlternateAction)action; } void KisTool::activatePrimaryAction() { resetCursorStyle(); } void KisTool::deactivatePrimaryAction() { resetCursorStyle(); } void KisTool::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event) { beginPrimaryAction(event); } void KisTool::continuePrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } bool KisTool::primaryActionSupportsHiResEvents() const { return false; } void KisTool::activateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::deactivateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action) { beginAlternateAction(event, action); } void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseTripleClickEvent(KoPointerEvent *event) { mouseDoubleClickEvent(event); } void KisTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseMoveEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::deleteSelection() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); if (!blockUntilOperationsFinished()) { return; } if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) { KoToolBase::deleteSelection(); } } KisTool::NodePaintAbility KisTool::nodePaintAbility() { KisNodeSP node = currentNode(); if (!node) { return NodePaintAbility::UNPAINTABLE; } if (node->inherits("KisShapeLayer")) { return NodePaintAbility::VECTOR; } if (node->inherits("KisCloneLayer")) { return NodePaintAbility::CLONE; } if (node->paintDevice()) { return NodePaintAbility::PAINT; } return NodePaintAbility::UNPAINTABLE; } QWidget* KisTool::createOptionWidget() { d->optionWidget = new QLabel(i18n("No options")); d->optionWidget->setObjectName("SpecialSpacer"); return d->optionWidget; } #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #define PROGRAM_VERTEX_ATTRIBUTE 0 void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path) { KisOpenGLCanvas2 *canvasWidget = dynamic_cast(canvas()->canvasWidget()); if (canvasWidget) { painter->beginNativePainting(); canvasWidget->paintToolOutline(path); painter->endNativePainting(); } else { painter->save(); painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter->setPen(QColor(128, 255, 128)); painter->drawPath(path); painter->restore(); } } void KisTool::resetCursorStyle() { useCursor(d->cursor); } bool KisTool::overrideCursorIfNotEditable() { // override cursor for canvas iff this tool is active // and we can't paint on the active layer if (isActive()) { KisNodeSP node = currentNode(); if (node && !node->isEditable()) { canvas()->setCursor(Qt::ForbiddenCursor); return true; } } return false; } bool KisTool::blockUntilOperationsFinished() { - KisCanvas2 * kiscanvas = static_cast(canvas()); - KisViewManager* viewManager = kiscanvas->viewManager(); - return viewManager->blockUntilOperationsFinished(image()); + // we cannot show any dialogs in the tool's code, + // it can make KisShortcutsMatcher crazy + image()->waitForDone(); + return true; + +// KisCanvas2 * kiscanvas = static_cast(canvas()); +// KisViewManager* viewManager = kiscanvas->viewManager(); +// return viewManager->blockUntilOperationsFinished(image()); } void KisTool::blockUntilOperationsFinishedForced() { - KisCanvas2 * kiscanvas = static_cast(canvas()); - KisViewManager* viewManager = kiscanvas->viewManager(); - viewManager->blockUntilOperationsFinishedForced(image()); + // we cannot show any dialogs in the tool's code, + // it can make KisShortcutsMatcher crazy + image()->waitForDone(); + +// KisCanvas2 * kiscanvas = static_cast(canvas()); +// KisViewManager* viewManager = kiscanvas->viewManager(); +// viewManager->blockUntilOperationsFinishedForced(image()); } bool KisTool::isActive() const { return d->m_isActive; } bool KisTool::nodeEditable() { KisNodeSP node = currentNode(); if (!node) { return false; } bool blockedNoIndirectPainting = false; const bool presetUsesIndirectPainting = !currentPaintOpPreset()->settings()->paintIncremental(); if (!presetUsesIndirectPainting) { const KisIndirectPaintingSupport *indirectPaintingLayer = dynamic_cast(node.data()); if (indirectPaintingLayer) { blockedNoIndirectPainting = !indirectPaintingLayer->supportsNonIndirectPainting(); } } bool nodeEditable = node->isEditable() && !blockedNoIndirectPainting; if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { message = i18n("Layer is locked and invisible."); } else if (node->userLocked()) { message = i18n("Layer is locked."); } else if(!node->visible()) { message = i18n("Layer is invisible."); } else if (blockedNoIndirectPainting) { message = i18n("Layer can be painted in Wash Mode only."); } else { message = i18n("Group not editable."); } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } return nodeEditable; } bool KisTool::selectionEditable() { KisCanvas2 * kisCanvas = static_cast(canvas()); KisViewManager * view = kisCanvas->viewManager(); bool editable = view->selectionEditable(); if (!editable) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked")); } return editable; } void KisTool::listenToModifiers(bool listen) { Q_UNUSED(listen); } bool KisTool::listeningToModifiers() { return false; } diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h index fdbd5e67f9..1aaab8ac3b 100644 --- a/libs/ui/tool/kis_tool.h +++ b/libs/ui/tool/kis_tool.h @@ -1,329 +1,326 @@ /* * Copyright (c) 2006 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_H_ #define KIS_TOOL_H_ #include #include #include #include #include #include #include #ifdef __GNUC__ #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come to" << __func__ << "while being mode" << _mode << "!" #else #define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come while being mode" << _mode << "!" #endif #define CHECK_MODE_SANITY_OR_RETURN(_mode) if (mode() != _mode) { WARN_WRONG_MODE(mode()); return; } class KoCanvasBase; class KoPattern; class KoAbstractGradient; class KisFilterConfiguration; class QPainter; class QPainterPath; class QPolygonF; /// Definitions of the toolgroups of Krita static const QString TOOL_TYPE_SHAPE = "0 Krita/Shape"; // Geometric shapes like ellipses and lines static const QString TOOL_TYPE_TRANSFORM = "2 Krita/Transform"; // Tools that transform the layer; static const QString TOOL_TYPE_FILL = "3 Krita/Fill"; // Tools that fill parts of the canvas static const QString TOOL_TYPE_VIEW = "4 Krita/View"; // Tools that affect the canvas: pan, zoom, etc. static const QString TOOL_TYPE_SELECTION = "5 Krita/Select"; // Tools that select pixels //activation id for Krita tools, Krita tools are always active and handle locked and invisible layers by themself static const QString KRITA_TOOL_ACTIVATION_ID = "flake/always"; class KRITAUI_EXPORT KisTool : public KoToolBase { Q_OBJECT Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) public: enum { FLAG_USES_CUSTOM_PRESET=0x01, FLAG_USES_CUSTOM_COMPOSITEOP=0x02, FLAG_USES_CUSTOM_SIZE=0x04 }; KisTool(KoCanvasBase * canvas, const QCursor & cursor); ~KisTool() override; virtual int flags() const { return 0; } void deleteSelection() override; // KoToolBase Implementation. public: /** * Called by KisToolProxy when the primary action of the tool is * going to be started now, that is when all the modifiers are * pressed and the only thing left is just to press the mouse * button. On coming of this callback the tool is supposed to * prepare the cursor and/or the outline to show the user shat is * going to happen next */ virtual void activatePrimaryAction(); /** * Called by KisToolProxy when the primary is no longer possible * to be started now, e.g. when its modifiers and released. The * tool is supposed revert all the preparetions it has doen in * activatePrimaryAction(). */ virtual void deactivatePrimaryAction(); /** * Called by KisToolProxy when a primary action for the tool is * started. The \p event stores the original event that * started the stroke. The \p event is _accepted_ by default. If * the tool decides to ignore this particular action (e.g. when * the node is not editable), it should call event->ignore(). Then * no further continuePrimaryAction() or endPrimaryAction() will * be called until the next user action. */ virtual void beginPrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is in progress * of pointer movement. If the tool has ignored the event in * beginPrimaryAction(), this method will not be called. */ virtual void continuePrimaryAction(KoPointerEvent *event); /** * Called by KisToolProxy when the primary action is being * finished, that is while mouseRelease or tabletRelease event. * If the tool has ignored the event in beginPrimaryAction(), this * method will not be called. */ virtual void endPrimaryAction(KoPointerEvent *event); /** * The same as beginPrimaryAction(), but called when the stroke is * started by a double-click * * \see beginPrimaryAction() */ virtual void beginPrimaryDoubleClickAction(KoPointerEvent *event); /** * Returns true if the tool can handle (and wants to handle) a * very tight flow of input events from the tablet */ virtual bool primaryActionSupportsHiResEvents() const; enum ToolAction { Primary, AlternateChangeSize, AlternatePickFgNode, AlternatePickBgNode, AlternatePickFgImage, AlternatePickBgImage, AlternateSecondary, AlternateThird, AlternateFourth, AlternateFifth, Alternate_NONE = 10000 }; // Technically users are allowed to configure this, but nobody ever would do that. // So these can basically be thought of as aliases to ctrl+click, etc. enum AlternateAction { ChangeSize = AlternateChangeSize, // Default: Shift+Left click PickFgNode = AlternatePickFgNode, // Default: Ctrl+Alt+Left click PickBgNode = AlternatePickBgNode, // Default: Ctrl+Alt+Right click PickFgImage = AlternatePickFgImage, // Default: Ctrl+Left click PickBgImage = AlternatePickBgImage, // Default: Ctrl+Right click Secondary = AlternateSecondary, Third = AlternateThird, Fourth = AlternateFourth, Fifth = AlternateFifth, NONE = 10000 }; enum NodePaintAbility { VECTOR, CLONE, PAINT, UNPAINTABLE }; Q_ENUMS(NodePaintAbility) static AlternateAction actionToAlternateAction(ToolAction action); virtual void activateAlternateAction(AlternateAction action); virtual void deactivateAlternateAction(AlternateAction action); virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action); virtual void beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action); void mousePressEvent(KoPointerEvent *event) override; void mouseDoubleClickEvent(KoPointerEvent *event) override; void mouseTripleClickEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; bool isActive() const; KisTool::NodePaintAbility nodePaintAbility(); public Q_SLOTS: void activate(ToolActivation activation, const QSet &shapes) override; void deactivate() override; void canvasResourceChanged(int key, const QVariant & res) override; // Implement this slot in case there are any widgets or properties which need // to be updated after certain operations, to reflect the inner state correctly. // At the moment this is used for smoothing options in the freehand brush, but // this will likely be expanded. virtual void updateSettingsViews(); Q_SIGNALS: void isActiveChanged(bool isActivated); protected: // conversion methods are also needed by the paint information builder friend class KisToolPaintingInformationBuilder; /// Convert from native (postscript points) to image pixel /// coordinates. QPointF convertToPixelCoord(KoPointerEvent *e); QPointF convertToPixelCoord(const QPointF& pt); QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset = QPointF(), bool useModifiers = true); QPointF convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset = QPointF()); protected: QPointF widgetCenterInWidgetPixels(); QPointF convertDocumentToWidget(const QPointF& pt); /// Convert from native (postscript points) to integer image pixel /// coordinates. This rounds down (not truncate) the pixel coordinates and /// should be used in preference to QPointF::toPoint(), which rounds, /// to ensure the cursor acts on the pixel it is visually over. QPoint convertToImagePixelCoordFloored(KoPointerEvent *e); QRectF convertToPt(const QRectF &rect); qreal convertToPt(qreal value); QPointF viewToPixel(const QPointF &viewCoord) const; /// Convert an integer pixel coordinate into a view coordinate. /// The view coordinate is at the centre of the pixel. QPointF pixelToView(const QPoint &pixelCoord) const; /// Convert a floating point pixel coordinate into a view coordinate. QPointF pixelToView(const QPointF &pixelCoord) const; /// Convert a pixel rectangle into a view rectangle. QRectF pixelToView(const QRectF &pixelRect) const; /// Convert a pixel path into a view path QPainterPath pixelToView(const QPainterPath &pixelPath) const; /// Convert a pixel polygon into a view path QPolygonF pixelToView(const QPolygonF &pixelPolygon) const; /// Update the canvas for the given rectangle in image pixel coordinates. void updateCanvasPixelRect(const QRectF &pixelRect); /// Update the canvas for the given rectangle in view coordinates. void updateCanvasViewRect(const QRectF &viewRect); QWidget* createOptionWidget() override; /** * To determine whether this tool will change its behavior when * modifier keys are pressed */ virtual bool listeningToModifiers(); /** * Request that this tool no longer listen to modifier keys * (Responding to the request is optional) */ virtual void listenToModifiers(bool listen); protected: KisImageWSP image() const; QCursor cursor() const; - /// Call this to set the document modified - void notifyModified() const; - KisImageWSP currentImage(); KoPattern* currentPattern(); KoAbstractGradient *currentGradient(); KisNodeSP currentNode() const; KisNodeList selectedNodes() const; KoColor currentFgColor(); KoColor currentBgColor(); KisPaintOpPresetSP currentPaintOpPreset(); KisFilterConfigurationSP currentGenerator(); /// paint the path which is in view coordinates, default paint mode is XOR_MODE, BW_MODE is also possible /// never apply transformations to the painter, they would be useless, if drawing in OpenGL mode. The coordinates in the path should be in view coordinates. void paintToolOutline(QPainter * painter, const QPainterPath &path); /// Checks checks if the current node is editable bool nodeEditable(); /// Checks checks if the selection is editable, only applies to local selection as global selection is always editable bool selectionEditable(); /// Override the cursor appropriately if current node is not editable bool overrideCursorIfNotEditable(); bool blockUntilOperationsFinished(); void blockUntilOperationsFinishedForced(); protected: enum ToolMode: int { HOVER_MODE, PAINT_MODE, SECONDARY_PAINT_MODE, MIRROR_AXIS_SETUP_MODE, GESTURE_MODE, PAN_MODE, OTHER, // tool-specific modes, like multibrush's symmetry axis setup OTHER_1 }; virtual void setMode(ToolMode mode); virtual ToolMode mode() const; void setCursor(const QCursor &cursor); protected Q_SLOTS: /** * Called whenever the configuration settings change. */ virtual void resetCursorStyle(); private: struct Private; Private* const d; }; #endif // KIS_TOOL_H_ diff --git a/libs/ui/tool/kis_tool_freehand.cc b/libs/ui/tool/kis_tool_freehand.cc index eb3720a37e..44bfc63df7 100644 --- a/libs/ui/tool/kis_tool_freehand.cc +++ b/libs/ui/tool/kis_tool_freehand.cc @@ -1,462 +1,461 @@ /* * kis_tool_freehand.cc - part of Krita * * Copyright (c) 2003-2007 Boudewijn Rempt * Copyright (c) 2004 Bart Coppens * Copyright (c) 2007,2008,2010 Cyrille Berger * Copyright (c) 2009 Lukáš Tvrdý * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_freehand.h" #include #include #include #include #include #include #include #include #include #include //pop up palette #include // Krita/image #include #include #include #include #include #include // Krita/ui #include "kis_abstract_perspective_grid.h" #include "kis_config.h" #include "canvas/kis_canvas2.h" #include "kis_cursor.h" #include #include #include "kis_painting_information_builder.h" #include "kis_tool_freehand_helper.h" #include "strokes/freehand_stroke.h" using namespace std::placeholders; // For _1 placeholder KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText) : KisToolPaint(canvas, cursor), m_paintopBasedPickingInAction(false), m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1)) { m_assistant = false; m_magnetism = 1.0; m_only_one_assistant = true; setSupportOutline(true); setMaskSyntheticEvents(KisConfig(true).disableTouchOnCanvas()); // Disallow mouse events from finger presses unless enabled m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this); m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText); connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline())); } KisToolFreehand::~KisToolFreehand() { delete m_helper; delete m_infoBuilder; } void KisToolFreehand::mouseMoveEvent(KoPointerEvent *event) { KisToolPaint::mouseMoveEvent(event); m_helper->cursorMoved(convertToPixelCoord(event)); } KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const { return m_helper->smoothingOptions(); } void KisToolFreehand::resetCursorStyle() { KisConfig cfg(true); switch (cfg.newCursorStyle()) { case CURSOR_STYLE_NO_CURSOR: useCursor(KisCursor::blankCursor()); break; case CURSOR_STYLE_POINTER: useCursor(KisCursor::arrowCursor()); break; case CURSOR_STYLE_SMALL_ROUND: useCursor(KisCursor::roundCursor()); break; case CURSOR_STYLE_CROSSHAIR: useCursor(KisCursor::crossCursor()); break; case CURSOR_STYLE_TRIANGLE_RIGHTHANDED: useCursor(KisCursor::triangleRightHandedCursor()); break; case CURSOR_STYLE_TRIANGLE_LEFTHANDED: useCursor(KisCursor::triangleLeftHandedCursor()); break; case CURSOR_STYLE_BLACK_PIXEL: useCursor(KisCursor::pixelBlackCursor()); break; case CURSOR_STYLE_WHITE_PIXEL: useCursor(KisCursor::pixelWhiteCursor()); break; case CURSOR_STYLE_TOOLICON: default: KisToolPaint::resetCursorStyle(); break; } } KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const { return m_infoBuilder; } void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper) { delete m_helper; m_helper = helper; } int KisToolFreehand::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } void KisToolFreehand::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); } void KisToolFreehand::deactivate() { if (mode() == PAINT_MODE) { endStroke(); setMode(KisTool::HOVER_MODE); } KisToolPaint::deactivate(); } void KisToolFreehand::initStroke(KoPointerEvent *event) { m_helper->initPaint(event, convertToPixelCoord(event), canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolFreehand::doStroke(KoPointerEvent *event) { //set canvas information here?// KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { m_helper->setCanvasHorizontalMirrorState(canvas2->xAxisMirrored()); m_helper->setCanvasRotation(canvas2->rotationAngle()); } m_helper->paintEvent(event); } void KisToolFreehand::endStroke() { m_helper->endPaint(); bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mouseReleaseEvent(); Q_UNUSED(paintOpIgnoredEvent); } bool KisToolFreehand::primaryActionSupportsHiResEvents() const { return true; } void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event) { // FIXME: workaround for the Duplicate Op tryPickByPaintOp(event, PickFgImage); requestUpdateOutline(event->point, event); NodePaintAbility paintability = nodePaintAbility(); // XXX: move this to KisTool and make it work properly for clone layers: for clone layers, the shape paint tools don't work either if (!nodeEditable() || paintability != PAINT) { if (paintability == KisToolPaint::VECTOR || paintability == KisToolPaint::CLONE){ KisCanvas2 * kiscanvas = static_cast(canvas()); QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask."); kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); } event->ignore(); return; } setMode(KisTool::PAINT_MODE); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->disableControls(); } initStroke(event); } void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); requestUpdateOutline(event->point, event); /** * Actual painting */ doStroke(event); } void KisToolFreehand::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); endStroke(); if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->endStroke(); } - notifyModified(); KisCanvas2 *canvas2 = dynamic_cast(canvas()); if (canvas2) { canvas2->viewManager()->enableControls(); } setMode(KisTool::HOVER_MODE); } bool KisToolFreehand::tryPickByPaintOp(KoPointerEvent *event, AlternateAction action) { if (action != PickFgNode && action != PickFgImage) return false; /** * FIXME: we need some better way to implement modifiers * for a paintop level. This method is used in DuplicateOp only! */ QPointF pos = adjustPosition(event->point, event->point); qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) { if (grid && grid->contains(pos)) { perspective = grid->distance(pos); break; } } if (!currentPaintOpPreset()) { return false; } bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()-> mousePressEvent(KisPaintInformation(convertToPixelCoord(event->point), m_infoBuilder->pressureToCurve(event->pressure()), event->xTilt(), event->yTilt(), event->rotation(), event->tangentialPressure(), perspective, 0, 0), event->modifiers(), currentNode()); // DuplicateOP during the picking of new source point (origin) // is the only paintop that returns "false" here return !paintOpIgnoredEvent; } void KisToolFreehand::activateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::activateAlternateAction(action); return; } useCursor(KisCursor::blankCursor()); setOutlineEnabled(true); } void KisToolFreehand::deactivateAlternateAction(AlternateAction action) { if (action != ChangeSize) { KisToolPaint::deactivateAlternateAction(action); return; } resetCursorStyle(); setOutlineEnabled(false); } void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action)) { m_paintopBasedPickingInAction = true; return; } if (action != ChangeSize) { KisToolPaint::beginAlternateAction(event, action); return; } setMode(GESTURE_MODE); m_initialGestureDocPoint = event->point; m_initialGestureGlobalPoint = QCursor::pos(); m_lastDocumentPoint = event->point; m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize(); } void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) return; if (action != ChangeSize) { KisToolPaint::continueAlternateAction(event, action); return; } QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint); QPointF actualWidgetPosition = convertDocumentToWidget(event->point); QPointF offset = actualWidgetPosition - lastWidgetPosition; KisCanvas2 *canvas2 = dynamic_cast(canvas()); QRect screenRect = QApplication::desktop()->screenGeometry(); qreal scaleX = 0; qreal scaleY = 0; canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY); const qreal maxBrushSize = KisConfig(true).readEntry("maximumBrushSize", 1000); const qreal effectiveMaxDragSize = 0.5 * screenRect.width(); const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX); const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize; const qreal sizeDiff = scaleCoeff * offset.x() ; if (qAbs(sizeDiff) > 0.01) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); const qreal newSize = qBound(0.01, m_lastPaintOpSize + sizeDiff, maxBrushSize); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); //m_brushResizeCompressor.start(newSize); m_lastDocumentPoint = event->point; m_lastPaintOpSize = newSize; } } void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) { m_paintopBasedPickingInAction = false; return; } if (action != ChangeSize) { KisToolPaint::endAlternateAction(event, action); return; } QCursor::setPos(m_initialGestureGlobalPoint); requestUpdateOutline(m_initialGestureDocPoint, 0); setMode(HOVER_MODE); } bool KisToolFreehand::wantsAutoScroll() const { return false; } void KisToolFreehand::setAssistant(bool assistant) { m_assistant = assistant; } void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant) { m_only_one_assistant = assistant; } void KisToolFreehand::slotDoResizeBrush(qreal newSize) { KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); settings->setPaintOpSize(newSize); requestUpdateOutline(m_initialGestureDocPoint, 0); } QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin) { if (m_assistant && static_cast(canvas())->paintingAssistantsDecoration()) { static_cast(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant); QPointF ap = static_cast(canvas())->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin); return (1.0 - m_magnetism) * point + m_magnetism * ap; } return point; } qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint) { qreal perspective = 1.0; Q_FOREACH (const QPointer grid, static_cast(canvas())->viewManager()->canvasResourceProvider()->perspectiveGrids()) { if (grid && grid->contains(documentPoint)) { perspective = grid->distance(documentPoint); break; } } return perspective; } void KisToolFreehand::explicitUpdateOutline() { requestUpdateOutline(m_outlineDocPoint, 0); } QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { QPointF imagePos = convertToPixelCoord(documentPos); if (currentPaintOpPreset()) return m_helper->paintOpOutline(imagePos, event, currentPaintOpPreset()->settings(), outlineMode); else return QPainterPath(); } diff --git a/libs/ui/tool/kis_tool_paint.cc b/libs/ui/tool/kis_tool_paint.cc index 8fdd621d2f..95633ac2b6 100644 --- a/libs/ui/tool/kis_tool_paint.cc +++ b/libs/ui/tool/kis_tool_paint.cc @@ -1,764 +1,765 @@ /* * Copyright (c) 2003-2009 Boudewijn Rempt * Copyright (c) 2015 Moritz Molch * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_paint.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_display_color_converter.h" #include #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_slider_spin_box.h" #include "kis_canvas_resource_provider.h" #include "kis_tool_utils.h" #include #include #include #include #include "strokes/kis_color_picker_stroke_strategy.h" KisToolPaint::KisToolPaint(KoCanvasBase *canvas, const QCursor &cursor) : KisTool(canvas, cursor), m_showColorPreview(false), m_colorPreviewShowComparePlate(false), m_colorPickerDelayTimer(), m_isOutlineEnabled(true) { m_specialHoverModifier = false; m_optionsWidgetLayout = 0; m_opacity = OPACITY_OPAQUE_U8; m_supportOutline = false; { int maxSize = KisConfig(true).readEntry("maximumBrushSize", 1000); int brushSize = 1; do { m_standardBrushSizes.push_back(brushSize); int increment = qMax(1, int(std::ceil(qreal(brushSize) / 15))); brushSize += increment; } while (brushSize < maxSize); m_standardBrushSizes.push_back(maxSize); } KisCanvas2 *kiscanvas = dynamic_cast(canvas); connect(this, SIGNAL(sigPaintingFinished()), kiscanvas->viewManager()->canvasResourceProvider(), SLOT(slotPainting())); m_colorPickerDelayTimer.setSingleShot(true); connect(&m_colorPickerDelayTimer, SIGNAL(timeout()), this, SLOT(activatePickColorDelayed())); using namespace std::placeholders; // For _1 placeholder std::function callback = std::bind(&KisToolPaint::addPickerJob, this, _1); m_colorPickingCompressor.reset( new PickingCompressor(100, callback, KisSignalCompressor::FIRST_ACTIVE)); } KisToolPaint::~KisToolPaint() { } int KisToolPaint::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP; } void KisToolPaint::canvasResourceChanged(int key, const QVariant& v) { KisTool::canvasResourceChanged(key, v); switch(key) { case(KisCanvasResourceProvider::Opacity): setOpacity(v.toDouble()); break; default: //nothing break; } connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle()), Qt::UniqueConnection); } void KisToolPaint::activate(ToolActivation toolActivation, const QSet &shapes) { if (currentPaintOpPreset()) { QString formattedBrushName = currentPaintOpPreset()->name().replace("_", " "); emit statusTextChanged(formattedBrushName); } KisTool::activate(toolActivation, shapes); if (flags() & KisTool::FLAG_USES_CUSTOM_SIZE) { connect(action("increase_brush_size"), SIGNAL(triggered()), SLOT(increaseBrushSize()), Qt::UniqueConnection); connect(action("decrease_brush_size"), SIGNAL(triggered()), SLOT(decreaseBrushSize()), Qt::UniqueConnection); } KisCanvasResourceProvider *provider = qobject_cast(canvas())->viewManager()->canvasResourceProvider(); m_oldOpacity = provider->opacity(); provider->setOpacity(m_localOpacity); } void KisToolPaint::deactivate() { if (flags() & KisTool::FLAG_USES_CUSTOM_SIZE) { disconnect(action("increase_brush_size"), 0, this, 0); disconnect(action("decrease_brush_size"), 0, this, 0); } KisCanvasResourceProvider *provider = qobject_cast(canvas())->viewManager()->canvasResourceProvider(); m_localOpacity = provider->opacity(); provider->setOpacity(m_oldOpacity); KisTool::deactivate(); } QPainterPath KisToolPaint::tryFixBrushOutline(const QPainterPath &originalOutline) { KisConfig cfg(true); if (cfg.newOutlineStyle() == OUTLINE_NONE) return originalOutline; const qreal minThresholdSize = cfg.outlineSizeMinimum(); /** * If the brush outline is bigger than the canvas itself (which * would make it invisible for a user in most of the cases) just * add a cross in the center of it */ QSize widgetSize = canvas()->canvasWidget()->size(); const int maxThresholdSum = widgetSize.width() + widgetSize.height(); QPainterPath outline = originalOutline; QRectF boundingRect = outline.boundingRect(); const qreal sum = boundingRect.width() + boundingRect.height(); QPointF center = boundingRect.center(); if (sum > maxThresholdSum) { const int hairOffset = 7; outline.moveTo(center.x(), center.y() - hairOffset); outline.lineTo(center.x(), center.y() + hairOffset); outline.moveTo(center.x() - hairOffset, center.y()); outline.lineTo(center.x() + hairOffset, center.y()); } else if (sum < minThresholdSize && !outline.isEmpty()) { outline = QPainterPath(); outline.addEllipse(center, 0.5 * minThresholdSize, 0.5 * minThresholdSize); } return outline; } void KisToolPaint::paint(QPainter &gc, const KoViewConverter &converter) { Q_UNUSED(converter); QPainterPath path = tryFixBrushOutline(pixelToView(m_currentOutline)); paintToolOutline(&gc, path); if (m_showColorPreview) { QRectF viewRect = converter.documentToView(m_oldColorPreviewRect); gc.fillRect(viewRect, m_colorPreviewCurrentColor); if (m_colorPreviewShowComparePlate) { QRectF baseColorRect = viewRect.translated(viewRect.width(), 0); gc.fillRect(baseColorRect, m_colorPreviewBaseColor); } } } void KisToolPaint::setMode(ToolMode mode) { if(this->mode() == KisTool::PAINT_MODE && mode != KisTool::PAINT_MODE) { // Let's add history information about recently used colors emit sigPaintingFinished(); } KisTool::setMode(mode); } void KisToolPaint::activatePickColor(AlternateAction action) { m_showColorPreview = true; requestUpdateOutline(m_outlineDocPoint, 0); int resource = colorPreviewResourceId(action); KoColor color = canvas()->resourceManager()->koColorResource(resource); KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); m_colorPreviewCurrentColor = kisCanvas->displayColorConverter()->toQColor(color); if (!m_colorPreviewBaseColor.isValid()) { m_colorPreviewBaseColor = m_colorPreviewCurrentColor; } } void KisToolPaint::deactivatePickColor(AlternateAction action) { Q_UNUSED(action); m_showColorPreview = false; m_oldColorPreviewRect = QRect(); m_oldColorPreviewUpdateRect = QRect(); m_colorPreviewCurrentColor = QColor(); } void KisToolPaint::pickColorWasOverridden() { m_colorPreviewShowComparePlate = false; m_colorPreviewBaseColor = QColor(); } void KisToolPaint::activateAlternateAction(AlternateAction action) { switch (action) { case PickFgNode: Q_FALLTHROUGH(); case PickBgNode: Q_FALLTHROUGH(); case PickFgImage: Q_FALLTHROUGH(); case PickBgImage: delayedAction = action; m_colorPickerDelayTimer.start(100); Q_FALLTHROUGH(); default: pickColorWasOverridden(); KisTool::activateAlternateAction(action); }; } void KisToolPaint::activatePickColorDelayed() { switch (delayedAction) { case PickFgNode: useCursor(KisCursor::pickerLayerForegroundCursor()); activatePickColor(delayedAction); break; case PickBgNode: useCursor(KisCursor::pickerLayerBackgroundCursor()); activatePickColor(delayedAction); break; case PickFgImage: useCursor(KisCursor::pickerImageForegroundCursor()); activatePickColor(delayedAction); break; case PickBgImage: useCursor(KisCursor::pickerImageBackgroundCursor()); activatePickColor(delayedAction); break; default: break; }; repaintDecorations(); } bool KisToolPaint::isPickingAction(AlternateAction action) { return action == PickFgNode || action == PickBgNode || action == PickFgImage || action == PickBgImage; } void KisToolPaint::deactivateAlternateAction(AlternateAction action) { if (!isPickingAction(action)) { KisTool::deactivateAlternateAction(action); return; } delayedAction = KisTool::NONE; m_colorPickerDelayTimer.stop(); resetCursorStyle(); deactivatePickColor(action); } void KisToolPaint::addPickerJob(const PickingJob &pickingJob) { /** * The actual picking is delayed by a compressor, so we can get this * event when the stroke is already closed */ if (!m_pickerStrokeId) return; KIS_ASSERT_RECOVER_RETURN(isPickingAction(pickingJob.action)); const QPoint imagePoint = image()->documentToImagePixelFloored(pickingJob.documentPixel); const bool fromCurrentNode = pickingJob.action == PickFgNode || pickingJob.action == PickBgNode; m_pickingResource = colorPreviewResourceId(pickingJob.action); if (!fromCurrentNode) { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN(kisCanvas); KisSharedPtr referencesLayer = kisCanvas->imageView()->document()->referenceImagesLayer(); if (referencesLayer && kisCanvas->referenceImagesDecoration()->visible()) { QColor color = referencesLayer->getPixel(imagePoint); if (color.isValid() && color.alpha() != 0) { slotColorPickingFinished(KoColor(color, image()->colorSpace())); return; } } } KisPaintDeviceSP device = fromCurrentNode ? currentNode()->colorPickSourceDevice() : image()->projection(); // Used for color picker blending. KoColor currentColor = canvas()->resourceManager()->foregroundColor(); if( pickingJob.action == PickBgNode || pickingJob.action == PickBgImage ){ currentColor = canvas()->resourceManager()->backgroundColor(); } image()->addJob(m_pickerStrokeId, new KisColorPickerStrokeStrategy::Data(device, imagePoint, currentColor)); } void KisToolPaint::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(!m_pickerStrokeId); setMode(SECONDARY_PAINT_MODE); KisColorPickerStrokeStrategy *strategy = new KisColorPickerStrokeStrategy(); connect(strategy, &KisColorPickerStrokeStrategy::sigColorUpdated, this, &KisToolPaint::slotColorPickingFinished); m_pickerStrokeId = image()->startStroke(strategy); m_colorPickingCompressor->start(PickingJob(event->point, action)); requestUpdateOutline(event->point, event); } else { KisTool::beginAlternateAction(event, action); } } void KisToolPaint::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId); m_colorPickingCompressor->start(PickingJob(event->point, action)); requestUpdateOutline(event->point, event); } else { KisTool::continueAlternateAction(event, action); } } void KisToolPaint::endAlternateAction(KoPointerEvent *event, AlternateAction action) { if (isPickingAction(action)) { KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId); image()->endStroke(m_pickerStrokeId); m_pickerStrokeId.clear(); requestUpdateOutline(event->point, event); setMode(HOVER_MODE); } else { KisTool::endAlternateAction(event, action); } } int KisToolPaint::colorPreviewResourceId(AlternateAction action) { bool toForegroundColor = action == PickFgNode || action == PickFgImage; int resource = toForegroundColor ? KoCanvasResourceProvider::ForegroundColor : KoCanvasResourceProvider::BackgroundColor; return resource; } -void KisToolPaint::slotColorPickingFinished(const KoColor &color) +void KisToolPaint::slotColorPickingFinished(KoColor color) { + color.setOpacity(OPACITY_OPAQUE_U8); canvas()->resourceManager()->setResource(m_pickingResource, color); if (!m_showColorPreview) return; KisCanvas2 * kisCanvas = dynamic_cast(canvas()); KIS_ASSERT_RECOVER_RETURN(kisCanvas); QColor previewColor = kisCanvas->displayColorConverter()->toQColor(color); m_colorPreviewShowComparePlate = true; m_colorPreviewCurrentColor = previewColor; requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } void KisToolPaint::mouseMoveEvent(KoPointerEvent *event) { KisTool::mouseMoveEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } void KisToolPaint::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); if (mode() == KisTool::HOVER_MODE) { requestUpdateOutline(event->point, event); } } QWidget * KisToolPaint::createOptionWidget() { QWidget *optionWidget = new QWidget(); optionWidget->setObjectName(toolId()); QVBoxLayout *verticalLayout = new QVBoxLayout(optionWidget); verticalLayout->setObjectName("KisToolPaint::OptionWidget::VerticalLayout"); verticalLayout->setContentsMargins(0,0,0,0); verticalLayout->setSpacing(5); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(optionWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); verticalLayout->addWidget(specialSpacer); verticalLayout->addWidget(specialSpacer); m_optionsWidgetLayout = new QGridLayout(); m_optionsWidgetLayout->setColumnStretch(1, 1); verticalLayout->addLayout(m_optionsWidgetLayout); m_optionsWidgetLayout->setContentsMargins(0,0,0,0); m_optionsWidgetLayout->setSpacing(5); if (!quickHelp().isEmpty()) { QPushButton *push = new QPushButton(KisIconUtils::loadIcon("help-contents"), QString(), optionWidget); connect(push, SIGNAL(clicked()), this, SLOT(slotPopupQuickHelp())); QHBoxLayout *hLayout = new QHBoxLayout(); hLayout->addWidget(push); hLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed)); verticalLayout->addLayout(hLayout); } return optionWidget; } QWidget* findLabelWidget(QGridLayout *layout, QWidget *control) { QWidget *result = 0; int index = layout->indexOf(control); int row, col, rowSpan, colSpan; layout->getItemPosition(index, &row, &col, &rowSpan, &colSpan); if (col > 0) { QLayoutItem *item = layout->itemAtPosition(row, col - 1); if (item) { result = item->widget(); } } else { QLayoutItem *item = layout->itemAtPosition(row, col + 1); if (item) { result = item->widget(); } } return result; } void KisToolPaint::showControl(QWidget *control, bool value) { control->setVisible(value); QWidget *label = findLabelWidget(m_optionsWidgetLayout, control); if (label) { label->setVisible(value); } } void KisToolPaint::enableControl(QWidget *control, bool value) { control->setEnabled(value); QWidget *label = findLabelWidget(m_optionsWidgetLayout, control); if (label) { label->setEnabled(value); } } void KisToolPaint::addOptionWidgetLayout(QLayout *layout) { Q_ASSERT(m_optionsWidgetLayout != 0); int rowCount = m_optionsWidgetLayout->rowCount(); m_optionsWidgetLayout->addLayout(layout, rowCount, 0, 1, 2); } void KisToolPaint::addOptionWidgetOption(QWidget *control, QWidget *label) { Q_ASSERT(m_optionsWidgetLayout != 0); if (label) { m_optionsWidgetLayout->addWidget(label, m_optionsWidgetLayout->rowCount(), 0); m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount() - 1, 1); } else { m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount(), 0, 1, 2); } } void KisToolPaint::setOpacity(qreal opacity) { m_opacity = quint8(opacity * OPACITY_OPAQUE_U8); } const KoCompositeOp* KisToolPaint::compositeOp() { if (currentNode()) { KisPaintDeviceSP device = currentNode()->paintDevice(); if (device) { QString op = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentCompositeOp).toString(); return device->colorSpace()->compositeOp(op); } } return 0; } void KisToolPaint::slotPopupQuickHelp() { QWhatsThis::showText(QCursor::pos(), quickHelp()); } void KisToolPaint::activatePrimaryAction() { pickColorWasOverridden(); setOutlineEnabled(true); KisTool::activatePrimaryAction(); } void KisToolPaint::deactivatePrimaryAction() { setOutlineEnabled(false); KisTool::deactivatePrimaryAction(); } bool KisToolPaint::isOutlineEnabled() const { return m_isOutlineEnabled; } void KisToolPaint::setOutlineEnabled(bool value) { m_isOutlineEnabled = value; requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::increaseBrushSize() { qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize(); std::vector::iterator result = std::upper_bound(m_standardBrushSizes.begin(), m_standardBrushSizes.end(), qRound(paintopSize)); int newValue = result != m_standardBrushSizes.end() ? *result : m_standardBrushSizes.back(); currentPaintOpPreset()->settings()->setPaintOpSize(newValue); requestUpdateOutline(m_outlineDocPoint, 0); } void KisToolPaint::decreaseBrushSize() { qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize(); std::vector::reverse_iterator result = std::upper_bound(m_standardBrushSizes.rbegin(), m_standardBrushSizes.rend(), (int)paintopSize, std::greater()); int newValue = result != m_standardBrushSizes.rend() ? *result : m_standardBrushSizes.front(); currentPaintOpPreset()->settings()->setPaintOpSize(newValue); requestUpdateOutline(m_outlineDocPoint, 0); } QRectF KisToolPaint::colorPreviewDocRect(const QPointF &outlineDocPoint) { if (!m_showColorPreview) return QRect(); KisConfig cfg(true); const QRectF colorPreviewViewRect = cfg.colorPreviewRect(); const QRectF colorPreviewDocumentRect = canvas()->viewConverter()->viewToDocument(colorPreviewViewRect); return colorPreviewDocumentRect.translated(outlineDocPoint); } void KisToolPaint::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event) { if (!m_supportOutline) return; KisConfig cfg(true); KisPaintOpSettings::OutlineMode outlineMode; if (isOutlineEnabled() && (mode() == KisTool::GESTURE_MODE || ((cfg.newOutlineStyle() == OUTLINE_FULL || cfg.newOutlineStyle() == OUTLINE_CIRCLE || cfg.newOutlineStyle() == OUTLINE_TILT) && ((mode() == HOVER_MODE) || (mode() == PAINT_MODE && cfg.showOutlineWhilePainting()))))) { // lisp forever! outlineMode.isVisible = true; if (cfg.newOutlineStyle() == OUTLINE_CIRCLE) { outlineMode.forceCircle = true; } else if(cfg.newOutlineStyle() == OUTLINE_TILT) { outlineMode.forceCircle = true; outlineMode.showTiltDecoration = true; } else { // noop } } outlineMode.forceFullSize = cfg.forceAlwaysFullSizedOutline(); m_outlineDocPoint = outlineDocPoint; m_currentOutline = getOutlinePath(m_outlineDocPoint, event, outlineMode); QRectF outlinePixelRect = m_currentOutline.boundingRect(); QRectF outlineDocRect = currentImage()->pixelToDocument(outlinePixelRect); // This adjusted call is needed as we paint with a 3 pixel wide brush and the pen is outside the bounds of the path // Pen uses view coordinates so we have to zoom the document value to match 2 pixel in view coordinates // See BUG 275829 qreal zoomX; qreal zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); qreal xoffset = 2.0/zoomX; qreal yoffset = 2.0/zoomY; if (!outlineDocRect.isEmpty()) { outlineDocRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } QRectF colorPreviewDocRect = this->colorPreviewDocRect(m_outlineDocPoint); QRectF colorPreviewDocUpdateRect; if (!colorPreviewDocRect.isEmpty()) { colorPreviewDocUpdateRect.adjust(-xoffset,-yoffset,xoffset,yoffset); } // DIRTY HACK ALERT: we should fetch the assistant's dirty rect when requesting // the update, instead of just dumbly update the entire canvas! KisCanvas2 * kiscanvas = dynamic_cast(canvas()); KisPaintingAssistantsDecorationSP decoration = kiscanvas->paintingAssistantsDecoration(); if (decoration && decoration->visible()) { kiscanvas->updateCanvas(); } else { // TODO: only this branch should be present! if (!m_oldColorPreviewUpdateRect.isEmpty()) { canvas()->updateCanvas(m_oldColorPreviewUpdateRect); } if (!m_oldOutlineRect.isEmpty()) { canvas()->updateCanvas(m_oldOutlineRect); } if (!outlineDocRect.isEmpty()) { canvas()->updateCanvas(outlineDocRect); } if (!colorPreviewDocUpdateRect.isEmpty()) { canvas()->updateCanvas(colorPreviewDocUpdateRect); } } m_oldOutlineRect = outlineDocRect; m_oldColorPreviewRect = colorPreviewDocRect; m_oldColorPreviewUpdateRect = colorPreviewDocUpdateRect; } QPainterPath KisToolPaint::getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) { Q_UNUSED(event); QPointF imagePos = currentImage()->documentToPixel(documentPos); QPainterPath path = currentPaintOpPreset()->settings()-> brushOutline(KisPaintInformation(imagePos), outlineMode); return path; } diff --git a/libs/ui/tool/kis_tool_paint.h b/libs/ui/tool/kis_tool_paint.h index ce52feb4f8..d3ea6f69eb 100644 --- a/libs/ui/tool/kis_tool_paint.h +++ b/libs/ui/tool/kis_tool_paint.h @@ -1,200 +1,200 @@ /* * Copyright (c) 2003 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_PAINT_H_ #define KIS_TOOL_PAINT_H_ #include "kis_tool.h" #include #include #include #include #include #include #include #include #include #include "kis_signal_compressor_with_param.h" #include #include class QGridLayout; class KoCompositeOp; class KoCanvasBase; class KRITAUI_EXPORT KisToolPaint : public KisTool { Q_OBJECT public: KisToolPaint(KoCanvasBase *canvas, const QCursor &cursor); ~KisToolPaint() override; int flags() const override; void mousePressEvent(KoPointerEvent *event) override; void mouseReleaseEvent(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; protected: void setMode(ToolMode mode) override; void canvasResourceChanged(int key, const QVariant &v) override; void paint(QPainter &gc, const KoViewConverter &converter) override; void activatePrimaryAction() override; void deactivatePrimaryAction() override; void activateAlternateAction(AlternateAction action) override; void deactivateAlternateAction(AlternateAction action) override; void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; virtual void requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event); /** If the paint tool support outline like brushes, set to true. * If not (e.g. gradient tool), set to false. Default is false. */ void setSupportOutline(bool supportOutline) { m_supportOutline = supportOutline; } virtual QPainterPath getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode); protected: bool isOutlineEnabled() const; void setOutlineEnabled(bool enabled); bool pickColor(const QPointF &documentPixel, AlternateAction action); /// Add the tool-specific layout to the default option widget layout. void addOptionWidgetLayout(QLayout *layout); /// Add a widget and a label to the current option widget layout. virtual void addOptionWidgetOption(QWidget *control, QWidget *label = 0); void showControl(QWidget *control, bool value); void enableControl(QWidget *control, bool value); QWidget * createOptionWidget() override; /** * Quick help is a short help text about the way the tool functions. */ virtual QString quickHelp() const { return QString(); } const KoCompositeOp* compositeOp(); public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; private Q_SLOTS: void slotPopupQuickHelp(); void increaseBrushSize(); void decreaseBrushSize(); void activatePickColorDelayed(); - void slotColorPickingFinished(const KoColor &color); + void slotColorPickingFinished(KoColor color); protected: quint8 m_opacity; bool m_paintOutline; QPointF m_outlineDocPoint; QPainterPath m_currentOutline; QRectF m_oldOutlineRect; bool m_showColorPreview; QRectF m_oldColorPreviewRect; QRectF m_oldColorPreviewUpdateRect; QColor m_colorPreviewCurrentColor; bool m_colorPreviewShowComparePlate; QColor m_colorPreviewBaseColor; private: QPainterPath tryFixBrushOutline(const QPainterPath &originalOutline); void setOpacity(qreal opacity); void activatePickColor(AlternateAction action); void deactivatePickColor(AlternateAction action); void pickColorWasOverridden(); int colorPreviewResourceId(AlternateAction action); QRectF colorPreviewDocRect(const QPointF &outlineDocPoint); bool isPickingAction(AlternateAction action); struct PickingJob { PickingJob() {} PickingJob(QPointF _documentPixel, AlternateAction _action) : documentPixel(_documentPixel), action(_action) {} QPointF documentPixel; AlternateAction action; }; void addPickerJob(const PickingJob &pickingJob); private: bool m_specialHoverModifier; QGridLayout *m_optionsWidgetLayout; bool m_supportOutline; /** * Used as a switch for pickColor */ // used to skip some of the tablet events and don't update the colour that often QTimer m_colorPickerDelayTimer; AlternateAction delayedAction; bool m_isOutlineEnabled; std::vector m_standardBrushSizes; KisStrokeId m_pickerStrokeId; int m_pickingResource; typedef KisSignalCompressorWithParam PickingCompressor; QScopedPointer m_colorPickingCompressor; qreal m_localOpacity {1.0}; qreal m_oldOpacity {1.0}; Q_SIGNALS: void sigPaintingFinished(); }; #endif // KIS_TOOL_PAINT_H_ diff --git a/libs/ui/tool/kis_tool_polyline_base.cpp b/libs/ui/tool/kis_tool_polyline_base.cpp index 62fe4a8f03..993199576d 100644 --- a/libs/ui/tool/kis_tool_polyline_base.cpp +++ b/libs/ui/tool/kis_tool_polyline_base.cpp @@ -1,274 +1,273 @@ /* This file is part of the KDE project * Copyright (C) 2009 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "kis_tool_polyline_base.h" #include "kis_canvas2.h" #include #include #include #include #include "kis_action_registry.h" #define SNAPPING_THRESHOLD 10 #define SNAPPING_HANDLE_RADIUS 8 #define PREVIEW_LINE_WIDTH 1 KisToolPolylineBase::KisToolPolylineBase(KoCanvasBase * canvas, KisToolPolylineBase::ToolType type, const QCursor & cursor) : KisToolShape(canvas, cursor), m_dragging(false), m_type(type), m_closeSnappingActivated(false) { } void KisToolPolylineBase::activate(KoToolBase::ToolActivation activation, const QSet &shapes) { KisToolShape::activate(activation, shapes); connect(action("undo_polygon_selection"), SIGNAL(triggered()), SLOT(undoSelection()), Qt::UniqueConnection); } void KisToolPolylineBase::deactivate() { disconnect(action("undo_polygon_selection"), 0, this, 0); cancelStroke(); KisToolShape::deactivate(); } void KisToolPolylineBase::requestStrokeEnd() { endStroke(); } void KisToolPolylineBase::requestStrokeCancellation() { cancelStroke(); } bool KisToolPolylineBase::hasUserInteractionRunning() const { return !m_points.isEmpty(); } void KisToolPolylineBase::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); NodePaintAbility paintability = nodePaintAbility(); if ((m_type == PAINT && (!nodeEditable() || paintability == UNPAINTABLE || paintability == KisToolPaint::CLONE)) || (m_type == SELECT && !selectionEditable())) { if (paintability == KisToolPaint::CLONE){ KisCanvas2 * kiscanvas = static_cast(canvas()); QString message = i18n("This tool cannot paint on clone layers. Please select a paint or vector layer or mask."); kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); } event->ignore(); return; } setMode(KisTool::PAINT_MODE); if(m_dragging && m_closeSnappingActivated) { m_points.append(m_points.first()); endStroke(); } else { m_dragging = true; } } void KisToolPolylineBase::endPrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if(m_dragging) { m_dragStart = convertToPixelCoordAndSnap(event); m_dragEnd = m_dragStart; m_points.append(m_dragStart); } } void KisToolPolylineBase::beginPrimaryDoubleClickAction(KoPointerEvent *event) { endStroke(); // this action will have no continuation event->ignore(); } void KisToolPolylineBase::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { if (action != ChangeSize || !m_dragging) { KisToolPaint::beginAlternateAction(event, action); } if (m_closeSnappingActivated) { m_points.append(m_points.first()); } endStroke(); } void KisToolPolylineBase::mouseMoveEvent(KoPointerEvent *event) { if (m_dragging && !m_points.empty()) { // erase old lines on canvas QRectF updateRect = dragBoundingRect(); // get current mouse position m_dragEnd = convertToPixelCoordAndSnap(event); // draw new lines on canvas updateRect |= dragBoundingRect(); updateCanvasViewRect(updateRect); QPointF basePoint = pixelToView(m_points.first()); m_closeSnappingActivated = m_points.size() > 1 && (basePoint - pixelToView(m_dragEnd)).manhattanLength() < SNAPPING_THRESHOLD; updateCanvasViewRect(QRectF(basePoint, 2 * QSize(SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH, SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH)).translated(-SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH,-SNAPPING_HANDLE_RADIUS + PREVIEW_LINE_WIDTH)); KisToolPaint::requestUpdateOutline(event->point, event); } else { KisToolPaint::mouseMoveEvent(event); } } void KisToolPolylineBase::undoSelection() { if(m_dragging) { //Update canvas for drag before undo QRectF updateRect = dragBoundingRect(); updateRect |= dragBoundingRect(); updateCanvasViewRect(updateRect); //Update canvas for last segment QRectF rect; if (m_points.size() > 2) { rect = pixelToView(QRectF(m_points.last(), m_points.at(m_points.size()-2)).normalized()); rect.adjust(-PREVIEW_LINE_WIDTH, -PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH); - rect |= rect; updateCanvasViewRect(rect); } if (m_points.size() > 0) { m_points.pop_back(); } if (m_points.size() > 0) { m_dragStart = m_points.last(); } } } void KisToolPolylineBase::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!canvas() || !currentImage()) return; QPointF start, end; QPointF startPos; QPointF endPos; QPainterPath path; if (m_dragging && !m_points.empty()) { startPos = pixelToView(m_dragStart); endPos = pixelToView(m_dragEnd); path.moveTo(startPos); path.lineTo(endPos); } for (vQPointF::iterator it = m_points.begin(); it != m_points.end(); ++it) { if (it == m_points.begin()) { start = (*it); } else { end = (*it); startPos = pixelToView(start); endPos = pixelToView(end); path.moveTo(startPos); path.lineTo(endPos); start = end; } } if (m_closeSnappingActivated) { QPointF basePoint = pixelToView(m_points.first()); path.addEllipse(basePoint, SNAPPING_HANDLE_RADIUS, SNAPPING_HANDLE_RADIUS); } paintToolOutline(&gc, path); KisToolPaint::paint(gc,converter); } void KisToolPolylineBase::updateArea() { updateCanvasPixelRect(image()->bounds()); } void KisToolPolylineBase::endStroke() { if (!m_dragging) return; m_dragging = false; if(m_points.count() > 1) { finishPolyline(m_points); } m_points.clear(); m_closeSnappingActivated = false; updateArea(); } void KisToolPolylineBase::cancelStroke() { if (!m_dragging) return; m_dragging = false; m_points.clear(); m_closeSnappingActivated = false; updateArea(); } QRectF KisToolPolylineBase::dragBoundingRect() { QRectF rect = pixelToView(QRectF(m_dragStart, m_dragEnd).normalized()); rect.adjust(-PREVIEW_LINE_WIDTH, -PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH, PREVIEW_LINE_WIDTH); return rect; } void KisToolPolylineBase::listenToModifiers(bool listen) { Q_UNUSED(listen) } bool KisToolPolylineBase::listeningToModifiers() { //Never grab modifier keys return false; } diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc index 9beec13b57..1180bdaa37 100644 --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -1,258 +1,257 @@ /* * Copyright (c) 2005 Adrian Page * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_shape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include #include #include #include #include "kis_selection_mask.h" #include "kis_shape_selection.h" +#include "kis_processing_applicator.h" KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor) : KisToolPaint(canvas, cursor) { m_shapeOptionsWidget = 0; } KisToolShape::~KisToolShape() { // in case the widget hasn't been shown if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) { delete m_shapeOptionsWidget; } } void KisToolShape::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } int KisToolShape::flags() const { return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET |KisTool::FLAG_USES_CUSTOM_SIZE; } QWidget * KisToolShape::createOptionWidget() { m_shapeOptionsWidget = new WdgGeometryOptions(0); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush); //connect two combo box event. Inherited classes can call the slots to make appropriate changes connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int))); connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int))); m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0)); m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0)); //if both settings are empty, force the outline to brush so the tool will work when first activated if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 && m_shapeOptionsWidget->cmbOutline->currentIndex() == 0) { m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush } return m_shapeOptionsWidget; } void KisToolShape::outlineSettingChanged(int value) { m_configGroup.writeEntry("outlineType", value); } void KisToolShape::fillSettingChanged(int value) { m_configGroup.writeEntry("fillType", value); } KisPainter::FillStyle KisToolShape::fillStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbFill->currentIndex()); } else { return KisPainter::FillStyleNone; } } KisPainter::StrokeStyle KisToolShape::strokeStyle(void) { if (m_shapeOptionsWidget) { return static_cast(m_shapeOptionsWidget->cmbOutline->currentIndex()); } else { return KisPainter::StrokeStyleNone; } } qreal KisToolShape::currentStrokeWidth() const { const qreal sizeInPx = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal(); return canvas()->unit().fromUserValue(sizeInPx); } KisToolShape::ShapeAddInfo KisToolShape::shouldAddShape(KisNodeSP currentNode) const { ShapeAddInfo info; if (currentNode->inherits("KisShapeLayer")) { info.shouldAddShape = true; } else if (KisSelectionMask *mask = dynamic_cast(currentNode.data())) { if (mask->selection()->hasShapeSelection()) { info.shouldAddShape = true; info.shouldAddSelectionShape = true; } } return info; } void KisToolShape::ShapeAddInfo::markAsSelectionShapeIfNeeded(KoShape *shape) const { if (this->shouldAddSelectionShape) { shape->setUserData(new KisShapeSelectionMarker()); } } void KisToolShape::addShape(KoShape* shape) { KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection(); switch(fillStyle()) { case KisPainter::FillStyleForegroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentFgColor().toQColor()))); break; case KisPainter::FillStyleBackgroundColor: shape->setBackground(QSharedPointer(new KoColorBackground(currentBgColor().toQColor()))); break; case KisPainter::FillStylePattern: if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); if (currentPattern()) { fill->setPattern(currentPattern()->pattern()); shape->setBackground(fill); } } else { shape->setBackground(QSharedPointer(0)); } break; case KisPainter::FillStyleGradient: { QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1)); gradient->setCoordinateMode(QGradient::ObjectBoundingMode); gradient->setStops(currentGradient()->toQGradient()->stops()); QSharedPointer gradientFill(new KoGradientBackground(gradient)); shape->setBackground(gradientFill); } break; case KisPainter::FillStyleNone: default: shape->setBackground(QSharedPointer(0)); break; } switch (strokeStyle()) { case KisPainter::StrokeStyleNone: shape->setStroke(KoShapeStrokeModelSP()); break; case KisPainter::StrokeStyleBrush: { KoShapeStrokeSP stroke(new KoShapeStroke()); stroke->setLineWidth(currentStrokeWidth()); stroke->setColor(canvas()->resourceManager()->foregroundColor().toQColor()); shape->setStroke(stroke); break; } } KUndo2Command *parentCommand = new KUndo2Command(); KoSelection *selection = canvas()->selectedShapesProxy()->selection(); const QList oldSelectedShapes = selection->selectedShapes(); // reset selection on the newly added shape :) // TODO: think about moving this into controller->addShape? new KoKeepShapesSelectedCommand(oldSelectedShapes, {shape}, canvas()->selectedShapesProxy(), false, parentCommand); KUndo2Command *cmd = canvas()->shapeController()->addShape(shape, 0, parentCommand); parentCommand->setText(cmd->text()); new KoKeepShapesSelectedCommand(oldSelectedShapes, {shape}, canvas()->selectedShapesProxy(), true, parentCommand); - canvas()->addCommand(parentCommand); + KisProcessingApplicator::runSingleCommandStroke(image(), cmd); } void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name) { KisNodeSP node = currentNode(); - if (!node || !blockUntilOperationsFinished()) { + if (!node) { return; } // Compute the outline KisImageSP image = this->image(); QTransform matrix; matrix.scale(image->xRes(), image->yRes()); matrix.translate(pathShape->position().x(), pathShape->position().y()); QPainterPath mapedOutline = matrix.map(pathShape->outline()); if (node->hasEditablePaintDevice()) { KisFigurePaintingToolHelper helper(name, image, node, canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPainterPath(mapedOutline); } else if (node->inherits("KisShapeLayer")) { pathShape->normalize(); addShape(pathShape); } - - notifyModified(); } diff --git a/libs/ui/tool/strokes/move_stroke_strategy.cpp b/libs/ui/tool/strokes/move_stroke_strategy.cpp index ee8ee8e1c3..79b3da1841 100644 --- a/libs/ui/tool/strokes/move_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/move_stroke_strategy.cpp @@ -1,238 +1,246 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "move_stroke_strategy.h" #include #include "kis_image_interfaces.h" #include "kis_node.h" #include "commands_new/kis_update_command.h" #include "commands_new/kis_node_move_command2.h" #include "kis_layer_utils.h" #include "krita_utils.h" MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade), m_nodes(), m_updatesFacade(updatesFacade), m_updatesEnabled(true) { m_nodes = KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, true); KritaUtils::filterContainer(m_nodes, [this](KisNodeSP node) { return !KisLayerUtils::checkIsCloneOf(node, m_nodes) && node->isEditable(false); }); Q_FOREACH(KisNodeSP subtree, m_nodes) { KisLayerUtils::recursiveApplyNodes( subtree, [this](KisNodeSP node) { if (KisLayerUtils::checkIsCloneOf(node, m_nodes) || !node->isEditable(false)) { m_blacklistedNodes.insert(node); } }); } setSupportsWrapAroundMode(true); + + enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER); } MoveStrokeStrategy::MoveStrokeStrategy(const MoveStrokeStrategy &rhs) - : KisStrokeStrategyUndoCommandBased(rhs), + : QObject(), + KisStrokeStrategyUndoCommandBased(rhs), m_nodes(rhs.m_nodes), m_blacklistedNodes(rhs.m_blacklistedNodes), m_updatesFacade(rhs.m_updatesFacade), m_finalOffset(rhs.m_finalOffset), m_dirtyRect(rhs.m_dirtyRect), m_dirtyRects(rhs.m_dirtyRects), m_updatesEnabled(rhs.m_updatesEnabled) { } void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node) { if (!m_blacklistedNodes.contains(node)) { m_initialNodeOffsets.insert(node, QPoint(node->x(), node->y())); } KisNodeSP child = node->firstChild(); while(child) { saveInitialNodeOffsets(child); child = child->nextSibling(); } } void MoveStrokeStrategy::initStrokeCallback() { + QRect handlesRect; + Q_FOREACH(KisNodeSP node, m_nodes) { saveInitialNodeOffsets(node); + handlesRect |= node->exactBounds(); } KisStrokeStrategyUndoCommandBased::initStrokeCallback(); + + emit sigHandlesRectCalculated(handlesRect); } void MoveStrokeStrategy::finishStrokeCallback() { Q_FOREACH (KisNodeSP node, m_nodes) { KUndo2Command *updateCommand = new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true); addMoveCommands(node, updateCommand); notifyCommandDone(KUndo2CommandSP(updateCommand), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } if (!m_updatesEnabled) { Q_FOREACH (KisNodeSP node, m_nodes) { m_updatesFacade->refreshGraphAsync(node, m_dirtyRects[node]); } } KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void MoveStrokeStrategy::cancelStrokeCallback() { if (!m_nodes.isEmpty()) { // FIXME: make cancel job exclusive instead m_updatesFacade->blockUpdates(); moveAndUpdate(QPoint()); m_updatesFacade->unblockUpdates(); } KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { Data *d = dynamic_cast(data); if(!m_nodes.isEmpty() && d) { moveAndUpdate(d->offset); /** * NOTE: we do not care about threading here, because * all our jobs are declared sequential */ m_finalOffset = d->offset; } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } #include "kis_selection_mask.h" #include "kis_selection.h" void MoveStrokeStrategy::moveAndUpdate(QPoint offset) { Q_FOREACH (KisNodeSP node, m_nodes) { QRect dirtyRect = moveNode(node, offset); m_dirtyRects[node] |= dirtyRect; if (m_updatesEnabled) { m_updatesFacade->refreshGraphAsync(node, dirtyRect); } if (KisSelectionMask *mask = dynamic_cast(node.data())) { Q_UNUSED(mask); //mask->selection()->notifySelectionChanged(); } } } QRect MoveStrokeStrategy::moveNode(KisNodeSP node, QPoint offset) { QRect dirtyRect; if (!m_blacklistedNodes.contains(node)) { dirtyRect = node->extent(); QPoint newOffset = m_initialNodeOffsets[node] + offset; /** * Some layers, e.g. clones need an update to change extent(), so * calculate the dirty rect manually */ QPoint currentOffset(node->x(), node->y()); dirtyRect |= dirtyRect.translated(newOffset - currentOffset); node->setX(newOffset.x()); node->setY(newOffset.y()); KisNodeMoveCommand2::tryNotifySelection(node); } KisNodeSP child = node->firstChild(); while(child) { dirtyRect |= moveNode(child, offset); child = child->nextSibling(); } return dirtyRect; } void MoveStrokeStrategy::addMoveCommands(KisNodeSP node, KUndo2Command *parent) { if (!m_blacklistedNodes.contains(node)) { QPoint nodeOffset(node->x(), node->y()); new KisNodeMoveCommand2(node, nodeOffset - m_finalOffset, nodeOffset, parent); } KisNodeSP child = node->firstChild(); while(child) { addMoveCommands(child, parent); child = child->nextSibling(); } } void MoveStrokeStrategy::setUpdatesEnabled(bool value) { m_updatesEnabled = value; } bool checkSupportsLodMoves(KisNodeSP subtree) { return !KisLayerUtils::recursiveFindNode( subtree, [](KisNodeSP node) -> bool { return !node->supportsLodMoves(); }); } KisStrokeStrategy* MoveStrokeStrategy::createLodClone(int levelOfDetail) { Q_UNUSED(levelOfDetail); Q_FOREACH (KisNodeSP node, m_nodes) { if (!checkSupportsLodMoves(node)) return 0; } MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this); this->setUpdatesEnabled(false); return clone; } diff --git a/libs/ui/tool/strokes/move_stroke_strategy.h b/libs/ui/tool/strokes/move_stroke_strategy.h index 0ae3bc643a..2fb09bdd3b 100644 --- a/libs/ui/tool/strokes/move_stroke_strategy.h +++ b/libs/ui/tool/strokes/move_stroke_strategy.h @@ -1,92 +1,97 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __MOVE_STROKE_STRATEGY_H #define __MOVE_STROKE_STRATEGY_H #include +#include #include "kritaui_export.h" #include "kis_stroke_strategy_undo_command_based.h" #include "kis_types.h" #include "kis_lod_transform.h" class KisUpdatesFacade; class KisPostExecutionUndoAdapter; -class KRITAUI_EXPORT MoveStrokeStrategy : public KisStrokeStrategyUndoCommandBased +class KRITAUI_EXPORT MoveStrokeStrategy : public QObject, public KisStrokeStrategyUndoCommandBased { + Q_OBJECT public: class Data : public KisStrokeJobData { public: Data(QPoint _offset) : KisStrokeJobData(SEQUENTIAL, EXCLUSIVE), offset(_offset) { } KisStrokeJobData* createLodClone(int levelOfDetail) override { return new Data(*this, levelOfDetail); } QPoint offset; private: Data(const Data &rhs, int levelOfDetail) : KisStrokeJobData(rhs) { KisLodTransform t(levelOfDetail); offset = t.map(rhs.offset); } }; public: MoveStrokeStrategy(KisNodeList nodes, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade); void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; KisStrokeStrategy* createLodClone(int levelOfDetail) override; +Q_SIGNALS: + void sigHandlesRectCalculated(const QRect &handlesRect); + private: MoveStrokeStrategy(const MoveStrokeStrategy &rhs); void setUndoEnabled(bool value); void setUpdatesEnabled(bool value); private: void moveAndUpdate(QPoint offset); QRect moveNode(KisNodeSP node, QPoint offset); void addMoveCommands(KisNodeSP node, KUndo2Command *parent); void saveInitialNodeOffsets(KisNodeSP node); private: KisNodeList m_nodes; QSet m_blacklistedNodes; KisUpdatesFacade *m_updatesFacade; QPoint m_finalOffset; QRect m_dirtyRect; QHash m_dirtyRects; bool m_updatesEnabled; QHash m_initialNodeOffsets; }; #endif /* __MOVE_STROKE_STRATEGY_H */ diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc index 1344e10228..c23a15bd56 100644 --- a/libs/ui/widgets/kis_advanced_color_space_selector.cc +++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc @@ -1,793 +1,795 @@ /* * Copyright (C) 2007 Cyrille Berger * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_advanced_color_space_selector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgcolorspaceselectoradvanced.h" #include struct KisAdvancedColorSpaceSelector::Private { Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector; QString knsrcFile; }; KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption) : QDialog(parent) , d(new Private) { setWindowTitle(caption); d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced; d->colorSpaceSelector->setupUi(this); d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible)); fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem()); d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open")); d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") ); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillCmbDepths(KoID))); connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(KoID)), this, SLOT(fillLstProfiles())); connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(colorSpaceChanged())); connect(this, SIGNAL(selectionChanged(bool)), this, SLOT(fillDescription())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint())); connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint())); connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile())); connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept())); connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject())); fillLstProfiles(); } KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector() { delete d->colorSpaceSelector; delete d; } void KisAdvancedColorSpaceSelector::fillLstProfiles() { d->colorSpaceSelector->lstProfile->blockSignals(true); const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId); d->colorSpaceSelector->lstProfile->clear(); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); QStringList profileNames; Q_FOREACH (const KoColorProfile *profile, profileList) { profileNames.append(profile->name()); } std::sort(profileNames.begin(), profileNames.end()); QListWidgetItem *defaultProfile = new QListWidgetItem; defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)")); Q_FOREACH (QString stringName, profileNames) { if (stringName == defaultProfileName) { d->colorSpaceSelector->lstProfile->addItem(defaultProfile); } else { d->colorSpaceSelector->lstProfile->addItem(stringName); } } d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile); d->colorSpaceSelector->lstProfile->blockSignals(false); colorSpaceChanged(); } void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id) { KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem(); d->colorSpaceSelector->cmbColorDepth->clear(); QList depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible); QList sortedDepths; if (depths.contains(Integer8BitsColorDepthID)) { sortedDepths << Integer8BitsColorDepthID; } if (depths.contains(Integer16BitsColorDepthID)) { sortedDepths << Integer16BitsColorDepthID; } if (depths.contains(Float16BitsColorDepthID)) { sortedDepths << Float16BitsColorDepthID; } if (depths.contains(Float32BitsColorDepthID)) { sortedDepths << Float32BitsColorDepthID; } if (depths.contains(Float64BitsColorDepthID)) { sortedDepths << Float64BitsColorDepthID; } d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths); if (sortedDepths.contains(activeDepth)) { d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth); } } void KisAdvancedColorSpaceSelector::fillDescription() { QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A"); QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants."); QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found"); QString whatIsColorant = i18n("Colorant in d50-adapted xyY."); //set colorants const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem()); QList profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId); if (!profileList.isEmpty()) { profileName = currentColorSpace()->profile()->name(); if (currentColorSpace()->profile()->hasColorants()){ QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); //QString text = currentColorSpace()->profile()->info() + " =" + d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip("
      "+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"
      "+ i18n("Red:") +""+QString::number(colorants[0], 'f', 4) + "" + QString::number(colorants[1], 'f', 4) + "" + QString::number(colorants[2], 'f', 4)+"
      "+ i18n("Green:")+""+QString::number(colorants[3], 'f', 4) + "" + QString::number(colorants[4], 'f', 4) + "" + QString::number(colorants[5], 'f', 4)+"
      "+ i18n("Blue:") +""+QString::number(colorants[6], 'f', 4) + "" + QString::number(colorants[7], 'f', 4) + "" + QString::number(colorants[8], 'f', 4)+"
      "); } else { QVector whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2)); d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4)); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } } else { d->colorSpaceSelector->lblXYZ_W->setText(notApplicable); d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip); d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip); } //set TRC QVector estimatedTRC(3); QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "Estimated Gamma: "); QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "Estimated Gamma: sRGB, L* or rec709 TRC"); QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC."); QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id(); if (profileList.isEmpty()) { d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false); d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } else if (currentModelStr == "RGBA") { QVector colorants = currentColorSpace()->profile()->getColorantsxyY(); QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); if (currentColorSpace()->profile()->hasColorants()){ d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } else { colorants.fill(0.0); d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF redcurve; QPolygonF greencurve; QPolygonF bluecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); redcurve<colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve); } if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
      "+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"
      "+estimatedCurve+""); } } else if (currentModelStr == "GRAYA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); if (estimatedTRC[0] == -1) { d->colorSpaceSelector->TRCwidget->setToolTip(""+whatissRGB+"
      "+estimatedCurve+""); } else { d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"
      "+estimatedCurve+""); } } else if (currentModelStr == "CMYKA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; QPolygonF cyancurve; QPolygonF magentacurve; QPolygonF yellowcurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { QPolygonF curve = currentColorSpace()->estimatedTRCXYY(); cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4); magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9); yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14); tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19); d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve); } d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK.")); } else if (currentModelStr == "XYZA") { QString estimatedCurve = " Estimated curve: "; estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+""); } else if (currentModelStr == "LABA") { estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC(); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setLABData(whitepoint); d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(""+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"
      "+estimatedCurve+""); } else if (currentModelStr == "YCbCrA") { QVector whitepoint = currentColorSpace()->profile()->getWhitePointxyY(); d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint); QString estimatedCurve = " Estimated curve: "; QPolygonF tonecurve; if (currentColorSpace()->profile()->hasTRC()){ for (int i=0; i<=10; i++) { QVector linear(3); linear.fill(i*0.1); currentColorSpace()->profile()->linearizeFloatValue(linear); estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]); QPointF tonepoint(linear[0],i*0.1); tonecurve<colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve); } else { d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false); } d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY()); d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb.")); } d->colorSpaceSelector->textProfileDescription->clear(); if (profileList.isEmpty()==false) { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("About ","About ") + currentColorSpace()->name() + "/" + profileName + "

      "); d->colorSpaceSelector->textProfileDescription->append("

      "+ i18nc("ICC profile version","ICC Version: ") + QString::number(currentColorSpace()->profile()->version()) + "

      "); //d->colorSpaceSelector->textProfileDescription->append("

      "+ i18nc("Who made the profile?","Manufacturer: ") + currentColorSpace()->profile()->manufacturer() + "

      "); //This would work if people actually wrote the manufacturer into the manufacturer fiedl... d->colorSpaceSelector->textProfileDescription->append("

      "+ i18nc("What is the copyright? These are from embedded strings from the icc profile, so they default to english.","Copyright: ") + currentColorSpace()->profile()->copyright() + "

      "); } else { d->colorSpaceSelector->textProfileDescription->append("

      " + profileName + "

      "); } if (currentModelStr == "RGBA") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("If the selected model is RGB", "RGB (Red, Green, Blue), is the color model used by screens and other light-based media.
      " "RGB is an additive color model: adding colors together makes them brighter. This color " "model is the most extensive of all color models, and is recommended as a model for painting," "that you can later convert to other spaces. RGB is also the recommended colorspace for HDR editing.")+"

      "); } else if (currentModelStr == "CMYKA") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("If the selected model is CMYK", "CMYK (Cyan, Magenta, Yellow, Key), " "is the model used by printers and other ink-based media.
      " "CMYK is a subtractive model, meaning that adding colors together will turn them darker. Because of CMYK " "profiles being very specific per printer, it is recommended to work in RGB space, and then later convert " "to a CMYK profile, preferably one delivered by your printer.
      " "CMYK is not recommended for painting." "Unfortunately, Krita cannot retrieve colorants or the TRC for this space.")+"

      "); } else if (currentModelStr == "XYZA") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("If the selected model is XYZ", "CIE XYZ" "is the space determined by the CIE as the space that encompasses all other colors, and used to " "convert colors between profiles. XYZ is an additive color model, meaning that adding colors together " "makes them brighter. XYZ is not recommended for painting, but can be useful to encode in. The Tone Response " "Curve is assumed to be linear.")+"

      "); } else if (currentModelStr == "GRAYA") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("If the selected model is Grayscale", "Grayscale only allows for " "gray values and transparent values. Grayscale images use half " "the memory and disk space compared to an RGB image of the same bit-depth.
      " "Grayscale is useful for inking and grayscale images. In " "Krita, you can mix Grayscale and RGB layers in the same image.")+"

      "); } else if (currentModelStr == "LABA") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("If the selected model is LAB", "L*a*b. L stands for Lightness, " "the a and b components represent color channels.
      " "L*a*b is a special model for color correction. It is based on human perception, meaning that it " "tries to encode the difference in lightness, red-green balance and yellow-blue balance. " "This makes it useful for color correction, but the vast majority of color maths in the blending " "modes do not work as expected here.
      " "Similarly, Krita does not support HDR in LAB, meaning that HDR images converted to LAB lose color " "information. This colorspace is not recommended for painting, nor for export, " "but best as a space to do post-processing in. The TRC is assumed to be the L* TRC.")+"

      "); } else if (currentModelStr == "YCbCrA") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("If the selected model is YCbCr", "YCbCr (Luma, Blue Chroma, Red Chroma), is a " "model designed for video encoding. It is based on human perception, meaning that it tries to " "encode the difference in lightness, red-green balance and yellow-blue balance. Chroma in " "this case is then a word indicating a special type of saturation, in these cases the saturation " "of Red and Blue, of which the desaturated equivalents are Green and Yellow respectively. It " "is available to open up certain images correctly, but Krita does not currently ship a profile for " "this due to lack of open source ICC profiles for YCrCb.")+"

      "); } QString currentDepthStr = d->colorSpaceSelector->cmbColorDepth->currentItem().id(); if (currentDepthStr == "U8") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("When the selected Bitdepth is 8", "8 bit integer: The default number of colors per channel. Each channel will have 256 values available, " "leading to a total amount of colors of 256 to the power of the number of channels. Recommended to use for images intended for the web, " "or otherwise simple images.")+"

      "); } else if (currentDepthStr == "U16") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("When the selected Bitdepth is 16", "16 bit integer: Also known as 'deep color'. 16 bit is ideal for editing images with a linear TRC, large " "color space, or just when you need more precise color blending. This does take twice as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it " "takes much more processing power. We recommend watching the RAM usage of the file carefully, or " "otherwise use 8 bit if your computer slows down. Take care to disable conversion optimization " "when converting from 16 bit/channel to 8 bit/channel.")+"

      "); } else if (currentDepthStr == "F16") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("When the selected Bitdepth is 16 bit float", "16 bit floating point: Also known as 'Half Floating Point', and the standard in VFX industry images. " "16 bit float is ideal for editing images with a linear Tone Response Curve, large color space, or just when you need " "more precise color blending. It being floating point is an absolute requirement for Scene Referred " "(HDR) images. This does take twice as much space on the RAM and hard-drive than any given 8 bit image " "of the same properties, and for some devices it takes much more processing power. We recommend watching " "the RAM usage of the file carefully, or otherwise use 8 bit if your computer slows down.")+"

      "); } else if (currentDepthStr == "F32") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("When the selected Bitdepth is 32bit float", "32 bit float point: Also known as 'Full Floating Point'. 32 bit float is ideal for editing images " "with a linear TRC, large color space, or just when you need more precise color blending. It being " "floating point is an absolute requirement for Scene Referred (HDR) images. This does take four times " "as much space on the RAM and hard-drive than any given 8 bit image of the same properties, and for " "some devices it takes much more processing power. We recommend watching the RAM usage of the file " "carefully, or otherwise use 8 bit if your computer slows down.")+"

      "); } else if (currentDepthStr == "F64") { d->colorSpaceSelector->textProfileDescription->append("

      "+i18nc("When the selected Bitdepth is 64bit float, but this isn't actually available in Krita at the moment.",\ "64 bit float point: 64 bit float is as precise as it gets in current technology, and this depth is used " "most of the time for images that are generated or used as an input for software. It being floating point " "is an absolute requirement for Scene Referred (HDR) images. This does take eight times as much space on " "the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it takes " "much more processing power. We recommend watching the RAM usage of the file carefully, or otherwise use " "8 bit if your computer slows down.")+"

      "); } if (profileList.isEmpty()==false) { QString possibleConversionIntents = "

      "+i18n("The following conversion intents are possible: ")+"

        "; if (currentColorSpace()->profile()->supportsPerceptual()){ possibleConversionIntents += "
      • "+i18n("Perceptual")+"
      • "; } if (currentColorSpace()->profile()->supportsRelative()){ possibleConversionIntents += "
      • "+i18n("Relative Colorimetric")+"
      • "; } if (currentColorSpace()->profile()->supportsAbsolute()){ possibleConversionIntents += "
      • "+i18n("Absolute Colorimetric")+"
      • "; } if (currentColorSpace()->profile()->supportsSaturation()){ possibleConversionIntents += "
      • "+i18n("Saturation")+"
      • "; } possibleConversionIntents += "

    "; d->colorSpaceSelector->textProfileDescription->append(possibleConversionIntents); } if (profileName.contains("-elle-")) { d->colorSpaceSelector->textProfileDescription->append("

    "+i18nc("These are Elle Stone's notes on her profiles that we ship.", "

    Extra notes on profiles by Elle Stone:

    " "

    Krita comes with a number of high quality profiles created by " "Elle Stone. This is a summary. Please check " "the full documentation as well.

    ")); if (profileName.contains("ACES-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    Quoting Wikipedia, 'Academy Color Encoding System (ACES) is a color image " "encoding system proposed by the Academy of Motion Picture Arts and Sciences that will allow for " "a fully encompassing color accurate workflow, with 'seamless interchange of high quality motion " "picture images regardless of source'.

    ")); } if (profileName.contains("ACEScg-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    The ACEScg color space is smaller than the ACES color space, but large enough to contain the 'Rec-2020 gamut " "and the DCI-P3 gamut', unlike the ACES color space it has no negative values and contains only few colors " "that fall just barely outside the area of real colors humans can see

    ")); } if (profileName.contains("ClayRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    To avoid possible copyright infringement issues, I used 'ClayRGB' (following ArgyllCMS) as the base name " "for these profiles. As used below, 'Compatible with Adobe RGB 1998' is terminology suggested in the preamble " "to the AdobeRGB 1998 color space specifications.

    " "The Adobe RGB 1998 color gamut covers a higher " "percentage of real-world cyans, greens, and yellow-greens than sRGB, but still doesn't include all printable " "cyans, greens, yellow-greens, especially when printing using today's high-end, wider gamut, ink jet printers. " "BetaRGB (not included in the profile pack) and Rec.2020 are better matches for the color gamuts of today's " "wide gamut printers.

    " "The Adobe RGB 1998 color gamut is a reasonable approximation to some of today's " "high-end wide gamut monitors.

    ")); } if (profileName.contains("AllColorsRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    This profile's color gamut is roughly the same size and shape as the ACES color space gamut, " "and like the ACES color space, AllColorsRGB holds all possible real colors. But AllColorsRGB " "actually has a slightly larger color gamut (to capture some fringe colors that barely qualify " "as real when viewed by the standard observer) and uses the D50 white point.

    " "Just like the ACES color space, AllColorsRGB holds a high percentage of imaginary colors. See the Completely " "" "Painless Programmer's Guide to XYZ, RGB, ICC, xyY, and TRCs for more information about imaginary " "colors.

    " "There is no particular reason why anyone would want to use this profile " "for editing, unless one needs to make sure your color space really does hold all " "possible real colors.

    ")); } if (profileName.contains("CIERGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    This profile is included mostly for its historical significance. " "It's the color space that was used in the original color matching experiments " "that led to the creation of the XYZ reference color space.

    " "The ASTM E white point " "is probably the right E white point to use when making the CIERGB color space profile. " "It's not clear to me what the correct CIERGB primaries really are. " "Lindbloom gives one set. The LCMS version 1 tutorial gives a different set. " "Experts in the field contend that the real primaries " "should be calculated from the spectral wavelengths, so I did.

    ")); } if (profileName.contains("IdentityRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    The IdentityRGB working space is included in the profile pack because it's a mathematically " "obvious way to include all possible visible colors, though it has a higher percentage of " "imaginary colors than the ACES and AllColorsRGB color spaces. I cannot think of any reason " "why you'd ever want to actually edit images in the IdentityRGB working space.

    ")); } if (profileName.contains("LargeRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    To avoid possible copyright infringement issues, I used 'LargeRGB' (following RawTherapee) " "as the base name for these profiles.

    " "Kodak designed the RIMM/ROMM (ProPhotoRGB) color " "gamut to include all printable and most real world colors. It includes some imaginary colors " "and excludes some of the real world blues and violet blues that can be captured by digital " "cameras. It also excludes some very saturated 'camera-captured' yellows as interpreted by " - "some (and probably many) camera matrix input profiles.

    " + "some (and probably many) camera matrix input profiles.

    " "The ProPhotoRGB primaries are " "hard-coded into Adobe products such as Lightroom and the Dng-DCP camera 'profiles'. However, " "other than being large enough to hold a lot of colors, ProPhotoRGB has no particular merit " - "as an RGB working space. Personally and for most editing purposes, I recommend BetaRGB, Rec2020, " - "or the ACEScg profiles ProPhotoRGB.

    ")); + "as an RGB working space. Personally I recommend the Rec.2020 or ACEScg profiles over " + "ProPhotoRGB. But if you have an already well-established workflow using ProPhotoRGB, you " + "might find a shift to another RGB working space a little odd, at least at first, and so you " + "have to weight the pros and cons of changing your workflow.

    ")); } if (profileName.contains("Rec2020-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    Rec.2020 is the up-and-coming replacement for the thoroughly outdated sRGB color space. As of " "June 2015, very few (if any) display devices (and certainly no affordable display devices) can " "display all of Rec.2020. However, display technology is closing in on Rec.2020, movies are " "already being made for Rec.2020, and various cameras offer support for Rec.2020. And in the " "digital darkroom Rec.2020 is much more suitable as a general RGB working space than the " "exceedingly small sRGB color space.

    ")); } if (profileName.contains("sRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    Hewlett-Packard and Microsoft designed sRGB to match the color gamut of consumer-grade CRTs " "from the 1990s. sRGB is the standard color space for the world wide web and is still the best " "choice for exporting images to the internet.

    " "The sRGB color gamut was a good match to " "calibrated decent quality CRTs. But sRGB is not a good match to many consumer-grade LCD monitors, " "which often cannot display the more saturated sRGB blues and magentas (the good news: as technology " "progresses, wider gamuts are trickling down to consumer grade monitors).

    " "Printer color gamuts can easily exceed the sRGB color gamut in cyans, greens, and yellow-greens. Colors from interpolated " "camera raw files also often exceed the sRGB color gamut.

    " "As a very relevant aside, using perceptual " "intent when converting to sRGB does not magically makes otherwise out of gamut colors fit inside the " "sRGB color gamut! The standard sRGB color space (along with all the other the RGB profiles provided " "in my profile pack) is a matrix profile, and matrix profiles don't have perceptual intent tables.

    ")); } if (profileName.contains("WideRGB-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    To avoid possible copyright infringement issues, I used 'WideRGB' as the base name for these profiles.

    " "WideGamutRGB was designed by Adobe to be a wide gamut color space that uses spectral colors " "as its primaries. Pascale's primary values produce a profile that matches old V2 Widegamut profiles " "from Adobe and Canon. It is an interesting color space, but shortly after its introduction, Adobe " "switched their emphasis to the ProPhotoRGB color space.

    ")); } if (profileName.contains("Gray-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    These profiles are for use with RGB images that have been converted to monotone gray (black and white). " "The main reason to convert from RGB to Gray is to save the file space needed to encode the image. " "Google places a premium on fast-loading web pages, and images are one of the slower-loading elements " "of a web page. So converting black and white images to Grayscale images does save some kilobytes. " " For grayscale images uploaded to the internet, convert the image to the V2 Gray profile with the sRGB TRC.

    ")); } if (profileName.contains("-g10")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    The profiles that end in '-g10.icc' are linear gamma (gamma=1.0, 'linear light', etc) profiles and " "should only be used when editing at high bit depths (16-bit floating point, 16-bit integer, 32-bit " "floating point, 32-bit integer). Many editing operations produce better results in linear gamma color " "spaces.

    ")); } if (profileName.contains("-labl")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    The profiles that end in '-labl.icc' have perceptually uniform TRCs. A few editing operations really " "should be done on perceptually uniform RGB. Make sure you use the V4 versions for editing high bit depth " "images.

    ")); } - if (profileName.contains("-srgbtrc") || profileName.contains("-g22") || profileName.contains("-g18") || profileName.contains("-bt709")) { + if (profileName.contains("-srgbtrc") || profileName.contains("-g22") || profileName.contains("-g18") || profileName.contains("-rec709")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", - "

    The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-bt709.icc' have approximately but not exactly " + "

    The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-rec709.icc' have approximately but not exactly " "perceptually uniform TRCs. ProPhotoRGB's gamma=1.8 TRC is not quite as close to being perceptually uniform.

    ")); } if (d->colorSpaceSelector->cmbColorDepth->currentItem().id()=="U8") { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    When editing 8-bit images, you should use a profile with a small color gamut and an approximately or " "exactly perceptually uniform TRC. Of the profiles supplied in my profile pack, only the sRGB and AdobeRGB1998 " "(ClayRGB) color spaces are small enough for 8-bit editing. Even with the AdobeRGB1998 color space you need to " "be careful to not cause posterization. And of course you cannot use the linear gamma versions of these profiles " "for 8-bit editing.

    ")); } if (profileName.contains("-V4-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    Use V4 profiles for editing images using high bit depth image editors that use LCMS as the Color Management Module. " "This includes Krita, digiKam/showFoto, and GIMP 2.9.

    ")); } if (profileName.contains("-V2-")) { d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.", "

    Use V2 profiles for exporting finished images to be uploaded to the web or for use with imaging software that " "cannot read V4 profiles.

    ")); } } d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start); } QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector whitePoint) { QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4)); //A (0.451170, 0.40594) (2856K)(tungsten) if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) && (whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){ name="A"; return name; } //B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete) //C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete) //D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant) if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) && (whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){ name="D50"; return name; } //D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight) if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) && (whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){ name="D55"; return name; } //D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default) if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) && (whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){ name="D60"; return name; } //D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default) //Elle's are old school with 0.3127 and 0.3289 if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) && (whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){ name="D65"; return name; } //D75 (0.29968, 0.31740) (7504K) (North sky Daylight) if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) && (whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){ name="D75"; return name; } //E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default) if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) && (whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){ name="E"; return name; } //The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.// //F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent) //F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent) //F3 (0.41761, 0.38324) (3450K) (White Fluorescent) //F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent) //F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent) //F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent) //F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator) //F8 (0.34902, 0.35939) (5000K) (D50 simulator) //F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent) //F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50) //F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40) //F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30) return name; } const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace() { QString check = ""; if (d->colorSpaceSelector->lstProfile->currentItem()) { check = d->colorSpaceSelector->lstProfile->currentItem()->text(); } else if (d->colorSpaceSelector->lstProfile->item(0)) { check = d->colorSpaceSelector->lstProfile->item(0)->text(); } return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(), d->colorSpaceSelector->cmbColorDepth->currentItem().id(), check); } void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id) { d->colorSpaceSelector->cmbColorModels->setCurrent(id); fillLstProfiles(); fillCmbDepths(id); } void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id) { d->colorSpaceSelector->cmbColorDepth->setCurrent(id); fillLstProfiles(); } void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name) { QList Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith); d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0)); } void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace) { if (!colorSpace) { return; } setCurrentColorModel(colorSpace->colorModelId()); setCurrentColorDepth(colorSpace->colorDepthId()); setCurrentProfile(colorSpace->profile()->name()); } void KisAdvancedColorSpaceSelector::colorSpaceChanged() { bool valid = d->colorSpaceSelector->lstProfile->count() != 0; emit(selectionChanged(valid)); if (valid) { emit colorSpaceChanged(currentColorSpace()); } } void KisAdvancedColorSpaceSelector::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { QUrl file(profileName); if (!QFile::copy(profileName, saveLocation + file.fileName())) { dbgKrita << "Could not install profile!"; return; } iccEngine->addProfile(saveLocation + file.fileName()); } fillLstProfiles(); } diff --git a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp index f319e708f6..df43af3e4b 100644 --- a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp +++ b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp @@ -1,363 +1,368 @@ /* * Copyright (c) 2004 Cyrille Berger * 2016 Sven Langkamp * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "widgets/kis_stopgradient_slider_widget.h" #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_debug.h" #include "krita_utils.h" KisStopGradientSliderWidget::KisStopGradientSliderWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) , m_selectedStop(0) , m_drag(0) { QLinearGradient defaultGradient; m_defaultGradient.reset(KoStopGradient::fromQGradient(&defaultGradient)); setGradientResource(0); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); setMouseTracking(true); QWindow *window = this->window()->windowHandle(); if (window) { connect(window, SIGNAL(screenChanged(QScreen*)), SLOT(updateHandleSize())); } updateHandleSize(); } void KisStopGradientSliderWidget::updateHandleSize() { QFontMetrics fm(font()); const int h = fm.height(); m_handleSize = QSize(0.34 * h, h); } int KisStopGradientSliderWidget::handleClickTolerance() const { // the size of the default text! return m_handleSize.width(); } void KisStopGradientSliderWidget::setGradientResource(KoStopGradient* gradient) { m_gradient = gradient ? gradient : m_defaultGradient.data(); if (m_gradient && m_selectedStop >= 0) { m_selectedStop = qBound(0, m_selectedStop, m_gradient->stops().size() - 1); emit sigSelectedStop(m_selectedStop); } else { m_selectedStop = -1; } } void KisStopGradientSliderWidget::paintHandle(qreal position, const QColor &color, bool isSelected, QPainter *painter) { const QRect handlesRect = this->handlesStipeRect(); const int handleCenter = handlesRect.left() + position * handlesRect.width(); const int handlesHalfWidth = handlesRect.height() * 0.26; // = 1.0 / 0.66 * 0.34 / 2.0 <-- golden ratio QPolygon triangle(3); triangle[0] = QPoint(handleCenter, handlesRect.top()); triangle[1] = QPoint(handleCenter - handlesHalfWidth, handlesRect.bottom()); triangle[2] = QPoint(handleCenter + handlesHalfWidth, handlesRect.bottom()); const qreal lineWidth = 1.0; if (!isSelected) { painter->setPen(QPen(palette().text(), lineWidth)); painter->setBrush(QBrush(color)); painter->setRenderHint(QPainter::Antialiasing); painter->drawPolygon(triangle); } else { painter->setPen(QPen(palette().highlight(), 1.5 * lineWidth)); painter->setBrush(QBrush(color)); painter->setRenderHint(QPainter::Antialiasing); painter->drawPolygon(triangle); } } void KisStopGradientSliderWidget::paintEvent(QPaintEvent* pe) { QWidget::paintEvent(pe); QPainter painter(this); painter.setPen(Qt::black); const QRect previewRect = gradientStripeRect(); KritaUtils::renderExactRect(&painter, kisGrowRect(previewRect, 1)); painter.drawRect(previewRect); if (m_gradient) { QImage image = m_gradient->generatePreview(previewRect.width(), previewRect.height()); if (!image.isNull()) { painter.drawImage(previewRect.topLeft(), image); } QList handlePositions = m_gradient->stops(); for (int i = 0; i < handlePositions.count(); i++) { if (i == m_selectedStop) continue; paintHandle(handlePositions[i].first, handlePositions[i].second.toQColor(), false, &painter); } if (m_selectedStop >= 0) { paintHandle(handlePositions[m_selectedStop].first, handlePositions[m_selectedStop].second.toQColor(), true, &painter); } } } int findNearestHandle(qreal t, const qreal tolerance, const QList &stops) { int result = -1; qreal minDistance = tolerance; for (int i = 0; i < stops.size(); i++) { const KoGradientStop &stop = stops[i]; const qreal distance = qAbs(t - stop.first); if (distance < minDistance) { minDistance = distance; result = i; } } return result; } void KisStopGradientSliderWidget::mousePressEvent(QMouseEvent * e) { if (!allowedClickRegion(handleClickTolerance()).contains(e->pos())) { QWidget::mousePressEvent(e); return; } + if (e->buttons() != Qt::LeftButton ) { + QWidget::mousePressEvent(e); + return; + } + const QRect handlesRect = this->handlesStipeRect(); const qreal t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width(); const QList stops = m_gradient->stops(); const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops); if (clickedStop >= 0) { if (m_selectedStop != clickedStop) { m_selectedStop = clickedStop; emit sigSelectedStop(m_selectedStop); } m_drag = true; } else { insertStop(qBound(0.0, t, 1.0)); m_drag = true; } update(); updateCursor(e->pos()); } void KisStopGradientSliderWidget::mouseReleaseEvent(QMouseEvent * e) { Q_UNUSED(e); m_drag = false; updateCursor(e->pos()); } int getNewInsertPosition(const KoGradientStop &stop, const QList &stops) { int result = 0; for (int i = 0; i < stops.size(); i++) { if (stop.first <= stops[i].first) break; result = i + 1; } return result; } void KisStopGradientSliderWidget::mouseMoveEvent(QMouseEvent * e) { updateCursor(e->pos()); if (m_drag) { const QRect handlesRect = this->handlesStipeRect(); double t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width(); QList stops = m_gradient->stops(); if (t < -0.1 || t > 1.1) { if (stops.size() > 2 && m_selectedStop >= 0) { m_removedStop = stops[m_selectedStop]; stops.removeAt(m_selectedStop); m_selectedStop = -1; } } else { if (m_selectedStop < 0) { m_removedStop.first = qBound(0.0, t, 1.0); const int newPos = getNewInsertPosition(m_removedStop, stops); stops.insert(newPos, m_removedStop); m_selectedStop = newPos; } else { KoGradientStop draggedStop = stops[m_selectedStop]; draggedStop.first = qBound(0.0, t, 1.0); stops.removeAt(m_selectedStop); const int newPos = getNewInsertPosition(draggedStop, stops); stops.insert(newPos, draggedStop); m_selectedStop = newPos; } } m_gradient->setStops(stops); emit sigSelectedStop(m_selectedStop); update(); } else { QWidget::mouseMoveEvent(e); } } void KisStopGradientSliderWidget::updateCursor(const QPoint &pos) { const bool isInAllowedRegion = allowedClickRegion(handleClickTolerance()).contains(pos); QCursor currentCursor; if (isInAllowedRegion) { const QRect handlesRect = this->handlesStipeRect(); const qreal t = (qreal(pos.x()) - handlesRect.x()) / handlesRect.width(); const QList stops = m_gradient->stops(); const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops); if (clickedStop >= 0) { currentCursor = m_drag ? Qt::ClosedHandCursor : Qt::OpenHandCursor; } } if (currentCursor.shape() != Qt::ArrowCursor) { setCursor(currentCursor); } else { unsetCursor(); } } void KisStopGradientSliderWidget::insertStop(double t) { KIS_ASSERT_RECOVER(t >= 0 && t <= 1.0 ) { t = qBound(0.0, t, 1.0); } QList stops = m_gradient->stops(); KoColor color; m_gradient->colorAt(color, t); const KoGradientStop stop(t, color); const int newPos = getNewInsertPosition(stop, stops); stops.insert(newPos, stop); m_gradient->setStops(stops); m_selectedStop = newPos; emit sigSelectedStop(m_selectedStop); } QRect KisStopGradientSliderWidget::sliderRect() const { return QRect(QPoint(), size()).adjusted(m_handleSize.width(), 1, -m_handleSize.width(), -1); } QRect KisStopGradientSliderWidget::gradientStripeRect() const { const QRect rc = sliderRect(); return rc.adjusted(0, 0, 0, -m_handleSize.height()); } QRect KisStopGradientSliderWidget::handlesStipeRect() const { const QRect rc = sliderRect(); return rc.adjusted(0, rc.height() - m_handleSize.height(), 0, 0); } QRegion KisStopGradientSliderWidget::allowedClickRegion(int tolerance) const { QRegion result; result += sliderRect(); result += handlesStipeRect().adjusted(-tolerance, 0, tolerance, 0); return result; } int KisStopGradientSliderWidget::selectedStop() { return m_selectedStop; } void KisStopGradientSliderWidget::setSelectedStop(int selected) { m_selectedStop = selected; emit sigSelectedStop(m_selectedStop); update(); } int KisStopGradientSliderWidget::minimalHeight() const { QFontMetrics fm(font()); const int h = fm.height(); QStyleOptionToolButton opt; QSize sz = (style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this). expandedTo(QApplication::globalStrut())); return sz.height() + m_handleSize.height(); } QSize KisStopGradientSliderWidget::sizeHint() const { const int h = minimalHeight(); return QSize(2 * h, h); } QSize KisStopGradientSliderWidget::minimumSizeHint() const { const int h = minimalHeight(); return QSize(h, h); } diff --git a/libs/widgets/KoResourceServerProvider.cpp b/libs/widgets/KoResourceServerProvider.cpp index f33db43416..1370a357a9 100644 --- a/libs/widgets/KoResourceServerProvider.cpp +++ b/libs/widgets/KoResourceServerProvider.cpp @@ -1,198 +1,199 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (C) 2011 Srikanth Tiyyagura 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.1 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 Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KoResourceServerProvider.h" #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoResourcePaths.h" +#include "klocalizedstring.h" #include using namespace std; class GradientResourceServer : public KoResourceServer { public: GradientResourceServer(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) , m_foregroundToTransparent(0) , m_foregroundToBackground(0) { insertSpecialGradients(); } void insertSpecialGradients() { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); QList stops; KoStopGradient* gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); - gradient->setName("Foreground to Transparent"); + gradient->setName(i18n("Foreground to Transparent")); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(QColor(0, 0, 0, 0), cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToTransparent = gradient; gradient = new KoStopGradient(); gradient->setType(QGradient::LinearGradient); - gradient->setName("Foreground to Background"); + gradient->setName(i18n("Foreground to Background")); stops.clear(); stops << KoGradientStop(0.0, KoColor(Qt::black, cs)) << KoGradientStop(1.0, KoColor(Qt::white, cs)); gradient->setStops(stops); gradient->setValid(true); gradient->setPermanent(true); addResource(gradient, false, true); m_foregroundToBackground = gradient; } private: friend class KoResourceBundle; KoAbstractGradient* createResource( const QString & filename ) override { QString fileExtension; int index = filename.lastIndexOf('.'); if (index != -1) fileExtension = filename.mid(index).toLower(); KoAbstractGradient* grad = 0; if(fileExtension == ".svg" || fileExtension == ".kgr") grad = new KoStopGradient(filename); else if(fileExtension == ".ggr" ) grad = new KoSegmentGradient(filename); return grad; } QList< KoAbstractGradient* > sortedResources() override { QList< KoAbstractGradient* > resources = KoResourceServer::sortedResources(); QList< KoAbstractGradient* > sorted; if (m_foregroundToTransparent && resources.contains(m_foregroundToTransparent)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToTransparent))); } if (m_foregroundToBackground && resources.contains(m_foregroundToBackground)) { sorted.append(resources.takeAt(resources.indexOf(m_foregroundToBackground))); } return sorted + resources; } KoAbstractGradient* m_foregroundToTransparent; KoAbstractGradient* m_foregroundToBackground; }; struct Q_DECL_HIDDEN KoResourceServerProvider::Private { KoResourceServer* patternServer; KoResourceServer* gradientServer; KoResourceServer* paletteServer; KoResourceServer *svgSymbolCollectionServer; KoResourceServer* gamutMaskServer; }; KoResourceServerProvider::KoResourceServerProvider() : d(new Private) { d->patternServer = new KoResourceServerSimpleConstruction("ko_patterns", "*.pat:*.jpg:*.gif:*.png:*.tif:*.xpm:*.bmp" ); d->patternServer->loadResources(blacklistFileNames(d->patternServer->fileNames(), d->patternServer->blackListedFiles())); d->gradientServer = new GradientResourceServer("ko_gradients", "*.svg:*.ggr"); d->gradientServer->loadResources(blacklistFileNames(d->gradientServer->fileNames(), d->gradientServer->blackListedFiles())); d->paletteServer = new KoResourceServerSimpleConstruction("ko_palettes", "*.kpl:*.gpl:*.pal:*.act:*.aco:*.css:*.colors:*.xml:*.sbz"); d->paletteServer->loadResources(blacklistFileNames(d->paletteServer->fileNames(), d->paletteServer->blackListedFiles())); d->svgSymbolCollectionServer = new KoResourceServerSimpleConstruction("symbols", "*.svg"); d->svgSymbolCollectionServer->loadResources(blacklistFileNames(d->svgSymbolCollectionServer->fileNames(), d->svgSymbolCollectionServer->blackListedFiles())); d->gamutMaskServer = new KoResourceServerSimpleConstruction("ko_gamutmasks", "*.kgm"); d->gamutMaskServer->loadResources(blacklistFileNames(d->gamutMaskServer->fileNames(), d->gamutMaskServer->blackListedFiles())); } KoResourceServerProvider::~KoResourceServerProvider() { delete d->patternServer; delete d->gradientServer; delete d->paletteServer; delete d->svgSymbolCollectionServer; delete d->gamutMaskServer; delete d; } Q_GLOBAL_STATIC(KoResourceServerProvider, s_instance) KoResourceServerProvider* KoResourceServerProvider::instance() { return s_instance; } QStringList KoResourceServerProvider::blacklistFileNames(QStringList fileNames, const QStringList &blacklistedFileNames) { if (!blacklistedFileNames.isEmpty()) { foreach (const QString &s, blacklistedFileNames) { fileNames.removeAll(s); } } return fileNames; } KoResourceServer* KoResourceServerProvider::patternServer() { return d->patternServer; } KoResourceServer* KoResourceServerProvider::gradientServer() { return d->gradientServer; } KoResourceServer* KoResourceServerProvider::paletteServer() { return d->paletteServer; } KoResourceServer *KoResourceServerProvider::svgSymbolCollectionServer() { return d->svgSymbolCollectionServer; } KoResourceServer* KoResourceServerProvider::gamutMaskServer() { return d->gamutMaskServer; } diff --git a/packaging/linux/flatpak/FLATPAK.md b/packaging/linux/flatpak/FLATPAK.md index adcfd4b7bd..613514777a 100644 --- a/packaging/linux/flatpak/FLATPAK.md +++ b/packaging/linux/flatpak/FLATPAK.md @@ -1,22 +1,18 @@ Krita Flatpak ------------- 1. add the flathub repository: `$ flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo` -2. install the KDE SDK from flathub: +2. compile krita and install it into a local repository: -`$ flatpak install flathub org.kde.Sdk` +`$ flatpak-builder --repo=repo_dir --install-deps-from=flathub --force-clean build_dir org.kde.krita.yaml` -3. compile krita and install it into a local repository: +3. export krita from the local repository to a bundle: -`$ flatpak-builder --repo=repo_dir --force-clean build_dir org.kde.krita-nightly.json` +`$ flatpak build-bundle repo_dir krita--x86_64.flatpak org.kde.krita master` -4. export krita from the local repository to a bundle: +4. install the bundle: -`$ flatpak build-bundle repo_dir krita-nightly-x86_64.flatpak org.kde.krita master` - -5. install the bundle: - -`$ flatpak install krita-nightly-x86_64.flatpak` +`$ flatpak install krita-x86_64.flatpak` diff --git a/packaging/linux/flatpak/org.kde.krita-nightly.yaml b/packaging/linux/flatpak/org.kde.krita-nightly.yaml new file mode 100644 index 0000000000..e201182837 --- /dev/null +++ b/packaging/linux/flatpak/org.kde.krita-nightly.yaml @@ -0,0 +1,280 @@ +app-id: org.kde.krita-nightly +default-branch: master +runtime: org.kde.Platform +runtime-version: '5.12' +sdk: org.kde.Sdk +command: krita +rename-icon: calligrakrita +rename-desktop-file: org.kde.krita.desktop +desktop-file-name-suffix: nightly +finish-args: + - --share=ipc + - --socket=x11 + - --share=network + - --device=dri + - --socket=pulseaudio + - --filesystem=host + - --filesystem=xdg-config/kdeglobals:ro + - --env=PYTHONPATH=/app/lib/python3/dist-packages + - --env=TMPDIR=/var/tmp +cleanup: + - /include + - /lib/pkgconfig + - /lib/cmake + - /share/aclocal + - /share/pkgconfig + - /share/info + - /share/man + - /cmake + - '*.a' + - '*.la' + - '*.cmake' +modules: + - name: sip + buildsystem: simple + build-commands: + - python3 configure.py + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --incdir=/app/include/python3 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages + --sip-module=PyQt5.sip + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/sip/4.19.17/sip-4.19.17.tar.gz + sha256: 12bcd8f4d5feefc105bc075d12c5090ee783f7380728563c91b8b95d0ec45df3 + + - name: pyqt + buildsystem: simple + build-commands: + - python3 configure.py + --confirm-license + --sip-incdir=/app/include/python3 + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --designer-plugindir=/app/lib/plugins/designer + --qml-plugindir=/app/lib/plugins/PyQt5 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages/PyQt5 + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.2/PyQt5_gpl-5.12.2.tar.gz + sha256: c565829e77dc9c281aa1a0cdf2eddaead4e0f844cbaf7a4408441967f03f5f0f + + - name: boost + buildsystem: simple + build-commands: + - ./bootstrap.sh --prefix=/app --with-libraries=system + - ./b2 -j $FLATPAK_BUILDER_N_JOBS install + sources: + - type: archive + url: https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2 + sha256: 8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406 + + - name: eigen + buildsystem: cmake-ninja + builddir: true + cleanup: + - '*' + sources: + - type: archive + url: https://bitbucket.org/eigen/eigen/get/3.3.7.tar.bz2 + sha256: 9f13cf90dedbe3e52a19f43000d71fdf72e986beb9a5436dddcd61ff9d77a3ce + + - name: quazip + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://github.com/stachenov/quazip/archive/0.7.6.tar.gz + sha256: 4118a830a375a81211956611cc34b1b5b4ddc108c126287b91b40c2493046b70 + - type: shell + commands: + - sed -i 's|${CMAKE_ROOT}/Modules|share/cmake|' CMakeLists.txt + + - name: exiv2 + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - /bin + sources: + - type: archive + url: https://www.exiv2.org/builds/exiv2-0.27.1-Source.tar.gz + sha256: f125286980fd1bcb28e188c02a93946951c61e10784720be2301b661a65b3081 + + - name: ilmbase + config-opts: + - --disable-static + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/ilmbase-2.3.0.tar.gz + sha256: 456978d1a978a5f823c7c675f3f36b0ae14dba36638aeaa3c4b0e784f12a3862 + + - name: openexr + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/openexr-2.3.0.tar.gz + sha256: fd6cb3a87f8c1a233be17b94c74799e6241d50fc5efd4df75c7a4b9cf4e25ea6 + + - name: libraw + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://www.libraw.org/data/LibRaw-0.19.2.tar.gz + sha256: 400d47969292291d297873a06fb0535ccce70728117463927ddd9452aa849644 + + - name: opencolorio + buildsystem: cmake # ninja build broken (fixed in 2.0) + builddir: true + build-options: + arch: + arm: + config-opts: + - -DOCIO_USE_SSE=OFF + aarch64: + config-opts: + - -DOCIO_USE_SSE=OFF + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DOCIO_BUILD_STATIC=OFF + - -DCMAKE_CXX_FLAGS='-Wno-error=deprecated-declarations -Wno-error=unused-function -Wno-error=cast-function-type' + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/imageworks/OpenColorIO/archive/v1.1.1.tar.gz + sha256: c9b5b9def907e1dafb29e37336b702fff22cc6306d445a13b1621b8a754c14c8 + + - name: vc + skip-arches: + - aarch64 + - arm + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - '*' + sources: + - type: archive + url: https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz + sha256: 08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe + - type: shell + commands: + - sed -i 's/x86|/x86|i686|/' CMakeLists.txt + + - name: poppler-data + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-data-0.4.9.tar.gz + sha256: 1f9c7e7de9ecd0db6ab287349e31bf815ca108a5a175cf906a90163bdbe32012 + + - name: poppler + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_GTK_TESTS=OFF + - -DBUILD_QT5_TESTS=OFF + - -DBUILD_CPP_TESTS=OFF + - -DENABLE_UTILS=OFF + - -DENABLE_CPP=OFF + - -DENABLE_GLIB=OFF + - -DENABLE_LIBOPENJPEG=none + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-0.77.0.tar.xz + sha256: 7267eb4cbccd64a58244b8211603c1c1b6bf32c7f6a4ced2642865346102f36b + + - name: gsl + config-opts: + - --disable-static + cleanup: + - /bin + sources: + - type: archive + url: https://ftpmirror.gnu.org/gnu/gsl/gsl-2.5.tar.gz + sha256: 0460ad7c2542caaddc6729762952d345374784100223995eb14d614861f2258d + + - name: gmic-qt + buildsystem: cmake-ninja + builddir: true + subdir: gmic-qt + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DGMIC_QT_HOST=krita + - -DGMIC_PATH=../src + - -DCMAKE_CXX_FLAGS=-lfftw3_threads + sources: + - type: archive + url: https://gmic.eu/files/source/gmic_2.6.4.tar.gz + sha256: 4cd88b2dca6b9b1a330ab4556d36656bafb98e4e9814bf0448545b27ef18dae3 + + - name: x264 + config-opts: + - --disable-cli + - --enable-shared + sources: + - type: archive + url: https://download.videolan.org/x264/snapshots/x264-snapshot-20190305-2245-stable.tar.bz2 + sha256: be52c96ef8bd930fbc1ecff03abac9b94976b444ea7641345e08e20d9e594d16 + + - name: ffmpeg + config-opts: + - --enable-rpath + - --enable-gpl + - --disable-static + - --enable-shared + - --disable-doc + - --disable-ffplay + - --disable-ffprobe + - --enable-libopus + - --enable-libvpx + - --enable-libx264 + cleanup: + - /share/ffmpeg/examples + sources: + - type: archive + url: https://www.ffmpeg.org/releases/ffmpeg-4.1.4.tar.xz + sha256: f1f049a82fcfbf156564e73a3935d7e750891fab2abf302e735104fd4050a7e1 + + - name: krita + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_TESTING=OFF + build-options: + env: + PYTHONPATH: /app/lib/python3/dist-packages + post-install: + - rm -r /app/share/icons/hicolor/{1024x1024,scalable} + sources: + - type: git + url: https://anongit.kde.org/krita diff --git a/packaging/linux/flatpak/org.kde.krita-stable.yaml b/packaging/linux/flatpak/org.kde.krita-stable.yaml new file mode 100644 index 0000000000..898d070b66 --- /dev/null +++ b/packaging/linux/flatpak/org.kde.krita-stable.yaml @@ -0,0 +1,280 @@ +app-id: org.kde.krita-stable +runtime: org.kde.Platform +runtime-version: '5.12' +sdk: org.kde.Sdk +command: krita +rename-icon: calligrakrita +rename-desktop-file: org.kde.krita.desktop +desktop-file-name-suffix: stable +finish-args: + - --share=ipc + - --socket=x11 + - --share=network + - --device=dri + - --socket=pulseaudio + - --filesystem=host + - --filesystem=xdg-config/kdeglobals:ro + - --env=PYTHONPATH=/app/lib/python3/dist-packages + - --env=TMPDIR=/var/tmp +cleanup: + - /include + - /lib/pkgconfig + - /lib/cmake + - /share/aclocal + - /share/pkgconfig + - /share/info + - /share/man + - /cmake + - '*.a' + - '*.la' + - '*.cmake' +modules: + - name: sip + buildsystem: simple + build-commands: + - python3 configure.py + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --incdir=/app/include/python3 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages + --sip-module=PyQt5.sip + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/sip/4.19.17/sip-4.19.17.tar.gz + sha256: 12bcd8f4d5feefc105bc075d12c5090ee783f7380728563c91b8b95d0ec45df3 + + - name: pyqt + buildsystem: simple + build-commands: + - python3 configure.py + --confirm-license + --sip-incdir=/app/include/python3 + --bindir=/app/bin + --destdir=/app/lib/python3/dist-packages + --designer-plugindir=/app/lib/plugins/designer + --qml-plugindir=/app/lib/plugins/PyQt5 + --sipdir=/app/share/sip + --stubsdir=/app/lib/python3/dist-packages/PyQt5 + - make -j $FLATPAK_BUILDER_N_JOBS + - make install + cleanup: + - /bin + sources: + - type: archive + url: https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.2/PyQt5_gpl-5.12.2.tar.gz + sha256: c565829e77dc9c281aa1a0cdf2eddaead4e0f844cbaf7a4408441967f03f5f0f + + - name: boost + buildsystem: simple + build-commands: + - ./bootstrap.sh --prefix=/app --with-libraries=system + - ./b2 -j $FLATPAK_BUILDER_N_JOBS install + sources: + - type: archive + url: https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2 + sha256: 8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406 + + - name: eigen + buildsystem: cmake-ninja + builddir: true + cleanup: + - '*' + sources: + - type: archive + url: https://bitbucket.org/eigen/eigen/get/3.3.7.tar.bz2 + sha256: 9f13cf90dedbe3e52a19f43000d71fdf72e986beb9a5436dddcd61ff9d77a3ce + + - name: quazip + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://github.com/stachenov/quazip/archive/0.7.6.tar.gz + sha256: 4118a830a375a81211956611cc34b1b5b4ddc108c126287b91b40c2493046b70 + - type: shell + commands: + - sed -i 's|${CMAKE_ROOT}/Modules|share/cmake|' CMakeLists.txt + + - name: exiv2 + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - /bin + sources: + - type: archive + url: https://www.exiv2.org/builds/exiv2-0.27.1-Source.tar.gz + sha256: f125286980fd1bcb28e188c02a93946951c61e10784720be2301b661a65b3081 + + - name: ilmbase + config-opts: + - --disable-static + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/ilmbase-2.3.0.tar.gz + sha256: 456978d1a978a5f823c7c675f3f36b0ae14dba36638aeaa3c4b0e784f12a3862 + + - name: openexr + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://github.com/openexr/openexr/releases/download/v2.3.0/openexr-2.3.0.tar.gz + sha256: fd6cb3a87f8c1a233be17b94c74799e6241d50fc5efd4df75c7a4b9cf4e25ea6 + + - name: libraw + config-opts: + - --disable-static + cleanup: + - /bin + - /share/doc + sources: + - type: archive + url: https://www.libraw.org/data/LibRaw-0.19.2.tar.gz + sha256: 400d47969292291d297873a06fb0535ccce70728117463927ddd9452aa849644 + + - name: opencolorio + buildsystem: cmake # ninja build broken (fixed in 2.0) + builddir: true + build-options: + arch: + arm: + config-opts: + - -DOCIO_USE_SSE=OFF + aarch64: + config-opts: + - -DOCIO_USE_SSE=OFF + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DOCIO_BUILD_STATIC=OFF + - -DCMAKE_CXX_FLAGS='-Wno-error=deprecated-declarations -Wno-error=unused-function -Wno-error=cast-function-type' + cleanup: + - /bin + sources: + - type: archive + url: https://github.com/imageworks/OpenColorIO/archive/v1.1.1.tar.gz + sha256: c9b5b9def907e1dafb29e37336b702fff22cc6306d445a13b1621b8a754c14c8 + + - name: vc + skip-arches: + - aarch64 + - arm + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + cleanup: + - '*' + sources: + - type: archive + url: https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz + sha256: 08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe + - type: shell + commands: + - sed -i 's/x86|/x86|i686|/' CMakeLists.txt + + - name: poppler-data + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-data-0.4.9.tar.gz + sha256: 1f9c7e7de9ecd0db6ab287349e31bf815ca108a5a175cf906a90163bdbe32012 + + - name: poppler + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_GTK_TESTS=OFF + - -DBUILD_QT5_TESTS=OFF + - -DBUILD_CPP_TESTS=OFF + - -DENABLE_UTILS=OFF + - -DENABLE_CPP=OFF + - -DENABLE_GLIB=OFF + - -DENABLE_LIBOPENJPEG=none + sources: + - type: archive + url: https://poppler.freedesktop.org/poppler-0.77.0.tar.xz + sha256: 7267eb4cbccd64a58244b8211603c1c1b6bf32c7f6a4ced2642865346102f36b + + - name: gsl + config-opts: + - --disable-static + cleanup: + - /bin + sources: + - type: archive + url: https://ftpmirror.gnu.org/gnu/gsl/gsl-2.5.tar.gz + sha256: 0460ad7c2542caaddc6729762952d345374784100223995eb14d614861f2258d + + - name: gmic-qt + buildsystem: cmake-ninja + builddir: true + subdir: gmic-qt + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DGMIC_QT_HOST=krita + - -DGMIC_PATH=../src + - -DCMAKE_CXX_FLAGS=-lfftw3_threads + sources: + - type: archive + url: https://gmic.eu/files/source/gmic_2.6.4.tar.gz + sha256: 4cd88b2dca6b9b1a330ab4556d36656bafb98e4e9814bf0448545b27ef18dae3 + + - name: x264 + config-opts: + - --disable-cli + - --enable-shared + sources: + - type: archive + url: https://download.videolan.org/x264/snapshots/x264-snapshot-20190305-2245-stable.tar.bz2 + sha256: be52c96ef8bd930fbc1ecff03abac9b94976b444ea7641345e08e20d9e594d16 + + - name: ffmpeg + config-opts: + - --enable-rpath + - --enable-gpl + - --disable-static + - --enable-shared + - --disable-doc + - --disable-ffplay + - --disable-ffprobe + - --enable-libopus + - --enable-libvpx + - --enable-libx264 + cleanup: + - /share/ffmpeg/examples + sources: + - type: archive + url: https://www.ffmpeg.org/releases/ffmpeg-4.1.4.tar.xz + sha256: f1f049a82fcfbf156564e73a3935d7e750891fab2abf302e735104fd4050a7e1 + + - name: krita + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_TESTING=OFF + build-options: + env: + PYTHONPATH: /app/lib/python3/dist-packages + post-install: + - rm -r /app/share/icons/hicolor/{1024x1024,scalable} + sources: + - type: git + url: https://anongit.kde.org/krita + branch: krita/4.2 diff --git a/packaging/linux/snap/snap/gui/krita.desktop b/packaging/linux/snap/snap/gui/krita.desktop index e28dfab4ea..33e10f2bcd 100755 --- a/packaging/linux/snap/snap/gui/krita.desktop +++ b/packaging/linux/snap/snap/gui/krita.desktop @@ -1,143 +1,143 @@ [Desktop Entry] Name=Krita Name[af]=Krita Name[ar]=كريتا Name[bg]=Krita Name[br]=Krita Name[bs]=Krita Name[ca]=Krita Name[ca@valencia]=Krita Name[cs]=Krita Name[cy]=Krita Name[da]=Krita Name[de]=Krita Name[el]=Krita Name[en_GB]=Krita Name[eo]=Krita Name[es]=Krita Name[et]=Krita Name[eu]=Krita Name[fi]=Krita Name[fr]=Krita Name[fy]=Krita Name[ga]=Krita Name[gl]=Krita Name[he]=Krita Name[hi]=केरिता Name[hne]=केरिता Name[hr]=Krita Name[hu]=Krita Name[ia]=Krita Name[is]=Krita Name[it]=Krita Name[ja]=Krita Name[kk]=Krita Name[ko]=Krita Name[lt]=Krita Name[lv]=Krita Name[mr]=क्रिटा Name[ms]=Krita Name[nb]=Krita Name[nds]=Krita Name[ne]=क्रिता Name[nl]=Krita Name[nn]=Krita Name[pl]=Krita Name[pt]=Krita Name[pt_BR]=Krita Name[ro]=Krita Name[ru]=Krita Name[se]=Krita Name[sk]=Krita Name[sl]=Krita Name[sv]=Krita Name[ta]=கிரிட்டா Name[tg]=Krita Name[tr]=Krita Name[ug]=Krita Name[uk]=Krita Name[uz]=Krita Name[uz@cyrillic]=Krita Name[wa]=Krita Name[xh]=Krita Name[x-test]=xxKritaxx Name[zh_CN]=Krita Name[zh_TW]=Krita Exec=krita %F GenericName=Digital Painting GenericName[ar]=رسم رقمي GenericName[bs]=Digitalno Bojenje GenericName[ca]=Dibuix digital GenericName[ca@valencia]=Dibuix digital GenericName[cs]=Digitální malování GenericName[da]=Digital tegning GenericName[de]=Digitales Malen GenericName[el]=Ψηφιακή ζωγραφική GenericName[en_GB]=Digital Painting GenericName[es]=Pintura digital GenericName[et]=Digitaalne joonistamine GenericName[eu]=Margolan digitala GenericName[fi]=Digitaalimaalaus GenericName[fr]=Peinture numérique GenericName[gl]=Debuxo dixital GenericName[hu]=Digitális festészet GenericName[ia]=Pintura Digital GenericName[is]=Stafræn málun GenericName[it]=Pittura digitale GenericName[ja]=デジタルペインティング GenericName[kk]=Цифрлық сурет салу GenericName[ko]=디지털 페인팅 GenericName[lt]=Skaitmeninis piešimas GenericName[mr]=डिजिटल पेंटिंग GenericName[nb]=Digital maling GenericName[nl]=Digitaal schilderen GenericName[nn]=Digital teikning GenericName[pl]=Cyfrowe malowanie GenericName[pt]=Pintura Digital GenericName[pt_BR]=Pintura digital GenericName[ru]=Цифровая живопись GenericName[sk]=Digitálne maľovanie GenericName[sl]=Digitalno slikanje GenericName[sv]=Digital målning GenericName[tr]=Sayısal Boyama GenericName[ug]=سىفىرلىق رەسىم سىزغۇ GenericName[uk]=Цифрове малювання GenericName[x-test]=xxDigital Paintingxx GenericName[zh_CN]=数字绘画 GenericName[zh_TW]=數位繪畫 MimeType=application/x-krita;image/openraster;application/x-krita-paintoppreset; Comment=Pixel-based image manipulation program for the Calligra Suite Comment[ar]=برنامج لتعديل الصور البكسليّة لطقم «كاليغرا» Comment[ca]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra Comment[ca@valencia]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra Comment[de]=Pixelbasiertes Bildbearbeitungsprogramm für die Calligra-Suite Comment[el]=Πρόγραμμα επεξεργασίας εικόνας με βάση εικονοστοιχεία για το Calligra Stage Comment[en_GB]=Pixel-based image manipulation program for the Calligra Suite Comment[es]=Programa de manipulación de imágenes basado en píxeles para la suite Calligra Comment[et]=Calligra pikslipõhine pilditöötluse rakendus Comment[eu]=Pixel-oinarridun irudiak manipulatzeko programa Calligra-Suiterako Comment[fi]=Bittikarttakuvankäsittelyohjelma Calligra-toimisto-ohjelmistoon Comment[gl]=Programa da colección de Calligra para a manipulación de imaxes baseadas en píxeles. Comment[is]=Myndvinnsluforrit fyrir Calligra-forritavöndulinn Comment[it]=Programma di manipolazione delle immagini basato su pixel per Calligra Suite Comment[ko]=Calligra Suite를 위한 픽셀 기반 이미지 처리 프로그램 Comment[nl]=Afbeeldingsbewerkingsprogramma gebaseerd op pixels voor de Calligra Suite Comment[nn]=Pikselbasert teikneprogram for Calligra Comment[pl]=Program do obróbki obrazów na poziomie pikseli dla Pakietu Calligra Comment[pt]='Plugin' de manipulação de imagens em pixels para o Calligra Stage Comment[pt_BR]=Programa de manipulação de imagens baseado em pixels para o Calligra Suite Comment[ru]=Программа редактирования пиксельной анимации для the Calligra Suite Comment[sk]=Program na manipuláciu s pixelmi pre Calligra Suite Comment[sv]=Bildpunktsbaserat bildbehandlingsprogram för Calligra-sviten Comment[tr]=Calligra Suite için Pixel tabanlı görüntü düzenleme programı Comment[uk]=Програма для роботи із растровими зображеннями для комплексу програм Calligra Comment[x-test]=xxPixel-based image manipulation program for the Calligra Suitexx Comment[zh_CN]=Calligra 套件的像素图像处理程序 Comment[zh_TW]=Calligra 套件中基於像素的影像處理程式 Type=Application Icon=${SNAP}/meta/gui/calligrakrita.png -Categories=Qt;KDE;Graphics; +Categories=Qt;KDE;Graphics;2DGraphics;RasterGraphics; X-KDE-NativeMimeType=application/x-krita X-KDE-ExtraNativeMimeTypes= StartupNotify=true X-Krita-Version=28 diff --git a/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h b/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h index ffeff923e4..97dbe3eb69 100644 --- a/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h +++ b/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h @@ -1,75 +1,76 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * 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.1 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; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISANIMATIONRENDERINGOPTIONS_H #define KISANIMATIONRENDERINGOPTIONS_H #include #include "kis_properties_configuration.h" -struct KisAnimationRenderingOptions +class KisAnimationRenderingOptions { +public: KisAnimationRenderingOptions(); QString lastDocuemntPath; QString videoMimeType; QString frameMimeType; QString basename; QString directory; int firstFrame = 0; int lastFrame = 0; int sequenceStart = 0; bool shouldEncodeVideo = false; bool shouldDeleteSequence = false; bool includeAudio = false; QString ffmpegPath; int frameRate = 25; int width = 0; int height = 0; QString videoFileName; QString customFFMpegOptions; KisPropertiesConfigurationSP frameExportConfig; QString resolveAbsoluteDocumentFilePath(const QString &documentPath) const; QString resolveAbsoluteVideoFilePath(const QString &documentPath) const; QString resolveAbsoluteFramesDirectory(const QString &documentPath) const; QString resolveAbsoluteVideoFilePath() const; QString resolveAbsoluteFramesDirectory() const; enum RenderMode { RENDER_FRAMES_ONLY, RENDER_VIDEO_ONLY, RENDER_FRAMES_AND_VIDEO }; RenderMode renderMode() const; KisPropertiesConfigurationSP toProperties() const; void fromProperties(KisPropertiesConfigurationSP config); }; #endif // KISANIMATIONRENDERINGOPTIONS_H diff --git a/plugins/extensions/animationrenderer/VideoHDRMetadataOptionsDialog.h b/plugins/extensions/animationrenderer/VideoHDRMetadataOptionsDialog.h index 98232d2e70..ae16369e58 100644 --- a/plugins/extensions/animationrenderer/VideoHDRMetadataOptionsDialog.h +++ b/plugins/extensions/animationrenderer/VideoHDRMetadataOptionsDialog.h @@ -1,52 +1,52 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * 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.1 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; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef VIDEOHDRMETADATAOPTIONSDIALOG_H #define VIDEOHDRMETADATAOPTIONSDIALOG_H #include #include "kis_types.h" -class KisHDRMetadataOptions; +struct KisHDRMetadataOptions; namespace Ui { class VideoHDRMetadataOptionsDialog; } class VideoHDRMetadataOptionsDialog : public QDialog { Q_OBJECT public: explicit VideoHDRMetadataOptionsDialog(QWidget *parent = nullptr); ~VideoHDRMetadataOptionsDialog(); void setHDRMetadataOptions(const KisHDRMetadataOptions &options); KisHDRMetadataOptions hdrMetadataOptions() const; private Q_SLOTS: void slotPredefinedDisplayIdChanged(); private: Ui::VideoHDRMetadataOptionsDialog *ui; }; #endif // VIDEOHDRMETADATAOPTIONSDIALOG_H diff --git a/plugins/extensions/metadataeditor/editors/exif.ui b/plugins/extensions/metadataeditor/editors/exif.ui index d0d3a518b5..d9c2a81dce 100644 --- a/plugins/extensions/metadataeditor/editors/exif.ui +++ b/plugins/extensions/metadataeditor/editors/exif.ui @@ -1,1109 +1,1109 @@ Exif 0 0 559 483 0 Exposure Brightness &value: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editBrightnessValue &ISO: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editISO 3600 100 100 Exposure &time: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editExposureTime E&xposure mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editExposureMode Auto Manual Auto bracket Exposure pro&gram: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editExposureProgram Not defined Manual Normal program Aperture priority Shutter priority Creative program Action program Portrait mode Landscape mode Exposure index: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editExposureIndex Exposure bias: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editExposureBiasValue Ape&rture: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editApertureValue Shutter speed: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editShutterSpeedValue &F Number: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editFNumber Qt::Vertical 517 51 Lens &Focal length: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editFocalLength 1000 1 35 Focal length (&35mm equivalent): Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editFocalLengthIn35mmFilm 1000 1 35 Max aperture: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editMaxApertureValue Qt::Vertical 517 101 Autofocus Sub&ject distance: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editSubjectDistance Meterin&g mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editMeteringMode Unknown Average Center weighted average - Spot + Spot Multi spot Pattern Partial Other D&istance range: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editSubjectDistanceRange Unknown Macro Close view Distant view Qt::Vertical 517 151 Flash Fired Stro&be return: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editStrobeReturn No strobe return detection Undefined No strobe return light detected Strobe return light detected Mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editFlashMode Unknown Compulsory flash fired Compulsory flash suppression Auto mode Function Red-eye removal Flash ener&gy: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editFlashEnergy Qt::Vertical 517 91 Postprocessing &Gain control: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editGainControl 1 0 None Low gain up High gain up Low gain down High gain down L&ight source: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editLightSource Unknown Daylight Fluorescent Tungsten Flash Undefined Undefined Undefined Undefined Fine weather Cloudy weather Shade Daylight fluorescent (D5700 - 7100K) Day white fluorescent (N4600 - 5400K) Cool white fluorescent (W3900 - 4500K) White fluorescent (WW 3200 - 3700K) Undefined Standard light A Standard light B Standard light C D55 D65 D75 D50 ISO studio tungsten other Sharpness: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editSharpness Normal Soft Hard Contrast: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editContrast Normal Soft Hard White &balance: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editWhiteBalance Auto Custom Qt::Vertical 517 91 Misc Scene capture t&ype: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editSceneCaptureType Standard - Landscape + Landscape - Portrait + Portrait Night scene Ma&ker: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editMake Model: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editModel Sens&ing method type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter editSensingMethod Not Defined Not Defined One-chip color area sensor Two-chip color area sensor Three-chip color area sensor Color sequential area sensor Trilinear sensor Color sequential linear sensor Qt::Vertical 517 71 KisIntParseSpinBox QSpinBox
    kis_int_parse_spin_box.h
    diff --git a/plugins/extensions/separate_channels/dlg_separate.cc b/plugins/extensions/separate_channels/dlg_separate.cc index ea9fb296bc..582e0c28ac 100644 --- a/plugins/extensions/separate_channels/dlg_separate.cc +++ b/plugins/extensions/separate_channels/dlg_separate.cc @@ -1,116 +1,124 @@ /* * dlg_separate.cc - part of KimageShop^WKrayon^WKrita * * Copyright (c) 2004 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_separate.h" #include #include #include #include #include #include DlgSeparate::DlgSeparate(const QString & imageCS, const QString & layerCS, QWidget * parent, const char * name) : KoDialog(parent) , m_imageCS(imageCS) , m_layerCS(layerCS) { setObjectName(name); setCaption(i18n("Separate Image")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgSeparations(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); resize(m_page->sizeHint()); m_page->lblColormodel->setText(layerCS); m_page->grpOutput->hide(); connect(m_page->radioCurrentLayer, SIGNAL(toggled(bool)), this, SLOT(slotSetColorSpaceLabel())); connect(m_page->radioAllLayers, SIGNAL(toggled(bool)), this, SLOT(slotSetColorSpaceLabel())); - connect(m_page->chkColors, SIGNAL(toggled(bool)), m_page->chkDownscale, SLOT(setDisabled(bool))); + connect(m_page->chkColors, SIGNAL(toggled(bool)), this, SLOT(disableDownScaleIfPossible(bool))); connect(this, SIGNAL(okClicked()), this, SLOT(okClicked())); } DlgSeparate::~DlgSeparate() { delete m_page; } enumSepAlphaOptions DlgSeparate::getAlphaOptions() { if (m_page->radioCopyAlpha->isChecked()) return COPY_ALPHA_TO_SEPARATIONS; if (m_page->radioDiscardAlpha->isChecked()) return DISCARD_ALPHA; if (m_page->radioSeparateAlpha->isChecked()) return CREATE_ALPHA_SEPARATION; return COPY_ALPHA_TO_SEPARATIONS; } enumSepSource DlgSeparate::getSource() { if (m_page->radioCurrentLayer->isChecked()) return CURRENT_LAYER; if (m_page->radioAllLayers->isChecked()) return ALL_LAYERS; //if (XXX) return VISIBLE_LAYERS; return CURRENT_LAYER; } enumSepOutput DlgSeparate::getOutput() { if (m_page->radioLayers->isChecked()) return TO_LAYERS; if (m_page->radioImages->isChecked()) return TO_IMAGES; return TO_LAYERS; } bool DlgSeparate::getDownscale() { return m_page->chkDownscale->isChecked(); } bool DlgSeparate::getToColor() { return m_page->chkColors->isChecked(); } void DlgSeparate::okClicked() { accept(); } +void DlgSeparate::disableDownScaleIfPossible(bool disable) +{ + if (m_canDownScale) { + m_page->chkDownscale->setDisabled(disable); + } +} + void DlgSeparate::slotSetColorSpaceLabel() { if (m_page->radioCopyAlpha->isChecked()) { m_page->lblColormodel->setText(m_layerCS); } else if (m_page->radioAllLayers->isChecked()) { m_page->lblColormodel->setText(m_imageCS); } } void DlgSeparate::enableDownscale(bool enable) { + m_canDownScale = enable; m_page->chkDownscale->setEnabled(enable); } diff --git a/plugins/extensions/separate_channels/dlg_separate.h b/plugins/extensions/separate_channels/dlg_separate.h index 020a628325..29acc922ea 100644 --- a/plugins/extensions/separate_channels/dlg_separate.h +++ b/plugins/extensions/separate_channels/dlg_separate.h @@ -1,75 +1,77 @@ /* * dlg_imagesize.h -- part of KimageShop^WKrayon^WKrita * * Copyright (c) 2005 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DLG_SEPARATE #define DLG_SEPARATE #include #include #include "ui_wdg_separations.h" class WdgSeparations : public QWidget, public Ui::WdgSeparations { public: WdgSeparations(QWidget *parent) : QWidget(parent) { setupUi(this); } }; /** * This dialog allows the user to configure the decomposition of an image * into layers: one layer for each color channel. */ class DlgSeparate: public KoDialog { Q_OBJECT public: DlgSeparate(const QString & imageCS, const QString & layerCS, QWidget * parent = 0, const char* name = 0); ~DlgSeparate() override; public: enumSepAlphaOptions getAlphaOptions(); enumSepSource getSource(); enumSepOutput getOutput(); bool getDownscale(); void enableDownscale(bool enable); bool getToColor(); private Q_SLOTS: void slotSetColorSpaceLabel(); void okClicked(); + void disableDownScaleIfPossible(bool disable); private: WdgSeparations * m_page; QString m_imageCS; QString m_layerCS; + bool m_canDownScale {true}; }; #endif // DLG_SEPARATE diff --git a/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp b/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp index 942ad93fbe..41dbcae7c0 100644 --- a/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp +++ b/plugins/filters/dodgeburn/DodgeBurnPlugin.cpp @@ -1,39 +1,39 @@ /* * Copyright (c) 2009 Cyrille Berger * * 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; 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DodgeBurnPlugin.h" #include #include #include "DodgeBurn.h" K_PLUGIN_FACTORY_WITH_JSON(DodgeBurnPluginFactory, "kritadodgeburn.json", registerPlugin();) DodgeBurnPlugin::DodgeBurnPlugin(QObject *parent, const QVariantList &) { Q_UNUSED(parent); - KisFilterRegistry::instance()->add(new KisFilterDodgeBurn("dodge", "Dodge", i18n("Dodge"))); - KisFilterRegistry::instance()->add(new KisFilterDodgeBurn("burn", "Burn", i18n("Burn"))); + KisFilterRegistry::instance()->add(new KisFilterDodgeBurn("dodge", "Dodge", i18n("Dodge..."))); + KisFilterRegistry::instance()->add(new KisFilterDodgeBurn("burn", "Burn", i18n("Burn..."))); } DodgeBurnPlugin::~DodgeBurnPlugin() { } #include "DodgeBurnPlugin.moc" diff --git a/plugins/filters/noisefilter/noisefilter.h b/plugins/filters/noisefilter/noisefilter.h index 217c237ea7..22cdcfc5cb 100644 --- a/plugins/filters/noisefilter/noisefilter.h +++ b/plugins/filters/noisefilter/noisefilter.h @@ -1,56 +1,56 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef NOISEFILTER_H #define NOISEFILTER_H #include #include #include "filter/kis_filter.h" class KisConfigWidget; class KritaNoiseFilter : public QObject { Q_OBJECT public: KritaNoiseFilter(QObject *parent, const QVariantList &); ~KritaNoiseFilter() override; }; class KisFilterNoise : public KisFilter { public: KisFilterNoise(); public: void processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const override; static inline KoID id() { - return KoID("noise", i18n("Noise")); + return KoID("noise", i18n("Random Noise")); } KisFilterConfigurationSP factoryConfiguration() const override; KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool useForMasks) const override; }; #endif diff --git a/plugins/filters/normalize/kis_normalize.cpp b/plugins/filters/normalize/kis_normalize.cpp index 75eb322d8f..437c534470 100644 --- a/plugins/filters/normalize/kis_normalize.cpp +++ b/plugins/filters/normalize/kis_normalize.cpp @@ -1,137 +1,137 @@ /* * * Copyright (c) 2015 Wolthera van Hövell tot Westerflier * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_normalize.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KritaNormalizeFilterFactory, "kritanormalize.json", registerPlugin();) KritaNormalizeFilter::KritaNormalizeFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisFilterNormalize())); } KritaNormalizeFilter::~KritaNormalizeFilter() { } KisFilterNormalize::KisFilterNormalize() : KisColorTransformationFilter(KoID("normalize", i18n("Normalize")), - FiltersCategoryMapId, i18n("&Normalize...")) + FiltersCategoryMapId, i18n("&Normalize")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); setShowConfigurationWidget(false); } KoColorTransformation* KisFilterNormalize::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { Q_UNUSED(config); return new KisNormalizeTransformation(cs); } KisNormalizeTransformation::KisNormalizeTransformation(const KoColorSpace* cs) : m_colorSpace(cs), m_psize(cs->pixelSize()) { } void KisNormalizeTransformation::transform(const quint8* src, quint8* dst, qint32 nPixels) const { // if the color space is not RGBA o something like that, just // pass the values through if (m_colorSpace->channelCount() != 4) { memcpy(dst, src, nPixels * m_colorSpace->pixelSize()); return; } QVector3D normal_vector; QVector channelValues(4); //if (m_colorSpace->colorDepthId().id()!="F16" && m_colorSpace->colorDepthId().id()!="F32" && m_colorSpace->colorDepthId().id()!="F64") { /* I don't know why, but the results of this are unexpected with a floating point space. * And manipulating the pixels gives strange results. */ while (nPixels--) { m_colorSpace->normalisedChannelsValue(src, channelValues); normal_vector.setX(channelValues[2]*2-1.0); normal_vector.setY(channelValues[1]*2-1.0); normal_vector.setZ(channelValues[0]*2-1.0); normal_vector.normalize(); channelValues[0]=normal_vector.z()*0.5+0.5; channelValues[1]=normal_vector.y()*0.5+0.5; channelValues[2]=normal_vector.x()*0.5+0.5; //channelValues[3]=1.0; m_colorSpace->fromNormalisedChannelsValue(dst, channelValues); dst[3]=src[3]; src += m_psize; dst += m_psize; } /* } else { while (nPixels--) { m_colorSpace->normalisedChannelsValue(src, channelValues); qreal max = qMax(channelValues[2], qMax(channelValues[1], channelValues[0])); qreal min = qMin(channelValues[2], qMin(channelValues[1], channelValues[0])); qreal range = max-min; normal_vector.setX( ((channelValues[2]-min)/range) *2.0-1.0); normal_vector.setY( ((channelValues[1]-min)/range) *2.0-1.0); normal_vector.setZ( ((channelValues[0]-min)/range) *2.0-1.0); normal_vector.normalize(); channelValues[2]=normal_vector.x()*0.5+0.5; channelValues[1]=normal_vector.y()*0.5+0.5; channelValues[0]=normal_vector.z()*0.5+0.5; //channelValues[3]=1.0; m_colorSpace->fromNormalisedChannelsValue(dst, channelValues); dst[3]=src[3]; //hack to trunucate values. m_colorSpace->toRgbA16(dst, reinterpret_cast(m_rgba), 1); m_colorSpace->fromRgbA16(reinterpret_cast(m_rgba), dst, 1); src += m_psize; dst += m_psize; } }*/ } #include "kis_normalize.moc" diff --git a/plugins/filters/palettize/palettize.cpp b/plugins/filters/palettize/palettize.cpp index e4742a7649..57eb9a14f8 100644 --- a/plugins/filters/palettize/palettize.cpp +++ b/plugins/filters/palettize/palettize.cpp @@ -1,287 +1,288 @@ /* * This file is part of Krita * * Copyright (c) 2019 Carl Olsson * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "palettize.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(PalettizeFactory, "kritapalettize.json", registerPlugin();) Palettize::Palettize(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(new KisFilterPalettize()); } #include "palettize.moc" KisFilterPalettize::KisFilterPalettize() : KisFilter(id(), FiltersCategoryMapId, i18n("&Palettize...")) { setColorSpaceIndependence(FULLY_INDEPENDENT); setSupportsPainting(true); setShowConfigurationWidget(true); } KisPalettizeWidget::KisPalettizeWidget(QWidget* parent) : KisConfigWidget(parent) { + Q_UNUSED(m_ditherPatternWidget); setupUi(this); paletteIconWidget->setFixedSize(32, 32); KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); QSharedPointer paletteAdapter(new KoResourceServerAdapter(paletteServer)); m_paletteWidget = new KoResourceItemChooser(paletteAdapter, this, false); paletteIconWidget->setPopupWidget(m_paletteWidget); QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, paletteIconWidget, &KisIconWidget::setResource); QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(colorspaceComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(ditherGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(ditherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(colorModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); offsetScaleSpinBox->setPrefix(QString("%1 ").arg(i18n("Offset Scale:"))); offsetScaleSpinBox->setRange(0.0, 1.0, 3); offsetScaleSpinBox->setSingleStep(0.125); QObject::connect(offsetScaleSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(alphaGroupBox, &QGroupBox::toggled, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(alphaModeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &KisConfigWidget::sigConfigurationItemChanged); alphaClipSpinBox->setPrefix(QString("%1 ").arg(i18n("Clip:"))); alphaClipSpinBox->setRange(0.0, 1.0, 3); alphaClipSpinBox->setSingleStep(0.125); QObject::connect(alphaClipSpinBox, &KisDoubleSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); alphaIndexSpinBox->setPrefix(QString("%1 ").arg(i18n("Index:"))); alphaIndexSpinBox->setRange(0, 255); QObject::connect(alphaIndexSpinBox, &KisSliderSpinBox::valueChanged, this, &KisConfigWidget::sigConfigurationItemChanged); QObject::connect(m_paletteWidget, &KoResourceItemChooser::resourceSelected, [this](){ const KoColorSet* const palette = static_cast(m_paletteWidget->currentResource()); alphaIndexSpinBox->setMaximum(palette ? int(palette->colorCount() - 1) : 0); alphaIndexSpinBox->setValue(std::min(alphaIndexSpinBox->value(), alphaIndexSpinBox->maximum())); }); QObject::connect(alphaDitherWidget, &KisDitherWidget::sigConfigurationItemChanged, this, &KisConfigWidget::sigConfigurationItemChanged); } void KisPalettizeWidget::setConfiguration(const KisPropertiesConfigurationSP config) { KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette")); if (palette) m_paletteWidget->setCurrentResource(palette); colorspaceComboBox->setCurrentIndex(config->getInt("colorspace")); ditherGroupBox->setChecked(config->getBool("ditherEnabled")); ditherWidget->setConfiguration(*config, "dither/"); colorModeComboBox->setCurrentIndex(config->getInt("dither/colorMode")); offsetScaleSpinBox->setValue(config->getDouble("dither/offsetScale")); alphaGroupBox->setChecked(config->getBool("alphaEnabled")); alphaModeComboBox->setCurrentIndex(config->getInt("alphaMode")); alphaClipSpinBox->setValue(config->getDouble("alphaClip")); alphaIndexSpinBox->setValue(config->getInt("alphaIndex")); alphaDitherWidget->setConfiguration(*config, "alphaDither/"); } KisPropertiesConfigurationSP KisPalettizeWidget::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1); if (m_paletteWidget->currentResource()) config->setProperty("palette", QVariant(m_paletteWidget->currentResource()->name())); config->setProperty("colorspace", colorspaceComboBox->currentIndex()); config->setProperty("ditherEnabled", ditherGroupBox->isChecked()); ditherWidget->configuration(*config, "dither/"); config->setProperty("dither/colorMode", colorModeComboBox->currentIndex()); config->setProperty("dither/offsetScale", offsetScaleSpinBox->value()); config->setProperty("alphaEnabled", alphaGroupBox->isChecked()); config->setProperty("alphaMode", alphaModeComboBox->currentIndex()); config->setProperty("alphaClip", alphaClipSpinBox->value()); config->setProperty("alphaIndex", alphaIndexSpinBox->value()); alphaDitherWidget->configuration(*config, "alphaDither/"); return config; } KisConfigWidget* KisFilterPalettize::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const { Q_UNUSED(dev) Q_UNUSED(useForMasks) return new KisPalettizeWidget(parent); } KisFilterConfigurationSP KisFilterPalettize::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("palettize", 1); config->setProperty("palette", "Default"); config->setProperty("colorspace", Colorspace::Lab); config->setProperty("ditherEnabled", false); KisDitherWidget::factoryConfiguration(*config, "dither/"); config->setProperty("dither/colorMode", ColorMode::PerChannelOffset); config->setProperty("dither/offsetScale", 0.125); config->setProperty("alphaEnabled", true); config->setProperty("alphaMode", AlphaMode::Clip); config->setProperty("alphaClip", 0.5); config->setProperty("alphaIndex", 0); KisDitherWidget::factoryConfiguration(*config, "alphaDither/"); return config; } void KisFilterPalettize::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { const KoColorSet* palette = KoResourceServerProvider::instance()->paletteServer()->resourceByName(config->getString("palette")); const int searchColorspace = config->getInt("colorspace"); const bool ditherEnabled = config->getBool("ditherEnabled"); const int colorMode = config->getInt("dither/colorMode"); const double offsetScale = config->getDouble("dither/offsetScale"); const bool alphaEnabled = config->getBool("alphaEnabled"); const int alphaMode = config->getInt("alphaMode"); const double alphaClip = config->getDouble("alphaClip"); const int alphaIndex = config->getInt("alphaIndex"); const KoColorSpace* colorspace = device->colorSpace(); const KoColorSpace* workColorspace = (searchColorspace == Colorspace::Lab ? KoColorSpaceRegistry::instance()->lab16() : KoColorSpaceRegistry::instance()->rgb16("sRGB-elle-V2-srgbtrc.icc")); const quint8 colorCount = ditherEnabled && colorMode == ColorMode::NearestColors ? 2 : 1; using SearchColor = boost::geometry::model::point; struct ColorCandidate { KoColor color; quint16 index; double distance; }; using SearchEntry = std::pair; boost::geometry::index::rtree> rtree; if (palette) { // Add palette colors to search tree quint16 index = 0; for (int row = 0; row < palette->rowCount(); ++row) { for (int column = 0; column < palette->columnCount(); ++column) { KisSwatch swatch = palette->getColorGlobal(column, row); if (swatch.isValid()) { KoColor color = swatch.color().convertedTo(colorspace); KoColor workColor = swatch.color().convertedTo(workColorspace); SearchColor searchColor; memcpy(&searchColor, workColor.data(), sizeof(SearchColor)); // Don't add duplicates so won't dither between identical colors std::vector result; rtree.query(boost::geometry::index::contains(searchColor), std::back_inserter(result)); if (result.empty()) rtree.insert(SearchEntry(searchColor, {color, index, 0.0})); } ++index; } } KisDitherUtil ditherUtil; if (ditherEnabled) ditherUtil.setConfiguration(*config, "dither/"); KisDitherUtil alphaDitherUtil; if (alphaMode == AlphaMode::Dither) alphaDitherUtil.setConfiguration(*config, "alphaDither/"); KisSequentialIteratorProgress pixel(device, applyRect, progressUpdater); while (pixel.nextPixel()) { KoColor workColor(pixel.oldRawData(), colorspace); workColor.convertTo(workColorspace); // Find dither threshold double threshold = 0.5; if (ditherEnabled) { threshold = ditherUtil.threshold(QPoint(pixel.x(), pixel.y())); // Traditional per-channel ordered dithering if (colorMode == ColorMode::PerChannelOffset) { QVector normalized(int(workColorspace->channelCount())); workColorspace->normalisedChannelsValue(workColor.data(), normalized); for (int channel = 0; channel < int(workColorspace->channelCount()); ++channel) { normalized[channel] += (threshold - 0.5) * offsetScale; } workColorspace->fromNormalisedChannelsValue(workColor.data(), normalized); } } // Get candidate colors and their distances SearchColor searchColor; memcpy(reinterpret_cast(&searchColor), workColor.data(), sizeof(SearchColor)); std::vector candidateColors; candidateColors.reserve(size_t(colorCount)); double distanceSum = 0.0; for (auto it = rtree.qbegin(boost::geometry::index::nearest(searchColor, colorCount)); it != rtree.qend() && candidateColors.size() < colorCount; ++it) { ColorCandidate candidate = it->second; candidate.distance = boost::geometry::distance(searchColor, it->first); candidateColors.push_back(candidate); distanceSum += candidate.distance; } // Select color candidate quint16 selected; if (ditherEnabled && colorMode == ColorMode::NearestColors) { // Sort candidates by palette order for stable dither color ordering const bool swap = candidateColors[0].index > candidateColors[1].index; selected = swap ^ (candidateColors[swap].distance / distanceSum > threshold); } else { selected = 0; } ColorCandidate &candidate = candidateColors[selected]; // Set alpha const double oldAlpha = colorspace->opacityF(pixel.oldRawData()); double newAlpha = oldAlpha; if (alphaEnabled && !(!ditherEnabled && alphaMode == AlphaMode::Dither)) { if (alphaMode == AlphaMode::Clip) { newAlpha = oldAlpha < alphaClip? 0.0 : 1.0; } else if (alphaMode == AlphaMode::Index) { newAlpha = (candidate.index == alphaIndex ? 0.0 : 1.0); } else if (alphaMode == AlphaMode::Dither) { newAlpha = oldAlpha < alphaDitherUtil.threshold(QPoint(pixel.x(), pixel.y())) ? 0.0 : 1.0; } } colorspace->setOpacity(candidate.color.data(), newAlpha, 1); // Copy color to pixel memcpy(pixel.rawData(), candidate.color.data(), colorspace->pixelSize()); } } } diff --git a/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp b/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp index 519e82373d..c63b366a8f 100644 --- a/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp +++ b/plugins/filters/raindropsfilter/kis_raindrops_filter.cpp @@ -1,397 +1,397 @@ /* * This file is part of the KDE project * * Copyright (c) 2004 Michael Thaler * * ported from digikam, copyrighted 2004 by Gilles Caulier, * Original RainDrops algorithm copyrighted 2004 by * Pieter Z. Voloshyn . * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_raindrops_filter.h" #include #include #include #include #include #include #include #include #include #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_multi_integer_filter_widget.h" KisRainDropsFilter::KisRainDropsFilter() : KisFilter(id(), FiltersCategoryArtisticId, i18n("&Raindrops...")) { setSupportsPainting(false); setSupportsThreading(false); setSupportsAdjustmentLayers(true); } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* Function to apply the RainDrops effect (inspired from Jason Waltman code) * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * DropSize => Raindrop size * number => Maximum number of raindrops * fishEyes => FishEye coefficient * * Theory => This functions does several math's functions and the engine * is simple to understand, but a little hard to implement. A * control will indicate if there is or not a raindrop in that * area, if not, a fisheye effect with a random size (max=DropSize) * will be applied, after this, a shadow will be applied too. * and after this, a blur function will finish the effect. */ void KisRainDropsFilter::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = applyRect.topLeft(); Q_ASSERT(device); //read the filter configuration values from the KisFilterConfiguration object quint32 DropSize = config->getInt("dropSize", 80); quint32 number = config->getInt("number", 80); quint32 fishEyes = config->getInt("fishEyes", 30); qsrand(config->getInt("seed")); if (fishEyes <= 0) fishEyes = 1; if (fishEyes > 100) fishEyes = 100; int Width = applyRect.width(); int Height = applyRect.height(); bool** BoolMatrix = CreateBoolArray(Width, Height); int i, j, k, l, m, n; // loop variables int Bright; // Bright value for shadows and highlights int x, y; // center coordinates int Counter = 0; // Counter (duh !) int NewSize; // Size of current raindrop int halfSize; // Half of the current raindrop int Radius; // Maximum radius for raindrop int BlurRadius; // Blur Radius int BlurPixels; double r, a; // polar coordinates double OldRadius; // Radius before processing double NewfishEyes = (double)fishEyes * 0.01; // FishEye fishEyesicients double s; double R, G, B; bool FindAnother = false; // To search for good coordinates const KoColorSpace * cs = device->colorSpace(); // Init boolean Matrix. for (i = 0 ; i < Width; ++i) { for (j = 0 ; j < Height; ++j) { BoolMatrix[i][j] = false; } } progressUpdater->setRange(0, number); KisRandomAccessorSP dstAccessor = device->createRandomAccessorNG(srcTopLeft.x(), srcTopLeft.y()); for (uint NumBlurs = 0; NumBlurs <= number; ++NumBlurs) { NewSize = (int)(qrand() * ((double)(DropSize - 5) / RAND_MAX) + 5); halfSize = NewSize / 2; Radius = halfSize; s = Radius / log(NewfishEyes * Radius + 1); Counter = 0; do { FindAnother = false; y = (int)(qrand() * ((double)(Width - 1) / RAND_MAX)); x = (int)(qrand() * ((double)(Height - 1) / RAND_MAX)); if (BoolMatrix[y][x]) FindAnother = true; else for (i = x - halfSize ; i <= x + halfSize; i++) for (j = y - halfSize ; j <= y + halfSize; j++) if ((i >= 0) && (i < Height) && (j >= 0) && (j < Width)) if (BoolMatrix[j][i]) FindAnother = true; Counter++; } while (FindAnother && Counter < 10000); if (Counter >= 10000) { NumBlurs = number; break; } for (i = -1 * halfSize ; i < NewSize - halfSize; i++) { for (j = -1 * halfSize ; j < NewSize - halfSize; j++) { r = sqrt((double)i * i + j * j); a = atan2(static_cast(i), static_cast(j)); if (r <= Radius) { OldRadius = r; r = (exp(r / s) - 1) / NewfishEyes; k = x + (int)(r * sin(a)); l = y + (int)(r * cos(a)); m = x + i; n = y + j; if ((k >= 0) && (k < Height) && (l >= 0) && (l < Width)) { if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) { Bright = 0; if (OldRadius >= 0.9 * Radius) { if ((a <= 0) && (a > -2.25)) Bright = -80; else if ((a <= -2.25) && (a > -2.5)) Bright = -40; else if ((a <= 0.25) && (a > 0)) Bright = -40; } else if (OldRadius >= 0.8 * Radius) { if ((a <= -0.75) && (a > -1.50)) Bright = -40; else if ((a <= 0.10) && (a > -0.75)) Bright = -30; else if ((a <= -1.50) && (a > -2.35)) Bright = -30; } else if (OldRadius >= 0.7 * Radius) { if ((a <= -0.10) && (a > -2.0)) Bright = -20; else if ((a <= 2.50) && (a > 1.90)) Bright = 60; } else if (OldRadius >= 0.6 * Radius) { if ((a <= -0.50) && (a > -1.75)) Bright = -20; else if ((a <= 0) && (a > -0.25)) Bright = 20; else if ((a <= -2.0) && (a > -2.25)) Bright = 20; } else if (OldRadius >= 0.5 * Radius) { if ((a <= -0.25) && (a > -0.50)) Bright = 30; else if ((a <= -1.75) && (a > -2.0)) Bright = 30; } else if (OldRadius >= 0.4 * Radius) { if ((a <= -0.5) && (a > -1.75)) Bright = 40; } else if (OldRadius >= 0.3 * Radius) { if ((a <= 0) && (a > -2.25)) Bright = 30; } else if (OldRadius >= 0.2 * Radius) { if ((a <= -0.5) && (a > -1.75)) Bright = 20; } BoolMatrix[n][m] = true; QColor originalColor; dstAccessor->moveTo(srcTopLeft.x() + l, srcTopLeft.y() + k); cs->toQColor(dstAccessor->oldRawData(), &originalColor); int newRed = CLAMP(originalColor.red() + Bright, 0, quint8_MAX); int newGreen = CLAMP(originalColor.green() + Bright, 0, quint8_MAX); int newBlue = CLAMP(originalColor.blue() + Bright, 0, quint8_MAX); QColor newColor; newColor.setRgb(newRed, newGreen, newBlue); dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m); cs->fromQColor(newColor, dstAccessor->rawData()); } } } } } BlurRadius = NewSize / 25 + 1; for (i = -1 * halfSize - BlurRadius ; i < NewSize - halfSize + BlurRadius; i++) { for (j = -1 * halfSize - BlurRadius; j < NewSize - halfSize + BlurRadius; ++j) { r = sqrt((double)i * i + j * j); if (r <= Radius * 1.1) { R = G = B = 0; BlurPixels = 0; for (k = -1 * BlurRadius; k < BlurRadius + 1; k++) for (l = -1 * BlurRadius; l < BlurRadius + 1; l++) { m = x + i + k; n = y + j + l; if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) { QColor color; dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m); cs->toQColor(dstAccessor->rawData(), &color); R += color.red(); G += color.green(); B += color.blue(); BlurPixels++; } } m = x + i; n = y + j; if ((m >= 0) && (m < Height) && (n >= 0) && (n < Width)) { QColor color; color.setRgb((int)(R / BlurPixels), (int)(G / BlurPixels), (int)(B / BlurPixels)); dstAccessor->moveTo(srcTopLeft.x() + n, srcTopLeft.y() + m); cs->fromQColor(color, dstAccessor->rawData()); } } } } progressUpdater->setValue(NumBlurs); } FreeBoolArray(BoolMatrix, Width); } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* Function to free a dynamic boolean array * * lpbArray => Dynamic boolean array * Columns => The array bidimension value * * Theory => An easy to understand 'for' statement */ void KisRainDropsFilter::FreeBoolArray(bool** lpbArray, uint Columns) const { for (uint i = 0; i < Columns; ++i) free(lpbArray[i]); free(lpbArray); } /* Function to create a bidimentional dynamic boolean array * * Columns => Number of columns * Rows => Number of rows * * Theory => Using 'for' statement, we can alloc multiple dynamic arrays * To create more dimensions, just add some 'for's, ok? */ bool** KisRainDropsFilter::CreateBoolArray(uint Columns, uint Rows) const { bool** lpbArray = 0; lpbArray = (bool**) malloc(Columns * sizeof(bool*)); if (lpbArray == 0) return (0); for (uint i = 0; i < Columns; ++i) { lpbArray[i] = (bool*) malloc(Rows * sizeof(bool)); if (lpbArray[i] == 0) { FreeBoolArray(lpbArray, Columns); return (0); } } return (lpbArray); } // This method have been ported from Pieter Z. Voloshyn algorithm code. /* This function limits the RGB values * * ColorValue => Here, is an RGB value to be analyzed * * Theory => A color is represented in RGB value (e.g. 0xFFFFFF is * white color). But R, G and B values have 256 values to be used * so, this function analyzes the value and limits to this range */ uchar KisRainDropsFilter::LimitValues(int ColorValue) const { if (ColorValue > 255) // MAX = 255 ColorValue = 255; if (ColorValue < 0) // MIN = 0 ColorValue = 0; return ((uchar) ColorValue); } KisConfigWidget * KisRainDropsFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { vKisIntegerWidgetParam param; param.push_back(KisIntegerWidgetParam(1, 200, 80, i18n("Drop size"), "dropsize")); - param.push_back(KisIntegerWidgetParam(1, 500, 80, i18n("Number"), "number")); + param.push_back(KisIntegerWidgetParam(1, 500, 80, i18n("Number of drops"), "number")); param.push_back(KisIntegerWidgetParam(1, 100, 30, i18n("Fish eyes"), "fishEyes")); KisMultiIntegerFilterWidget * w = new KisMultiIntegerFilterWidget(id().id(), parent, id().id(), param); w->setConfiguration(factoryConfiguration()); return w; } KisFilterConfigurationSP KisRainDropsFilter::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("raindrops", 2); config->setProperty("dropsize", 80); config->setProperty("number", 80); config->setProperty("fishEyes", 30); config->setProperty("seed", QTime::currentTime().msec()); return config; } diff --git a/plugins/filters/tests/kis_crash_filter_test.cpp b/plugins/filters/tests/kis_crash_filter_test.cpp index e044e8d527..427157f46e 100644 --- a/plugins/filters/tests/kis_crash_filter_test.cpp +++ b/plugins/filters/tests/kis_crash_filter_test.cpp @@ -1,122 +1,118 @@ /* * Copyright (c) 2008 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_crash_filter_test.h" #include #include #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_processing_information.h" #include "filter/kis_filter.h" #include "kis_pixel_selection.h" #include #include "kis_transaction.h" #include bool KisCrashFilterTest::applyFilter(const KoColorSpace * cs, KisFilterSP f) { QImage qimage(QString(FILES_DATA_DIR) + QDir::separator() + "carrot.png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(new TestUtil::TestingTimedDefaultBounds(qimage.rect())); dev->convertFromQImage(qimage, 0, 0, 0); // Get the predefined configuration from a file KisFilterConfigurationSP kfc = f->defaultConfiguration(); QFile file(QString(FILES_DATA_DIR) + QDir::separator() + f->id() + ".cfg"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { dbgKrita << "creating new file for " << f->id(); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); out.setCodec("UTF-8"); out << kfc->toXML(); } else { QString s; QTextStream in(&file); in.setCodec("UTF-8"); s = in.readAll(); kfc->fromXML(s); } dbgKrita << f->id() << ", " << cs->id() << ", " << cs->profile()->name();// << kfc->toXML() << "\n"; { KisTransaction t(kundo2_noi18n(f->name()), dev); f->process(dev, QRect(QPoint(0,0), qimage.size()), kfc); } return true; } bool KisCrashFilterTest::testFilter(KisFilterSP f) { QList colorSpaces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile); bool ok = false; Q_FOREACH (const KoColorSpace* colorSpace, colorSpaces) { - // XXX: Let's not check the painterly colorspaces right now - if (colorSpace->id().startsWith("KS", Qt::CaseInsensitive)) { - continue; - } // Alpha color spaces are never processed directly. They are // first converted into GrayA color space if (colorSpace->id().startsWith("ALPHA", Qt::CaseInsensitive)) { continue; } ok = applyFilter(colorSpace, f); } return ok; } void KisCrashFilterTest::testCrashFilters() { QStringList excludeFilters; excludeFilters << "colortransfer"; excludeFilters << "gradientmap"; excludeFilters << "phongbumpmap"; excludeFilters << "perchannel"; excludeFilters << "height to normal"; QStringList failures; QStringList successes; QList filterList = KisFilterRegistry::instance()->keys(); std::sort(filterList.begin(), filterList.end()); for (QList::Iterator it = filterList.begin(); it != filterList.end(); ++it) { if (excludeFilters.contains(*it)) continue; if (testFilter(KisFilterRegistry::instance()->value(*it))) successes << *it; else failures << *it; } dbgKrita << "Success: " << successes; if (failures.size() > 0) { QFAIL(QString("Failed filters:\n\t %1").arg(failures.join("\n\t")).toLatin1()); } } #include KISTEST_MAIN(KisCrashFilterTest) diff --git a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp index b9e3ba7d02..1f234403e7 100644 --- a/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp +++ b/plugins/flake/textshape/kotext/styles/KoTableCellStyle.cpp @@ -1,1051 +1,1051 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Roopesh Chander * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Pierre Ducroquet * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTableCellStyle.h" #include "KoTableCellStyle_p.h" #include #include #include #include #include "KoParagraphStyle.h" #include #include #include #include #include #include #include #include #include "TextDebug.h" #include KoTableCellStyle::RotationAlignment rotationAlignmentFromString(const QString& align) { if (align == "bottom") return KoTableCellStyle::RAlignBottom; if (align == "center") return KoTableCellStyle::RAlignCenter; if (align == "top") return KoTableCellStyle::RAlignTop; return KoTableCellStyle::RAlignNone; } QString rotationAlignmentToString(KoTableCellStyle::RotationAlignment align) { if (align == KoTableCellStyle::RAlignBottom) return "bottom"; if (align == KoTableCellStyle::RAlignTop) return "top"; if (align == KoTableCellStyle::RAlignCenter) return "center"; return "none"; } KoTableCellStylePrivate::KoTableCellStylePrivate() : paragraphStyle(0) , parentStyle(0) , next(0) { } KoTableCellStylePrivate::~KoTableCellStylePrivate() { } void KoTableCellStylePrivate::setProperty(int key, const QVariant &value) { stylesPrivate.add(key, value); } KoTableCellStyle::KoTableCellStyle(QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const QTextTableCellFormat &format, QObject *parent) : QObject(parent) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); d->stylesPrivate = format.properties(); d->paragraphStyle = new KoParagraphStyle(this); } KoTableCellStyle::KoTableCellStyle(const KoTableCellStyle &other) :QObject(other.parent()) , d_ptr(new KoTableCellStylePrivate) { Q_D(KoTableCellStyle); copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); } KoTableCellStyle& KoTableCellStyle::operator=(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); if (this == &other) { return *this; } copyProperties(&other); d->paragraphStyle = other.paragraphStyle()->clone(this); return *this; } KoTableCellStyle::~KoTableCellStyle() { delete d_ptr; } KoTableCellStyle *KoTableCellStyle::fromTableCell(const QTextTableCell &tableCell, QObject *parent) { QTextTableCellFormat tableCellFormat = tableCell.format().toTableCellFormat(); return new KoTableCellStyle(tableCellFormat, parent); } QTextCharFormat KoTableCellStyle::cleanCharFormat(const QTextCharFormat &charFormat) { if (charFormat.isTableCellFormat()) { QTextTableCellFormat format; const QMap props = charFormat.properties(); QMap::const_iterator it = props.begin(); while (it != props.end()) { // lets save all Qt's table cell properties if (it.key()>=QTextFormat::TableCellRowSpan && it.key()=StyleId && it.key()parentStyle = parent; } void KoTableCellStyle::setLeftPadding(qreal padding) { setProperty(QTextFormat::TableCellLeftPadding, padding); } void KoTableCellStyle::setTopPadding(qreal padding) { setProperty(QTextFormat::TableCellTopPadding, padding); } void KoTableCellStyle::setRightPadding(qreal padding) { setProperty(QTextFormat::TableCellRightPadding, padding); } void KoTableCellStyle::setBottomPadding(qreal padding) { setProperty(QTextFormat::TableCellBottomPadding, padding); } qreal KoTableCellStyle::leftPadding() const { return propertyDouble(QTextFormat::TableCellLeftPadding); } qreal KoTableCellStyle::rightPadding() const { return propertyDouble(QTextFormat::TableCellRightPadding); } qreal KoTableCellStyle::topPadding() const { return propertyDouble(QTextFormat::TableCellTopPadding); } qreal KoTableCellStyle::bottomPadding() const { return propertyDouble(QTextFormat::TableCellBottomPadding); } void KoTableCellStyle::setPadding(qreal padding) { setBottomPadding(padding); setTopPadding(padding); setRightPadding(padding); setLeftPadding(padding); } KoParagraphStyle *KoTableCellStyle::paragraphStyle() const { Q_D(const KoTableCellStyle); return d->paragraphStyle; } bool KoTableCellStyle::shrinkToFit() const { return propertyBoolean(ShrinkToFit); } void KoTableCellStyle::setShrinkToFit(bool state) { setProperty(ShrinkToFit, state); } void KoTableCellStyle::setProperty(int key, const QVariant &value) { Q_D(KoTableCellStyle); if (d->parentStyle) { QVariant var = d->parentStyle->value(key); if (!var.isNull() && var == value) { // same as parent, so its actually a reset. d->stylesPrivate.remove(key); return; } } d->stylesPrivate.add(key, value); } void KoTableCellStyle::remove(int key) { Q_D(KoTableCellStyle); d->stylesPrivate.remove(key); } QVariant KoTableCellStyle::value(int key) const { Q_D(const KoTableCellStyle); QVariant var = d->stylesPrivate.value(key); if (var.isNull() && d->parentStyle) var = d->parentStyle->value(key); return var; } bool KoTableCellStyle::hasProperty(int key) const { Q_D(const KoTableCellStyle); return d->stylesPrivate.contains(key); } qreal KoTableCellStyle::propertyDouble(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0.0; return variant.toDouble(); } QPen KoTableCellStyle::propertyPen(int key) const { const QVariant prop = value(key); if (prop.userType() != QVariant::Pen) return QPen(Qt::NoPen); return qvariant_cast(prop); } int KoTableCellStyle::propertyInt(int key) const { QVariant variant = value(key); if (variant.isNull()) return 0; return variant.toInt(); } bool KoTableCellStyle::propertyBoolean(int key) const { QVariant variant = value(key); if (variant.isNull()) return false; return variant.toBool(); } QColor KoTableCellStyle::propertyColor(int key) const { QVariant variant = value(key); if (variant.isNull()) { return QColor(); } return qvariant_cast(variant); } void KoTableCellStyle::applyStyle(QTextTableCellFormat &format) const { Q_D(const KoTableCellStyle); if (d->parentStyle) { d->parentStyle->applyStyle(format); } QList keys = d->stylesPrivate.keys(); for (int i = 0; i < keys.count(); i++) { QVariant variant = d->stylesPrivate.value(keys[i]); format.setProperty(keys[i], variant); } // Hack : build KoBorder here if (d->parentStyle && d->parentStyle->hasProperty(Borders) && this->hasProperty(Borders)) { KoBorder parentBorder = d->parentStyle->borders(); KoBorder childBorder = this->borders(); if (childBorder.hasBorder(KoBorder::LeftBorder)) parentBorder.setBorderData(KoBorder::LeftBorder, childBorder.borderData(KoBorder::LeftBorder)); if (childBorder.hasBorder(KoBorder::RightBorder)) parentBorder.setBorderData(KoBorder::RightBorder, childBorder.borderData(KoBorder::RightBorder)); if (childBorder.hasBorder(KoBorder::TopBorder)) parentBorder.setBorderData(KoBorder::TopBorder, childBorder.borderData(KoBorder::TopBorder)); if (childBorder.hasBorder(KoBorder::BottomBorder)) parentBorder.setBorderData(KoBorder::BottomBorder, childBorder.borderData(KoBorder::BottomBorder)); if (childBorder.hasBorder(KoBorder::BltrBorder)) parentBorder.setBorderData(KoBorder::BltrBorder, childBorder.borderData(KoBorder::BltrBorder)); if (childBorder.hasBorder(KoBorder::TlbrBorder)) parentBorder.setBorderData(KoBorder::TlbrBorder, childBorder.borderData(KoBorder::TlbrBorder)); format.setProperty(Borders, QVariant::fromValue(parentBorder)); } } void KoTableCellStyle::applyStyle(QTextTableCell &cell) const { Q_D(const KoTableCellStyle); QTextTableCellFormat format = cell.format().toTableCellFormat(); applyStyle(format); if (d->paragraphStyle) { d->paragraphStyle->KoCharacterStyle::applyStyle(format); } cell.setFormat(format); } void KoTableCellStyle::setBackground(const QBrush &brush) { setProperty(CellBackgroundBrush, brush); } void KoTableCellStyle::clearBackground() { Q_D(KoTableCellStyle); d->stylesPrivate.remove(CellBackgroundBrush); } QBrush KoTableCellStyle::background() const { Q_D(const KoTableCellStyle); QVariant variant = d->stylesPrivate.value(CellBackgroundBrush); if (variant.isNull()) { return QBrush(); } return qvariant_cast(variant); } void KoTableCellStyle::setWrap(bool state) { setProperty(Wrap, state); } bool KoTableCellStyle::wrap() const { return propertyBoolean(Wrap); } void KoTableCellStyle::setAlignment(Qt::Alignment alignment) { setProperty(VerticalAlignment, (int) alignment); } Qt::Alignment KoTableCellStyle::alignment() const { if (propertyInt(VerticalAlignment) == 0) return Qt::AlignTop; return static_cast(propertyInt(VerticalAlignment)); } KoTableCellStyle *KoTableCellStyle::parentStyle() const { Q_D(const KoTableCellStyle); return d->parentStyle; } QString KoTableCellStyle::name() const { Q_D(const KoTableCellStyle); return d->name; } void KoTableCellStyle::setName(const QString &name) { Q_D(KoTableCellStyle); if (name == d->name) return; d->name = name; emit nameChanged(name); } int KoTableCellStyle::styleId() const { return propertyInt(StyleId); } void KoTableCellStyle::setStyleId(int id) { Q_D(KoTableCellStyle); setProperty(StyleId, id); if (d->next == 0) d->next = id; } QString KoTableCellStyle::masterPageName() const { return value(MasterPageName).toString(); } void KoTableCellStyle::setMasterPageName(const QString &name) { setProperty(MasterPageName, name); } void KoTableCellStyle::setCellProtection(KoTableCellStyle::CellProtectionFlag protection) { setProperty(CellProtection, protection); } KoTableCellStyle::CellProtectionFlag KoTableCellStyle::cellProtection() const { return (CellProtectionFlag) propertyInt(CellProtection); } void KoTableCellStyle::setTextDirection(KoText::Direction value) { setProperty(TextWritingMode, value); } KoText::Direction KoTableCellStyle::textDirection() const { return (KoText::Direction) propertyInt(TextWritingMode); } bool KoTableCellStyle::printContent() const { return (hasProperty(PrintContent) && propertyBoolean(PrintContent)); } void KoTableCellStyle::setPrintContent(bool state) { setProperty(PrintContent, state); } bool KoTableCellStyle::repeatContent() const { return (hasProperty(RepeatContent) && propertyBoolean(RepeatContent)); } void KoTableCellStyle::setRepeatContent(bool state) { setProperty(RepeatContent, state); } int KoTableCellStyle::decimalPlaces() const { return propertyInt(DecimalPlaces); } void KoTableCellStyle::setDecimalPlaces(int places) { setProperty(DecimalPlaces, places); } bool KoTableCellStyle::alignFromType() const { return (hasProperty(AlignFromType) && propertyBoolean(AlignFromType)); } void KoTableCellStyle::setAlignFromType(bool state) { setProperty(AlignFromType, state); } qreal KoTableCellStyle::rotationAngle() const { return propertyDouble(RotationAngle); } void KoTableCellStyle::setRotationAngle(qreal value) { if (value >= 0) setProperty(RotationAngle, value); } void KoTableCellStyle::setVerticalGlyphOrientation(bool state) { setProperty(VerticalGlyphOrientation, state); } bool KoTableCellStyle::verticalGlyphOrientation() const { if (hasProperty(VerticalGlyphOrientation)) return propertyBoolean(VerticalGlyphOrientation); return true; } void KoTableCellStyle::setDirection(KoTableCellStyle::CellTextDirection direction) { setProperty(Direction, direction); } KoBorder KoTableCellStyle::borders() const { if (hasProperty(Borders)) return value(Borders).value(); return KoBorder(); } void KoTableCellStyle::setBorders(const KoBorder& borders) { setProperty(Borders, QVariant::fromValue(borders)); } KoShadowStyle KoTableCellStyle::shadow() const { if (hasProperty(Shadow)) return value(Shadow).value(); return KoShadowStyle(); } void KoTableCellStyle::setShadow(const KoShadowStyle& shadow) { setProperty(Shadow, QVariant::fromValue(shadow)); } KoTableCellStyle::RotationAlignment KoTableCellStyle::rotationAlignment() const { return static_cast(propertyInt(RotationAlign)); } void KoTableCellStyle::setRotationAlignment(KoTableCellStyle::RotationAlignment align) { setProperty(RotationAlign, align); } KoTableCellStyle::CellTextDirection KoTableCellStyle::direction() const { if (hasProperty(Direction)) return (KoTableCellStyle::CellTextDirection) propertyInt(Direction); return KoTableCellStyle::Default; } void KoTableCellStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext) { KoOdfLoadingContext &context = scontext.odfLoadingContext(); Q_D(KoTableCellStyle); if (element->hasAttributeNS(KoXmlNS::style, "display-name")) d->name = element->attributeNS(KoXmlNS::style, "display-name", QString()); if (d->name.isEmpty()) // if no style:display-name is given us the style:name d->name = element->attributeNS(KoXmlNS::style, "name", QString()); QString masterPage = element->attributeNS(KoXmlNS::style, "master-page-name", QString()); if (! masterPage.isEmpty()) { setMasterPageName(masterPage); } paragraphStyle()->loadOdf(element, scontext, true); // load the par and char properties context.styleStack().save(); QString family = element->attributeNS(KoXmlNS::style, "family", "table-cell"); context.addStyles(element, family.toLocal8Bit().constData()); // Load all parents - only because we don't support inheritance. context.styleStack().setTypeProperties("table-cell"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().setTypeProperties("graphic"); loadOdfProperties(scontext, context.styleStack()); context.styleStack().restore(); } void KoTableCellStyle::loadOdfProperties(KoShapeLoadingContext &context, KoStyleStack &styleStack) { // Padding if (styleStack.hasProperty(KoXmlNS::fo, "padding-left")) setLeftPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-left"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-right")) setRightPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-right"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-top")) setTopPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-top"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding-bottom")) setBottomPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding-bottom"))); if (styleStack.hasProperty(KoXmlNS::fo, "padding")) setPadding(KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "padding"))); if (styleStack.hasProperty(KoXmlNS::style, "shadow")) { KoShadowStyle shadow; if (shadow.loadOdf(styleStack.property(KoXmlNS::style, "shadow"))) { setShadow(shadow); } } // The fo:background-color attribute specifies the background color of a cell. if (styleStack.hasProperty(KoXmlNS::fo, "background-color")) { const QString bgcolor = styleStack.property(KoXmlNS::fo, "background-color"); QBrush brush = background(); if (bgcolor == "transparent") setBackground(Qt::NoBrush); else { if (brush.style() == Qt::NoBrush) brush.setStyle(Qt::SolidPattern); brush.setColor(bgcolor); // #rrggbb format setBackground(brush); } } QString fillStyle = styleStack.property(KoXmlNS::draw, "fill"); if (fillStyle == "solid" || fillStyle == "hatch") { styleStack.save(); QBrush brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fillStyle, context.odfLoadingContext().stylesReader()); setBackground(brush); styleStack.restore(); } if (styleStack.hasProperty(KoXmlNS::style, "shrink-to-fit")) { setShrinkToFit(styleStack.property(KoXmlNS::style, "shrink-to-fit") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "print-content")) { setPrintContent(styleStack.property(KoXmlNS::style, "print-content") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "repeat-content")) { setRepeatContent(styleStack.property(KoXmlNS::style, "repeat-content") == "true"); } if (styleStack.hasProperty(KoXmlNS::style, "decimal-places")) { bool ok; int value = styleStack.property(KoXmlNS::style, "decimal-places").toInt(&ok); if (ok) setDecimalPlaces(value); } if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) { setRotationAngle(KoUnit::parseAngle(styleStack.property(KoXmlNS::style, "rotation-angle"))); } if (styleStack.hasProperty(KoXmlNS::style, "glyph-orientation-vertical")) { setVerticalGlyphOrientation(styleStack.property(KoXmlNS::style, "glyph-orientation-vertical") == "auto"); } if (styleStack.hasProperty(KoXmlNS::style, "direction")) { if (styleStack.property(KoXmlNS::style, "direction") == "ltr") setDirection(KoTableCellStyle::LeftToRight); else setDirection(KoTableCellStyle::TopToBottom); } if (styleStack.hasProperty(KoXmlNS::style, "rotation-align")) { setRotationAlignment(rotationAlignmentFromString(styleStack.property(KoXmlNS::style, "rotation-align"))); } if (styleStack.hasProperty(KoXmlNS::style, "text-align-source")) { setAlignFromType(styleStack.property(KoXmlNS::style, "text-align-source") == "value-type"); } if (styleStack.hasProperty(KoXmlNS::fo, "wrap-option")) { setWrap(styleStack.property(KoXmlNS::fo, "wrap-option") == "wrap"); } if (styleStack.hasProperty(KoXmlNS::style, "cell-protect")) { QString protection = styleStack.property(KoXmlNS::style, "cell-protect"); if (protection == "none") setCellProtection(NoProtection); else if (protection == "hidden-and-protected") setCellProtection(HiddenAndProtected); else if (protection == "protected") setCellProtection(Protected); else if (protection == "formula-hidden") setCellProtection(FormulaHidden); else if ((protection == "protected formula-hidden") || (protection == "formula-hidden protected")) setCellProtection(ProtectedAndFormulaHidden); } // Alignment const QString verticalAlign(styleStack.property(KoXmlNS::style, "vertical-align")); if (!verticalAlign.isEmpty()) { if (verticalAlign == "automatic") setAlignment((Qt::AlignmentFlag) 0); else setAlignment(KoText::valignmentFromString(verticalAlign)); } if (styleStack.hasProperty(KoXmlNS::style, "writing-mode")) setTextDirection(KoText::directionFromString(styleStack.property(KoXmlNS::style, "writing-mode"))); } void KoTableCellStyle::copyProperties(const KoTableCellStyle *style) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *styleD = static_cast(style->d_func()); d->stylesPrivate = styleD->stylesPrivate; setName(style->name()); // make sure we emit property change d->next = styleD->next; d->parentStyle = styleD->parentStyle; } KoTableCellStyle *KoTableCellStyle::clone(QObject *parent) { KoTableCellStyle *newStyle = new KoTableCellStyle(parent); newStyle->copyProperties(this); return newStyle; } bool KoTableCellStyle::operator==(const KoTableCellStyle &other) const { Q_D(const KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); return otherD->stylesPrivate == d->stylesPrivate; } void KoTableCellStyle::removeDuplicates(const KoTableCellStyle &other) { Q_D(KoTableCellStyle); const KoTableCellStylePrivate *otherD = static_cast(other.d_func()); d->stylesPrivate.removeDuplicates(otherD->stylesPrivate); } void KoTableCellStyle::saveOdf(KoGenStyle &style, KoShapeSavingContext &context) { Q_D(KoTableCellStyle); QList keys = d->stylesPrivate.keys(); bool donePadding = false; if (hasProperty(QTextFormat::TableCellLeftPadding) && hasProperty(QTextFormat::TableCellRightPadding) && hasProperty(QTextFormat::TableCellTopPadding) && hasProperty(QTextFormat::TableCellBottomPadding) && leftPadding() == rightPadding() && topPadding() == bottomPadding() && topPadding() == leftPadding()) { donePadding = true; style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::TableCellType); } Q_FOREACH (int key, keys) { if (key == CellBackgroundBrush) { QBrush backBrush = background(); if (backBrush.style() != Qt::NoBrush) style.addProperty("fo:background-color", backBrush.color().name(), KoGenStyle::TableCellType); else style.addProperty("fo:background-color", "transparent", KoGenStyle::TableCellType); } else if (key == VerticalAlignment) { if (propertyInt(VerticalAlignment) == 0) style.addProperty("style:vertical-align", "automatic", KoGenStyle::TableCellType); else style.addProperty("style:vertical-align", KoText::valignmentToString(alignment()), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellLeftPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellRightPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellTopPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::TableCellType); } else if ((key == QTextFormat::TableCellBottomPadding) && (!donePadding)) { style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::TableCellType); } else if (key == ShrinkToFit) { style.addProperty("style:shrink-to-fit", shrinkToFit(), KoGenStyle::TableCellType); } else if (key == PrintContent) { style.addProperty("style:print-content", printContent(), KoGenStyle::TableCellType); } else if (key == RepeatContent) { style.addProperty("style:repeat-content", repeatContent(), KoGenStyle::TableCellType); } else if (key == DecimalPlaces) { style.addProperty("style:decimal-places", decimalPlaces(), KoGenStyle::TableCellType); } else if (key == RotationAngle) { style.addProperty("style:rotation-angle", QString::number(rotationAngle()), KoGenStyle::TableCellType); } else if (key == Wrap) { if (wrap()) style.addProperty("fo:wrap-option", "wrap", KoGenStyle::TableCellType); else style.addProperty("fo:wrap-option", "no-wrap", KoGenStyle::TableCellType); } else if (key == Direction) { if (direction() == LeftToRight) style.addProperty("style:direction", "ltr", KoGenStyle::TableCellType); else if (direction() == TopToBottom) style.addProperty("style:direction", "ttb", KoGenStyle::TableCellType); } else if (key == CellProtection) { if (cellProtection() == NoProtection) style.addProperty("style:cell-protect", "none", KoGenStyle::TableCellType); else if (cellProtection() == HiddenAndProtected) style.addProperty("style:cell-protect", "hidden-and-protected", KoGenStyle::TableCellType); else if (cellProtection() == Protected) style.addProperty("style:cell-protect", "protected", KoGenStyle::TableCellType); else if (cellProtection() == FormulaHidden) style.addProperty("style:cell-protect", "formula-hidden", KoGenStyle::TableCellType); else if (cellProtection() == ProtectedAndFormulaHidden) style.addProperty("style:cell-protect", "protected formula-hidden", KoGenStyle::TableCellType); } else if (key == AlignFromType) { if (alignFromType()) style.addProperty("style:text-align-source", "value-type", KoGenStyle::TableCellType); else style.addProperty("style:text-align-source", "fix", KoGenStyle::TableCellType); } else if (key == RotationAlign) { style.addProperty("style:rotation-align", rotationAlignmentToString(rotationAlignment()), KoGenStyle::TableCellType); } else if (key == TextWritingMode) { style.addProperty("style:writing-mode", KoText::directionToString(textDirection()), KoGenStyle::TableCellType); } else if (key == VerticalGlyphOrientation) { if (verticalGlyphOrientation()) style.addProperty("style:glyph-orientation-vertical", "auto", KoGenStyle::TableCellType); else style.addProperty("style:glyph-orientation-vertical", "0", KoGenStyle::TableCellType); } else if (key == Borders) { borders().saveOdf(style, KoGenStyle::TableCellType); } else if (key == Shadow) { style.addProperty("style:shadow", shadow().saveOdf()); } } if (d->paragraphStyle) { d->paragraphStyle->saveOdf(style, context); } } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, KoBorder::BorderStyle style, qreal width, const QColor &color) { KoBorder::BorderData edge; qreal innerWidth = 0; qreal middleWidth = 0; qreal space = 0; QVector dashes; switch (style) { case KoBorder::BorderNone: width = 0.0; break; case KoBorder::BorderDouble: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDotted: dashes << 1 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashed: dashes << 4 << 1; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashedLong: { dashes << 4 << 4; edge.outerPen.setDashPattern(dashes); break; } case KoBorder::BorderTriple: innerWidth = middleWidth = space = width/6; width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDashDot: dashes << 3 << 3<< 7 << 3; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderDashDotDot: dashes << 2 << 2<< 6 << 2 << 2 << 2; edge.outerPen.setDashPattern(dashes); break; case KoBorder::BorderWave: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderSlash: edge.outerPen.setStyle(Qt::SolidLine); break; case KoBorder::BorderDoubleWave: innerWidth = space = width/3; //some nice default look width -= (space + innerWidth); edge.outerPen.setStyle(Qt::SolidLine); break; default: edge.outerPen.setStyle(Qt::SolidLine); break; } edge.outerPen.setColor(color); edge.outerPen.setJoinStyle(Qt::MiterJoin); edge.outerPen.setCapStyle(Qt::FlatCap); edge.outerPen.setWidthF(width); edge.spacing = space; edge.innerPen = edge.outerPen; edge.innerPen.setWidthF(innerWidth); QPen middlePen; middlePen = edge.outerPen; middlePen.setWidthF(middleWidth); setEdge(side, edge, style); } void KoTableCellStyle::setEdge(KoBorder::BorderSide side, const KoBorder::BorderData &edge, KoBorder::BorderStyle style) { KoBorder borders = this->borders(); KoBorder::BorderData edgeCopy(edge); edgeCopy.style = style; // Just for safety. borders.setBorderData(side, edgeCopy); setBorders(borders); } void KoTableCellStyle::setEdgeDoubleBorderValues(KoBorder::BorderSide side, qreal innerWidth, qreal space) { KoBorder::BorderData edge = getEdge(side); qreal totalWidth = edge.outerPen.widthF() + edge.spacing + edge.innerPen.widthF(); if (edge.innerPen.widthF() > 0.0) { edge.outerPen.setWidthF(totalWidth - innerWidth - space); edge.spacing = space; edge.innerPen.setWidthF(innerWidth); setEdge(side, edge, getBorderStyle(side)); } } bool KoTableCellStyle::hasBorders() const { return borders().hasBorder(); } qreal KoTableCellStyle::leftBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::rightBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::topBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.spacing + edge.innerPen.widthF() + edge.outerPen.widthF(); } qreal KoTableCellStyle::leftInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::rightInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::topInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::bottomInnerBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.innerPen.widthF(); } qreal KoTableCellStyle::leftOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::LeftBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::rightOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::RightBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::topOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::TopBorder); return edge.outerPen.widthF(); } qreal KoTableCellStyle::bottomOuterBorderWidth() const { const KoBorder::BorderData &edge = getEdge(KoBorder::BottomBorder); return edge.outerPen.widthF(); } KoBorder::BorderData KoTableCellStyle::getEdge(KoBorder::BorderSide side) const { KoBorder border = this->borders(); return border.borderData(side); } KoBorder::BorderStyle KoTableCellStyle::getBorderStyle(KoBorder::BorderSide side) const { KoBorder::BorderData edge = getEdge(side); return edge.style; } diff --git a/plugins/generators/simplexnoise/3rdparty/c-open-simplex/open-simplex-noise.h b/plugins/generators/simplexnoise/3rdparty/c-open-simplex/open-simplex-noise.h index 460ce8fa45..5c767b744f 100644 --- a/plugins/generators/simplexnoise/3rdparty/c-open-simplex/open-simplex-noise.h +++ b/plugins/generators/simplexnoise/3rdparty/c-open-simplex/open-simplex-noise.h @@ -1,51 +1,53 @@ #ifndef OPEN_SIMPLEX_NOISE_H__ #define OPEN_SIMPLEX_NOISE_H__ /* * OpenSimplex (Simplectic) Noise in C. * Ported to C from Kurt Spencer's java implementation by Stephen M. Cameron * * v1.1 (October 6, 2014) * - Ported to C * * v1.1 (October 5, 2014) * - Added 2D and 4D implementations. * - Proper gradient sets for all dimensions, from a * dimensionally-generalizable scheme with an actual * rhyme and reason behind it. * - Removed default permutation array in favor of * default seed. * - Changed seed-based constructor to be independent * of any particular randomization library, so results * will be the same when ported to other languages. */ -#if ((__GNUC_STDC_INLINE__) || (__STDC_VERSION__ >= 199901L)) - #include - #define INLINE inline +#if (defined (__STDC_VERSION__) || defined (__GNUC_STDC_INLINE__)) + #if ((__GNUC_STDC_INLINE__) || (__STDC_VERSION__ >= 199901L)) + #include + #define INLINE inline + #endif #elif (defined (_MSC_VER) || defined (__GNUC_GNU_INLINE__)) #include #define INLINE __inline #else /* ANSI C doesn't have inline or stdint.h. */ #define INLINE #endif #ifdef __cplusplus extern "C" { #endif struct osn_context; int open_simplex_noise(int64_t seed, struct osn_context **ctx); void open_simplex_noise_free(struct osn_context *ctx); int open_simplex_noise_init_perm(struct osn_context *ctx, int16_t p[], int nelements); double open_simplex_noise2(struct osn_context *ctx, double x, double y); double open_simplex_noise3(struct osn_context *ctx, double x, double y, double z); double open_simplex_noise4(struct osn_context *ctx, double x, double y, double z, double w); #ifdef __cplusplus } #endif #endif diff --git a/plugins/impex/heif/HeifImport.cpp b/plugins/impex/heif/HeifImport.cpp index e903f23483..be266c149f 100644 --- a/plugins/impex/heif/HeifImport.cpp +++ b/plugins/impex/heif/HeifImport.cpp @@ -1,189 +1,193 @@ /* * Copyright (c) 2018 Dirk Farin * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "HeifImport.h" #include "HeifError.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "libheif/heif_cxx.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin();) HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } HeifImport::~HeifImport() { } class Reader_QIODevice : public heif::Context::Reader { public: Reader_QIODevice(QIODevice* device) : m_device(device) { m_total_length=m_device->bytesAvailable(); } int64_t get_position() const { return m_device->pos(); } - int read(void* data, size_t size) { return m_device->read((char*)data,size) != size; } + int read(void* data, size_t size) + { + qint64 readSize = m_device->read((char*)data, (qint64)size); + return (readSize > 0 && (quint64)readSize != size); + } int seek(int64_t position) { return !m_device->seek(position); } heif_reader_grow_status wait_for_file_size(int64_t target_size) { return (target_size > m_total_length) ? heif_reader_grow_status_size_beyond_eof : heif_reader_grow_status_size_reached; } private: QIODevice* m_device; int64_t m_total_length; }; KisImportExportErrorCode HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { // Wrap input stream into heif Reader object Reader_QIODevice reader(io); try { heif::Context ctx; ctx.read_from_reader(reader); // decode primary image heif::ImageHandle handle = ctx.get_primary_image_handle(); heif::Image heifimage = handle.decode_image(heif_colorspace_RGB, heif_chroma_444); int width =handle.get_width(); int height=handle.get_height(); bool hasAlpha = handle.has_alpha_channel(); // convert HEIF image to Krita KisDocument int strideR, strideG, strideB, strideA; const uint8_t* imgR = heifimage.get_plane(heif_channel_R, &strideR); const uint8_t* imgG = heifimage.get_plane(heif_channel_G, &strideG); const uint8_t* imgB = heifimage.get_plane(heif_channel_B, &strideB); const uint8_t* imgA = heifimage.get_plane(heif_channel_Alpha, &strideA); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "HEIF image"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); for (int y=0;ypaintDevice()->createHLineIteratorNG(0, y, width); for (int x=0;x::setRed(it->rawData(), imgR[y*strideR+x]); KoBgrTraits::setGreen(it->rawData(), imgG[y*strideG+x]); KoBgrTraits::setBlue(it->rawData(), imgB[y*strideB+x]); if (hasAlpha) { colorSpace->setOpacity(it->rawData(), quint8(imgA[y*strideA+x]), 1); } else { colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } it->nextPixel(); } } image->addNode(layer.data(), image->rootLayer().data()); // --- Iterate through all metadata blocks and extract Exif and XMP metadata --- std::vector metadata_IDs = handle.get_list_of_metadata_block_IDs(); for (heif_item_id id : metadata_IDs) { if (handle.get_metadata_type(id) == "Exif") { // Read exif information std::vector exif_data = handle.get_metadata(id); if (exif_data.size()>4) { uint32_t skip = ((exif_data[0]<<24) | (exif_data[1]<<16) | (exif_data[2]<<8) | exif_data[3]) + 4; if (exif_data.size()>skip) { KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); // Copy the exif data into the byte array QByteArray ba; ba.append((char*)(exif_data.data()+skip), exif_data.size()-skip); QBuffer buf(&ba); exifIO->loadFrom(layer->metaData(), &buf); } } } if (handle.get_metadata_type(id) == "mime" && handle.get_metadata_content_type(id) == "application/rdf+xml") { // Read XMP information std::vector xmp_data = handle.get_metadata(id); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); // Copy the xmp data into the byte array QByteArray ba; ba.append((char*)(xmp_data.data()), xmp_data.size()); QBuffer buf(&ba); xmpIO->loadFrom(layer->metaData(), &buf); } } document->setCurrentImage(image); return ImportExportCodes::OK; } catch (heif::Error err) { return setHeifError(document, err); } } #include diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 807bfbe6de..6b07e59b64 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,377 +1,377 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImportExportErrorCode KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return ImportExportCodes::FileFormatIncorrect; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; KisImportExportErrorCode res = oldLoadAndParse(m_store, "root", doc); if (res.isOk()) res = loadXML(doc, m_store); if (!res.isOk()) { return res; } } else { errUI << "ERROR: No maindoc.xml" << endl; return ImportExportCodes::FileFormatIncorrect; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? ImportExportCodes::OK : ImportExportCodes::Failure; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return ImportExportCodes::CannotCreateFile; } m_kraSaver = new KisKraSaver(m_doc, filename); KisImportExportErrorCode resultCode = saveRootDocuments(m_store); if (!resultCode.isOk()) { return resultCode; } bool result; result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile()); if (!result) { qWarning() << "saving palettes data failed"; } if (!m_store->finalize()) { return ImportExportCodes::Failure; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return ImportExportCodes::Failure; } return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return ImportExportCodes::NoAccessToWrite; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return ImportExportCodes::ErrorWhileWriting; } if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! bool success = dev.write(s.data(), s.size()); if (!success) { return ImportExportCodes::ErrorWhileWriting; } store->close(); } else { return ImportExportCodes::Failure; } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) KisImportExportErrorCode result = savePreview(store); (void)store->close(); if (!result.isOk()) { return result; } } else { return ImportExportCodes::Failure; } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); return ImportExportCodes::OK; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } KisImportExportErrorCode KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return ImportExportCodes::NoAccessToWrite; } bool ret = preview.save(&io, "PNG"); io.close(); return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting; } KisImportExportErrorCode KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; return ImportExportCodes::FileNotExist; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; return ImportExportCodes::FileFormatIncorrect; } dbgUI << "File" << filename << " loaded and parsed"; return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { errUI << "The format is not supported or the file is corrupted"; return ImportExportCodes::FileFormatIncorrect; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { - errUI << "The file is too new for this version of Krita: " + syntaxVersion; + errUI << "The file is too new for this version of Krita: " + QString::number(syntaxVersion); return ImportExportCodes::FormatFeaturesUnsupported; } if (!root.hasChildNodes()) { errUI << "The file has no layers."; return ImportExportCodes::FileFormatIncorrect; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // reset the old image before loading the next one m_doc->setCurrentImage(0, false); for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { errUI << "Unknown error while opening the .kra file."; } else { errUI << m_kraLoader->errorMessages().join("\n"); } return ImportExportCodes::Failure; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); return ImportExportCodes::OK; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return ImportExportCodes::FileFormatIncorrect; } } } return ImportExportCodes::Failure; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); QString layerPathName = m_kraLoader->imageName(); if (!m_store->hasDirectory(layerPathName)) { // We might be hitting an encoding problem. Get the only folder in the toplevel Q_FOREACH (const QString &entry, m_store->directoryList()) { if (entry.contains("/layers/")) { layerPathName = entry.split("/layers/").first(); m_store->setSubstitution(m_kraLoader->imageName(), layerPathName); break; } } } m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_kraLoader->loadPalettes(store, m_doc); m_image->unblockUpdates(); if (!m_kraLoader->warningMessages().isEmpty()) { // warnings do not interrupt loading process, so we do not return here m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); } m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); return true; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/qimageio/kis_qimageio_import.cpp b/plugins/impex/qimageio/kis_qimageio_import.cpp index f09ccbf767..a73e6f3e66 100644 --- a/plugins/impex/qimageio/kis_qimageio_import.cpp +++ b/plugins/impex/qimageio/kis_qimageio_import.cpp @@ -1,73 +1,67 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_qimageio_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOImportFactory, "krita_qimageio_import.json", registerPlugin();) KisQImageIOImport::KisQImageIOImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisQImageIOImport::~KisQImageIOImport() { } KisImportExportErrorCode KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QImage img; if (!img.loadFromData(io->readAll()/*, fi.suffix().toLower().toLatin1()*/)) { return ImportExportCodes::FileFormatIncorrect; } - const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); - KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, i18n("Imported Image")); - - KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); - layer->paintDevice()->convertFromQImage(img, 0, 0, 0); - image->addNode(layer.data(), image->rootLayer().data()); - + KisImageSP image = KisImage::fromQImage(img, document->createUndoStore()); document->setCurrentImage(image); return ImportExportCodes::OK; } #include "kis_qimageio_import.moc" diff --git a/plugins/impex/xcf/3rdparty/xcftools/utils.c b/plugins/impex/xcf/3rdparty/xcftools/utils.c index 06958d0be6..d76b3dd812 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/utils.c +++ b/plugins/impex/xcf/3rdparty/xcftools/utils.c @@ -1,168 +1,169 @@ /* Generic support functions for Xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include #include #include #include const char *progname = "$0" ; int verboseFlag = 0 ; void vFatalGeneric(int status,const char *format,va_list args) { + (void) status; /* mark as unused */ if( format ) { if( *format == '!' ) { vfprintf(stderr,format+1,args); fprintf(stderr,": %s\n",strerror(errno)); } else { vfprintf(stderr,format,args); fputc('\n',stderr); } } /* don't exit here - Krita can't handle errors otherwise */ /* exit(status); */ } void FatalGeneric(int status,const char* format,...) { va_list v; va_start(v,format); if( format ) fprintf(stderr,"%s: ",progname); vFatalGeneric(status,format,v); } void FatalUnexpected(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: ",progname); vFatalGeneric(127,format,v) ; } void FatalBadXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s:\n ",progname,_("Corrupted or malformed XCF file")); vFatalGeneric(125,format,v) ; } int xcfCheckspace(uint32_t addr,int spaceafter,const char *format,...) { if( xcf_length < spaceafter || addr > xcf_length - spaceafter ) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname,_("Corrupted or truncated XCF file")); fprintf(stderr,"(0x%" PRIXPTR " bytes): ",(uintptr_t)xcf_length); vFatalGeneric(125,format,v) ; return XCF_ERROR; } return XCF_OK; } void FatalUnsupportedXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname, _("The image contains features not understood by this program:")); vFatalGeneric(123,format,v) ; } void gpl_blurb(void) { fprintf(stderr,PACKAGE_STRING "\n"); fprintf(stderr, _("Type \"%s -h\" to get an option summary.\n"),progname); /* don't exit here - Krita will close otherwise */ /* exit(1) ; */ } /* ******************************************************* */ void * xcfmalloc(size_t size) { void *ptr = malloc(size); if( !ptr ) { FatalUnexpected(_("Out of memory")); return XCF_PTR_EMPTY; } return ptr ; } void xcffree(void *block) { if( xcf_file && (uint8_t*)block >= xcf_file && (uint8_t*)block < xcf_file + xcf_length ) ; else free(block); } /* ******************************************************* */ FILE * openout(const char *name) { FILE *newfile ; if( strcmp(name,"-") == 0 ) return stdout ; newfile = fopen(name,"wb") ; if( newfile == NULL ) { FatalUnexpected(_("!Cannot create file %s"),name); return XCF_PTR_EMPTY; } return newfile ; } int closeout(FILE *f,const char *name) { if( f == NULL ) return XCF_OK; if( fflush(f) == 0 ) { errno = 0 ; if( !ferror(f) ) { if( fclose(f) == 0 ) return XCF_OK; } else if( errno == 0 ) { /* Attempt to coax a valid errno out of the standard library, * following an idea by Bruno Haible * http://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html */ if( fputc('\0', f) != EOF && fflush(f) == 0 ) errno = EIO ; /* Argh, everything succedes. Just call it an I/O error */ } } FatalUnexpected(_("!Error writing file %s"),name); return XCF_ERROR; } diff --git a/plugins/tools/basictools/CMakeLists.txt b/plugins/tools/basictools/CMakeLists.txt index c0771eafd5..9d725a7c7a 100644 --- a/plugins/tools/basictools/CMakeLists.txt +++ b/plugins/tools/basictools/CMakeLists.txt @@ -1,44 +1,45 @@ if (NOT APPLE) add_subdirectory(tests) endif () set(kritadefaulttools_SOURCES default_tools.cc kis_tool_colorpicker.cc kis_tool_brush.cc kis_tool_line.cc kis_tool_line_helper.cpp kis_tool_fill.cc kis_tool_rectangle.cc kis_tool_ellipse.cc kis_tool_gradient.cc kis_tool_measure.cc kis_tool_path.cc kis_tool_move.cc kis_tool_movetooloptionswidget.cpp strokes/move_selection_stroke_strategy.cpp + KisMoveBoundsCalculationJob.cpp kis_tool_multihand.cpp kis_tool_multihand_config.cpp kis_tool_pencil.cc kis_tool_pan.cpp ) ki18n_wrap_ui(kritadefaulttools_SOURCES wdgcolorpicker.ui wdgmovetool.ui wdgmultihandtool.ui) qt5_add_resources(kritadefaulttools_SOURCES defaulttools.qrc ) add_library(kritadefaulttools MODULE ${kritadefaulttools_SOURCES}) generate_export_header(kritadefaulttools BASE_NAME kritadefaulttools) target_link_libraries(kritadefaulttools kritaui kritabasicflakes) target_link_libraries(kritadefaulttools ${Boost_SYSTEM_LIBRARY}) install(TARGETS kritadefaulttools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) ########### install files ############### install( FILES KisToolPath.action KisToolPencil.action DESTINATION ${DATA_INSTALL_DIR}/krita/actions) diff --git a/plugins/tools/basictools/KisMoveBoundsCalculationJob.cpp b/plugins/tools/basictools/KisMoveBoundsCalculationJob.cpp new file mode 100644 index 0000000000..7d1a97f054 --- /dev/null +++ b/plugins/tools/basictools/KisMoveBoundsCalculationJob.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Dmitry Kazakov + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KisMoveBoundsCalculationJob.h" +#include "kis_node.h" +#include "kis_selection.h" + + +KisMoveBoundsCalculationJob::KisMoveBoundsCalculationJob(KisNodeList nodes, + KisSelectionSP selection, + QObject *requestedBy) + : m_nodes(nodes), + m_selection(selection), + m_requestedBy(requestedBy) +{ + setExclusive(true); +} + +void KisMoveBoundsCalculationJob::run() +{ + QRect handlesRect; + + Q_FOREACH (KisNodeSP node, m_nodes) { + handlesRect |= node->exactBounds(); + } + + if (m_selection) { + handlesRect &= m_selection->selectedExactRect(); + } + + emit sigCalcualtionFinished(handlesRect); +} + +bool KisMoveBoundsCalculationJob::overrides(const KisSpontaneousJob *_otherJob) +{ + const KisMoveBoundsCalculationJob *otherJob = + dynamic_cast(_otherJob); + + return otherJob && otherJob->m_requestedBy == m_requestedBy; +} + +int KisMoveBoundsCalculationJob::levelOfDetail() const +{ + return 0; +} diff --git a/libs/command/kundo2commandextradata.h b/plugins/tools/basictools/KisMoveBoundsCalculationJob.h similarity index 50% copy from libs/command/kundo2commandextradata.h copy to plugins/tools/basictools/KisMoveBoundsCalculationJob.h index 3524d763ad..0c1f2037c3 100644 --- a/libs/command/kundo2commandextradata.h +++ b/plugins/tools/basictools/KisMoveBoundsCalculationJob.h @@ -1,31 +1,46 @@ /* - * Copyright (c) 2015 Dmitry Kazakov + * Copyright (c) 2019 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __KUNDO2COMMANDEXTRADATA_H -#define __KUNDO2COMMANDEXTRADATA_H +#ifndef KISMOVEBOUNDSCALCULATIONJOB_H +#define KISMOVEBOUNDSCALCULATIONJOB_H -#include "kritacommand_export.h" +#include +#include "kis_spontaneous_job.h" +#include "kis_types.h" +#include "kis_selection.h" - -class KRITACOMMAND_EXPORT KUndo2CommandExtraData +class KisMoveBoundsCalculationJob : public QObject, public KisSpontaneousJob { + Q_OBJECT public: - virtual ~KUndo2CommandExtraData(); + KisMoveBoundsCalculationJob(KisNodeList nodes, KisSelectionSP selection, QObject *requestedBy); + + void run() override; + bool overrides(const KisSpontaneousJob *otherJob) override; + int levelOfDetail() const override; + +Q_SIGNALS: + void sigCalcualtionFinished(const QRect &bounds); + +private: + KisNodeList m_nodes; + KisSelectionSP m_selection; + QObject *m_requestedBy; }; -#endif /* __KUNDO2COMMANDEXTRADATA_H */ +#endif // KISMOVEBOUNDSCALCULATIONJOB_H diff --git a/plugins/tools/basictools/kis_tool_ellipse.cc b/plugins/tools/basictools/kis_tool_ellipse.cc index 812f3ed8a0..716b588edc 100644 --- a/plugins/tools/basictools/kis_tool_ellipse.cc +++ b/plugins/tools/basictools/kis_tool_ellipse.cc @@ -1,83 +1,82 @@ /* * kis_tool_ellipse.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_ellipse.h" #include #include #include #include "kis_figure_painting_tool_helper.h" #include KisToolEllipse::KisToolEllipse(KoCanvasBase * canvas) : KisToolEllipseBase(canvas, KisToolEllipseBase::PAINT, KisCursor::load("tool_ellipse_cursor.png", 6, 6)) { setObjectName("tool_ellipse"); setSupportOutline(true); } KisToolEllipse::~KisToolEllipse() { } void KisToolEllipse::resetCursorStyle() { KisToolEllipseBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolEllipse::finishRect(const QRectF& rect, qreal roundCornersX, qreal roundCornersY) { Q_UNUSED(roundCornersX); Q_UNUSED(roundCornersY); - if (rect.isEmpty() || !blockUntilOperationsFinished()) + if (rect.isEmpty()) return; const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Ellipse"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintEllipse(rect); } else { QRectF r = convertToPt(rect); KoShape* shape = KisShapeToolHelper::createEllipseShape(r); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); shape->setStroke(border); info.markAsSelectionShapeIfNeeded(shape); addShape(shape); } - notifyModified(); } diff --git a/plugins/tools/basictools/kis_tool_gradient.cc b/plugins/tools/basictools/kis_tool_gradient.cc index 1c620b7da6..db5b4dded5 100644 --- a/plugins/tools/basictools/kis_tool_gradient.cc +++ b/plugins/tools/basictools/kis_tool_gradient.cc @@ -1,304 +1,321 @@ /* * kis_tool_gradient.cc - part of Krita * * Copyright (c) 2002 Patrick Julien * Copyright (c) 2003 Boudewijn Rempt * Copyright (c) 2004-2007 Adrian Page * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_gradient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_resources_snapshot.h" +#include "kis_command_utils.h" +#include "kis_processing_applicator.h" +#include "kis_processing_visitor.h" KisToolGradient::KisToolGradient(KoCanvasBase * canvas) : KisToolPaint(canvas, KisCursor::load("tool_gradient_cursor.png", 6, 6)) { setObjectName("tool_gradient"); m_startPos = QPointF(0, 0); m_endPos = QPointF(0, 0); m_reverse = false; m_shape = KisGradientPainter::GradientShapeLinear; m_repeat = KisGradientPainter::GradientRepeatNone; m_antiAliasThreshold = 0.2; } KisToolGradient::~KisToolGradient() { } void KisToolGradient::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolGradient::activate(ToolActivation toolActivation, const QSet &shapes) { KisToolPaint::activate(toolActivation, shapes); m_configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolGradient::paint(QPainter &painter, const KoViewConverter &converter) { if (mode() == KisTool::PAINT_MODE && m_startPos != m_endPos) { qreal sx, sy; converter.zoom(&sx, &sy); painter.scale(sx / currentImage()->xRes(), sy / currentImage()->yRes()); paintLine(painter); } } void KisToolGradient::beginPrimaryAction(KoPointerEvent *event) { if (!nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); m_startPos = convertToPixelCoordAndSnap(event, QPointF(), false); m_endPos = m_startPos; } void KisToolGradient::continuePrimaryAction(KoPointerEvent *event) { /** * TODO: The gradient tool is still not in strokes, so the end of * its action can call processEvent(), which would result in * nested event hadler calls. Please uncomment this line * when the tool is ported to strokes. */ //CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false); QRectF bound(m_startPos, m_endPos); canvas()->updateCanvas(convertToPt(bound.normalized())); if (event->modifiers() == Qt::ShiftModifier) { m_endPos = straightLine(pos); } else { m_endPos = pos; } bound.setTopLeft(m_startPos); bound.setBottomRight(m_endPos); canvas()->updateCanvas(convertToPt(bound.normalized())); } void KisToolGradient::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); - if (!currentNode() || !blockUntilOperationsFinished()) + if (!currentNode()) return; if (m_startPos == m_endPos) { return; } - KisPaintDeviceSP device; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), this->canvas()->resourceManager()); - if (image && (device = resources->currentNode()->paintDevice())) { - QApplication::setOverrideCursor(Qt::BusyCursor); + if (image && resources->currentNode()->paintDevice()) { + // TODO: refactor out local variables when we switch to C++14 + QPointF startPos = m_startPos; + QPointF endPos = m_endPos; + KisGradientPainter::enumGradientShape shape = m_shape; + KisGradientPainter::enumGradientRepeat repeat = m_repeat; + bool reverse = m_reverse; + double antiAliasThreshold = m_antiAliasThreshold; KUndo2MagicString actionName = kundo2_i18n("Gradient"); - KisUndoAdapter *undoAdapter = image->undoAdapter(); - undoAdapter->beginMacro(actionName); - - KisGradientPainter painter(device, resources->activeSelection()); - resources->setupPainter(&painter); - - painter.beginTransaction(); - - KisCanvas2 * canvas = dynamic_cast(this->canvas()); - KoUpdaterPtr updater = canvas->viewManager()->createUnthreadedUpdater(i18nc("@info:progress", "Gradient...")); - - painter.setProgress(updater); - - painter.setGradientShape(m_shape); - painter.paintGradient(m_startPos, m_endPos, m_repeat, m_antiAliasThreshold, m_reverse, 0, 0, image->width(), image->height()); - painter.endTransaction(undoAdapter); - undoAdapter->endMacro(); - - QApplication::restoreOverrideCursor(); - currentNode()->setDirty(); - notifyModified(); + KisProcessingApplicator applicator(image, resources->currentNode(), + KisProcessingApplicator::NONE, + KisImageSignalVector() << ModifiedSignal, + actionName); + + applicator.applyCommand( + new KisCommandUtils::LambdaCommand( + [resources, startPos, endPos, + shape, repeat, reverse, antiAliasThreshold] () mutable { + + KisNodeSP node = resources->currentNode(); + KisPaintDeviceSP device = node->paintDevice(); + KisProcessingVisitor::ProgressHelper helper(node); + const QRect bounds = device->defaultBounds()->bounds(); + + KisGradientPainter painter(device, resources->activeSelection()); + resources->setupPainter(&painter); + painter.setProgress(helper.updater()); + + painter.beginTransaction(); + + painter.setGradientShape(shape); + painter.paintGradient(startPos, endPos, + repeat, antiAliasThreshold, + reverse, 0, 0, + bounds.width(), bounds.height()); + + return painter.endAndTakeTransaction(); + })); + applicator.end(); } canvas()->updateCanvas(convertToPt(currentImage()->bounds())); } QPointF KisToolGradient::straightLine(QPointF point) { QPointF comparison = point - m_startPos; QPointF result; if (fabs(comparison.x()) > fabs(comparison.y())) { result.setX(point.x()); result.setY(m_startPos.y()); } else { result.setX(m_startPos.x()); result.setY(point.y()); } return result; } void KisToolGradient::paintLine(QPainter& gc) { if (canvas()) { QPen old = gc.pen(); QPen pen(Qt::SolidLine); gc.setPen(pen); gc.drawLine(m_startPos, m_endPos); gc.setPen(old); } } QWidget* KisToolGradient::createOptionWidget() { QWidget *widget = KisToolPaint::createOptionWidget(); Q_CHECK_PTR(widget); widget->setObjectName(toolId() + " option widget"); // Make sure to create the connections last after everything is set up. The initialized values // won't be loaded from the configuration file if you add the widget before the connection m_lbShape = new QLabel(i18n("Shape:"), widget); m_cmbShape = new KComboBox(widget); m_cmbShape->setObjectName("shape_combo"); m_cmbShape->addItem(i18nc("the gradient will be drawn linearly", "Linear")); m_cmbShape->addItem(i18nc("the gradient will be drawn bilinearly", "Bi-Linear")); m_cmbShape->addItem(i18nc("the gradient will be drawn radially", "Radial")); m_cmbShape->addItem(i18nc("the gradient will be drawn in a square around a centre", "Square")); m_cmbShape->addItem(i18nc("the gradient will be drawn as an asymmetric cone", "Conical")); m_cmbShape->addItem(i18nc("the gradient will be drawn as a symmetric cone", "Conical Symmetric")); m_cmbShape->addItem(i18nc("the gradient will be drawn in a selection outline", "Shaped")); addOptionWidgetOption(m_cmbShape, m_lbShape); connect(m_cmbShape, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetShape(int))); m_lbRepeat = new QLabel(i18n("Repeat:"), widget); m_cmbRepeat = new KComboBox(widget); m_cmbRepeat->setObjectName("repeat_combo"); m_cmbRepeat->addItem(i18nc("The gradient will not repeat", "None")); m_cmbRepeat->addItem(i18nc("The gradient will repeat forwards", "Forwards")); m_cmbRepeat->addItem(i18nc("The gradient will repeat alternatingly", "Alternating")); addOptionWidgetOption(m_cmbRepeat, m_lbRepeat); connect(m_cmbRepeat, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetRepeat(int))); m_lbAntiAliasThreshold = new QLabel(i18n("Anti-alias threshold:"), widget); m_slAntiAliasThreshold = new KisDoubleSliderSpinBox(widget); m_slAntiAliasThreshold->setObjectName("threshold_slider"); m_slAntiAliasThreshold->setRange(0, 1, 3); addOptionWidgetOption(m_slAntiAliasThreshold, m_lbAntiAliasThreshold); connect(m_slAntiAliasThreshold, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAntiAliasThreshold(qreal))); m_ckReverse = new QCheckBox(i18nc("the gradient will be drawn with the color order reversed", "Reverse"), widget); m_ckReverse->setObjectName("reverse_check"); connect(m_ckReverse, SIGNAL(toggled(bool)), this, SLOT(slotSetReverse(bool))); addOptionWidgetOption(m_ckReverse); widget->setFixedHeight(widget->sizeHint().height()); // load configuration settings into widget (updating UI will update internal variables from signals/slots) m_ckReverse->setChecked((bool)m_configGroup.readEntry("reverse", false)); m_cmbShape->setCurrentIndex((int)m_configGroup.readEntry("shape", 0)); m_cmbRepeat->setCurrentIndex((int)m_configGroup.readEntry("repeat", 0)); m_slAntiAliasThreshold->setValue((qreal)m_configGroup.readEntry("antialiasThreshold", 0.0)); return widget; } void KisToolGradient::slotSetShape(int shape) { m_shape = static_cast(shape); m_configGroup.writeEntry("shape", shape); } void KisToolGradient::slotSetRepeat(int repeat) { m_repeat = static_cast(repeat); m_configGroup.writeEntry("repeat", repeat); } void KisToolGradient::slotSetReverse(bool state) { m_reverse = state; m_configGroup.writeEntry("reverse", state); } void KisToolGradient::slotSetAntiAliasThreshold(qreal value) { m_antiAliasThreshold = value; m_configGroup.writeEntry("antialiasThreshold", value); } void KisToolGradient::setOpacity(qreal opacity) { m_opacity = opacity; } diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc index b8222a8cb0..3abda4d635 100644 --- a/plugins/tools/basictools/kis_tool_move.cc +++ b/plugins/tools/basictools/kis_tool_move.cc @@ -1,683 +1,699 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2002 Patrick Julien * 2004 Boudewijn Rempt * 2016 Michael Abrahams * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_move.h" #include #include "kis_cursor.h" #include "kis_selection.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_tool_utils.h" #include "kis_paint_layer.h" #include "strokes/move_stroke_strategy.h" #include "kis_tool_movetooloptionswidget.h" #include "strokes/move_selection_stroke_strategy.h" #include "kis_resources_snapshot.h" #include "kis_action_registry.h" #include "krita_utils.h" #include #include #include "kis_node_manager.h" +#include "kis_selection_manager.h" #include "kis_signals_blocker.h" #include +#include "KisMoveBoundsCalculationJob.h" + struct KisToolMoveState : KisToolChangesTrackerData, boost::equality_comparable { KisToolMoveState(QPoint _accumulatedOffset) : accumulatedOffset(_accumulatedOffset) {} KisToolChangesTrackerData* clone() const { return new KisToolMoveState(*this); } bool operator ==(const KisToolMoveState &rhs) { return accumulatedOffset == rhs.accumulatedOffset; } QPoint accumulatedOffset; }; KisToolMove::KisToolMove(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::moveCursor()) , m_updateCursorCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { setObjectName("tool_move"); m_showCoordinatesAction = action("movetool-show-coordinates"); m_showCoordinatesAction = action("movetool-show-coordinates"); connect(&m_updateCursorCompressor, SIGNAL(timeout()), this, SLOT(resetCursorStyle())); m_optionsWidget = new MoveToolOptionsWidget(0, currentImage()->xRes(), toolId()); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); m_showCoordinatesAction->setChecked(m_optionsWidget->showCoordinates()); m_optionsWidget->slotSetTranslate(m_handlesRect.topLeft() + currentOffset()); connect(m_optionsWidget, SIGNAL(sigSetTranslateX(int)), SLOT(moveBySpinX(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigSetTranslateY(int)), SLOT(moveBySpinY(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigRequestCommitOffsetChanges()), this, SLOT(commitChanges()), Qt::UniqueConnection); connect(this, SIGNAL(moveInNewPosition(QPoint)), m_optionsWidget, SLOT(slotSetTranslate(QPoint)), Qt::UniqueConnection); connect(qobject_cast(canvas)->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)), this, SLOT(slotNodeChanged(KisNodeList)), Qt::UniqueConnection); + connect(qobject_cast(canvas)->viewManager()->selectionManager(), SIGNAL(currentSelectionChanged()), this, SLOT(slotSelectionChanged()), Qt::UniqueConnection); } KisToolMove::~KisToolMove() { endStroke(); } void KisToolMove::resetCursorStyle() { KisTool::resetCursorStyle(); if (!isActive()) return; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisNodeList nodes = fetchSelectedNodes(moveToolMode(), &m_lastCursorPos, selection); if (nodes.isEmpty()) { canvas()->setCursor(Qt::ForbiddenCursor); } } KisNodeList KisToolMove::fetchSelectedNodes(MoveToolMode mode, const QPoint *pixelPoint, KisSelectionSP selection) { KisNodeList nodes; KisImageSP image = this->image(); if (mode != MoveSelectedLayer && pixelPoint) { const bool wholeGroup = !selection && mode == MoveGroup; KisNodeSP node = KisToolUtils::findNode(image->root(), *pixelPoint, wholeGroup); if (node) { nodes = {node}; } } if (nodes.isEmpty()) { nodes = this->selectedNodes(); KritaUtils::filterContainer(nodes, [](KisNodeSP node) { return node->isEditable(); }); } return nodes; } bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos) { KisNodeSP node; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisNodeList nodes = fetchSelectedNodes(mode, pos, selection); if (nodes.size() == 1) { node = nodes.first(); } if (nodes.isEmpty()) { return false; } /** * If the target node has changed, the stroke should be * restarted. Otherwise just continue processing current node. */ if (m_strokeId && !tryEndPreviousStroke(nodes)) { return true; } - /** - * FIXME: The move tool is not completely asynchronous, it - * needs the content of the layer and/or selection to calculate - * the bounding rectange. Technically, we can move its calculation - * into the stroke itself and pass it back to the tool via a signal. - * - * But currently, we will just disable starting a new stroke - * asynchronously. - */ - if (!blockUntilOperationsFinished()) { - return false; - } - - initHandles(nodes); - KisStrokeStrategy *strategy; KisPaintLayerSP paintLayer = node ? dynamic_cast(node.data()) : 0; if (paintLayer && selection && (!selection->selectedRect().isEmpty() && !selection->selectedExactRect().isEmpty())) { - strategy = + MoveSelectionStrokeStrategy *moveStrategy = new MoveSelectionStrokeStrategy(paintLayer, selection, image.data(), image.data()); + + connect(moveStrategy, + SIGNAL(sigHandlesRectCalculated(const QRect&)), + SLOT(slotHandlesRectCalculated(const QRect&))); + + strategy = moveStrategy; + } else { - strategy = + + MoveStrokeStrategy *moveStrategy = new MoveStrokeStrategy(nodes, image.data(), image.data()); + connect(moveStrategy, + SIGNAL(sigHandlesRectCalculated(const QRect&)), + SLOT(slotHandlesRectCalculated(const QRect&))); + + strategy = moveStrategy; } + // disable outline feedback until the stroke calcualtes + // correct bounding rect + m_handlesRect = QRect(); m_strokeId = image->startStroke(strategy); m_currentlyProcessingNodes = nodes; m_accumulatedOffset = QPoint(); KIS_SAFE_ASSERT_RECOVER(m_changesTracker.isEmpty()) { m_changesTracker.reset(); } commitChanges(); return true; } QPoint KisToolMove::currentOffset() const { return m_accumulatedOffset + m_dragPos - m_dragStart; } void KisToolMove::notifyGuiAfterMove(bool showFloatingMessage) { if (!m_optionsWidget) return; + if (m_handlesRect.isEmpty()) return; const QPoint currentTopLeft = m_handlesRect.topLeft() + currentOffset(); KisSignalsBlocker b(m_optionsWidget); emit moveInNewPosition(currentTopLeft); // TODO: fetch this info not from options widget, but from config const bool showCoordinates = m_optionsWidget->showCoordinates(); if (showCoordinates && showFloatingMessage) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in move tool", "X: %1 px, Y: %2 px", currentTopLeft.x(), currentTopLeft.y()), QIcon(), 1000, KisFloatingMessage::High); } } bool KisToolMove::tryEndPreviousStroke(KisNodeList nodes) { if (!m_strokeId) return false; bool strokeEnded = false; if (!KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { endStroke(); strokeEnded = true; } return strokeEnded; } void KisToolMove::commitChanges() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); QSharedPointer newState(new KisToolMoveState(m_accumulatedOffset)); KisToolMoveState *lastState = dynamic_cast(m_changesTracker.lastState().data()); if (lastState && *lastState == *newState) return; m_changesTracker.commitConfig(newState); } +void KisToolMove::slotHandlesRectCalculated(const QRect &handlesRect) +{ + m_handlesRect = handlesRect; + notifyGuiAfterMove(false); +} + void KisToolMove::moveDiscrete(MoveDirection direction, bool big) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()) return; if (!image()) return; if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } // Larger movement if "shift" key is pressed. qreal scale = big ? m_optionsWidget->moveScale() : 1.0; qreal moveStep = m_optionsWidget->moveStep() * scale; const QPoint offset = direction == Up ? QPoint( 0, -moveStep) : direction == Down ? QPoint( 0, moveStep) : direction == Left ? QPoint(-moveStep, 0) : QPoint( moveStep, 0) ; m_accumulatedOffset += offset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); commitChanges(); setMode(KisTool::HOVER_MODE); } void KisToolMove::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUp())); m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDown())); m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeft())); m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRight())); m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUpMore())); m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDownMore())); m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeftMore())); m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRightMore())); connect(m_showCoordinatesAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(setShowCoordinates(bool)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(showCoordinatesChanged(bool)), m_showCoordinatesAction, SLOT(setChecked(bool)), Qt::UniqueConnection); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); slotNodeChanged(this->selectedNodes()); } void KisToolMove::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); - if (m_strokeId) { + if (m_strokeId && !m_handlesRect.isEmpty()) { QPainterPath handles; handles.addRect(m_handlesRect.translated(currentOffset())); QPainterPath path = pixelToView(handles); paintToolOutline(&gc, path); } } -void KisToolMove::initHandles(const KisNodeList &nodes) -{ - /** - * The handles should be initialized only once, **before** the start of - * the stroke. If the nodes change, we should restart the stroke. - */ - KIS_SAFE_ASSERT_RECOVER_NOOP(!m_strokeId); - - m_handlesRect = QRect(); - for (KisNodeSP node : nodes) { - node->exactBounds(); - m_handlesRect |= node->exactBounds(); - } - if (image()->globalSelection()) { - m_handlesRect &= image()->globalSelection()->selectedExactRect(); - } -} - void KisToolMove::deactivate() { m_actionConnections.clear(); disconnect(m_showCoordinatesAction, 0, this, 0); disconnect(m_optionsWidget, 0, this, 0); endStroke(); KisTool::deactivate(); } void KisToolMove::requestStrokeEnd() { endStroke(); } void KisToolMove::requestStrokeCancellation() { cancelStroke(); } void KisToolMove::requestUndoDuringStroke() { if (!m_strokeId) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolMove::beginPrimaryAction(KoPointerEvent *event) { startAction(event, moveToolMode()); } void KisToolMove::continuePrimaryAction(KoPointerEvent *event) { continueAction(event); } void KisToolMove::endPrimaryAction(KoPointerEvent *event) { endAction(event); } void KisToolMove::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { // Ctrl+Right click toggles between moving current layer and moving layer w/ content if (action == PickFgNode || action == PickBgImage) { MoveToolMode mode = moveToolMode(); if (mode == MoveSelectedLayer) { mode = MoveFirstLayer; } else if (mode == MoveFirstLayer) { mode = MoveSelectedLayer; } startAction(event, mode); } else { startAction(event, MoveGroup); } } void KisToolMove::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) continueAction(event); } void KisToolMove::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) endAction(event); } void KisToolMove::mouseMoveEvent(KoPointerEvent *event) { m_lastCursorPos = convertToPixelCoord(event).toPoint(); KisTool::mouseMoveEvent(event); if (moveToolMode() == MoveFirstLayer) { m_updateCursorCompressor.start(); } } void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode) { QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); m_dragStart = pos; m_dragPos = pos; if (startStrokeImpl(mode, &pos)) { setMode(KisTool::PAINT_MODE); } else { event->ignore(); m_dragPos = QPoint(); m_dragStart = QPoint(); } qobject_cast(canvas())->updateCanvas(); } void KisToolMove::continueAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); m_dragPos = pos; drag(pos); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::endAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); drag(pos); m_accumulatedOffset += pos - m_dragStart; m_dragStart = QPoint(); m_dragPos = QPoint(); commitChanges(); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::drag(const QPoint& newPos) { KisImageWSP image = currentImage(); QPoint offset = m_accumulatedOffset + newPos - m_dragStart; image->addJob(m_strokeId, new MoveStrokeStrategy::Data(offset)); } void KisToolMove::endStroke() { if (!m_strokeId) return; KisImageSP image = currentImage(); image->endStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_accumulatedOffset = QPoint(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::slotTrackerChangedConfig(KisToolChangesTrackerDataSP state) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); KisToolMoveState *newState = dynamic_cast(state.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newState); if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging m_accumulatedOffset = newState->accumulatedOffset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); } void KisToolMove::slotMoveDiscreteLeft() { moveDiscrete(MoveDirection::Left, false); } void KisToolMove::slotMoveDiscreteRight() { moveDiscrete(MoveDirection::Right, false); } void KisToolMove::slotMoveDiscreteUp() { moveDiscrete(MoveDirection::Up, false); } void KisToolMove::slotMoveDiscreteDown() { moveDiscrete(MoveDirection::Down, false); } void KisToolMove::slotMoveDiscreteLeftMore() { moveDiscrete(MoveDirection::Left, true); } void KisToolMove::slotMoveDiscreteRightMore() { moveDiscrete(MoveDirection::Right, true); } void KisToolMove::slotMoveDiscreteUpMore() { moveDiscrete(MoveDirection::Up, true); } void KisToolMove::slotMoveDiscreteDownMore() { moveDiscrete(MoveDirection::Down, true); } void KisToolMove::cancelStroke() { if (!m_strokeId) return; KisImageSP image = currentImage(); image->cancelStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_accumulatedOffset = QPoint(); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } QWidget* KisToolMove::createOptionWidget() { return m_optionsWidget; } KisToolMove::MoveToolMode KisToolMove::moveToolMode() const { if (m_optionsWidget) return m_optionsWidget->mode(); return MoveSelectedLayer; } QPoint KisToolMove::applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos) { QPoint move = pos - m_dragStart; // Snap to axis if (modifiers & Qt::ShiftModifier) { move = snapToClosestAxis(move); } // "Precision mode" - scale down movement by 1/5 if (modifiers & Qt::AltModifier) { const qreal SCALE_FACTOR = .2; move = SCALE_FACTOR * move; } return m_dragStart + move; } void KisToolMove::moveBySpinX(int newX) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.rx() = newX - m_handlesRect.x(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::moveBySpinY(int newY) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.ry() = newY - m_handlesRect.y(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } +void KisToolMove::requestHandlesRectUpdate() +{ + KisResourcesSnapshotSP resources = + new KisResourcesSnapshot(image(), currentNode(), canvas()->resourceManager()); + KisSelectionSP selection = resources->activeSelection(); + + KisMoveBoundsCalculationJob *job = new KisMoveBoundsCalculationJob(this->selectedNodes(), + selection, this); + connect(job, + SIGNAL(sigCalcualtionFinished(const QRect&)), + SLOT(slotHandlesRectCalculated(const QRect &))); + + KisImageSP image = this->image(); + image->addSpontaneousJob(job); + + notifyGuiAfterMove(false); +} + void KisToolMove::slotNodeChanged(KisNodeList nodes) { if (m_strokeId && !tryEndPreviousStroke(nodes)) { return; } + requestHandlesRectUpdate(); +} - initHandles(nodes); - notifyGuiAfterMove(false); +void KisToolMove::slotSelectionChanged() +{ + if (m_strokeId) return; + requestHandlesRectUpdate(); } QList KisToolMoveFactory::createActionsImpl() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QList actions = KisToolPaintFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("movetool-move-up"); actions << actionRegistry->makeQAction("movetool-move-down"); actions << actionRegistry->makeQAction("movetool-move-left"); actions << actionRegistry->makeQAction("movetool-move-right"); actions << actionRegistry->makeQAction("movetool-move-up-more"); actions << actionRegistry->makeQAction("movetool-move-down-more"); actions << actionRegistry->makeQAction("movetool-move-left-more"); actions << actionRegistry->makeQAction("movetool-move-right-more"); actions << actionRegistry->makeQAction("movetool-show-coordinates"); return actions; } diff --git a/plugins/tools/basictools/kis_tool_move.h b/plugins/tools/basictools/kis_tool_move.h index db9e39ab77..d71ccaa9c2 100644 --- a/plugins/tools/basictools/kis_tool_move.h +++ b/plugins/tools/basictools/kis_tool_move.h @@ -1,202 +1,205 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2003 Patrick Julien * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_MOVE_H_ #define KIS_TOOL_MOVE_H_ #include #include #include #include #include #include #include #include #include #include "KisToolChangesTracker.h" #include "kis_signal_compressor.h" #include "kis_signal_auto_connection.h" #include "kis_canvas2.h" class KoCanvasBase; class MoveToolOptionsWidget; class KisDocument; class KisToolMove : public KisTool { Q_OBJECT Q_ENUMS(MoveToolMode); public: KisToolMove(KoCanvasBase * canvas); ~KisToolMove() override; /** * @brief wantsAutoScroll * reimplemented from KoToolBase * there's an issue where autoscrolling with this tool never makes the * stroke end, so we return false here so that users don't get stuck with * the tool. See bug 362659 * @return false */ bool wantsAutoScroll() const override { return false; } public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; public Q_SLOTS: void requestStrokeEnd() override; void requestStrokeCancellation() override; void requestUndoDuringStroke() override; protected Q_SLOTS: void resetCursorStyle() override; public: enum MoveToolMode { MoveSelectedLayer, MoveFirstLayer, MoveGroup }; enum MoveDirection { Up, Down, Left, Right }; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; void mouseMoveEvent(KoPointerEvent *event) override; void startAction(KoPointerEvent *event, MoveToolMode mode); void continueAction(KoPointerEvent *event); void endAction(KoPointerEvent *event); void paint(QPainter& gc, const KoViewConverter &converter) override; - void initHandles(const KisNodeList &nodes); QWidget *createOptionWidget() override; void updateUIUnit(int newUnit); MoveToolMode moveToolMode() const; void setShowCoordinates(bool value); public Q_SLOTS: void moveDiscrete(MoveDirection direction, bool big); void moveBySpinX(int newX); void moveBySpinY(int newY); void slotNodeChanged(KisNodeList nodes); + void slotSelectionChanged(); void commitChanges(); + void slotHandlesRectCalculated(const QRect &handlesRect); + Q_SIGNALS: void moveToolModeChanged(); void moveInNewPosition(QPoint); private: void drag(const QPoint& newPos); void cancelStroke(); QPoint applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos); bool startStrokeImpl(MoveToolMode mode, const QPoint *pos); QPoint currentOffset() const; void notifyGuiAfterMove(bool showFloatingMessage = true); bool tryEndPreviousStroke(KisNodeList nodes); KisNodeList fetchSelectedNodes(MoveToolMode mode, const QPoint *pixelPoint, KisSelectionSP selection); + void requestHandlesRectUpdate(); private Q_SLOTS: void endStroke(); void slotTrackerChangedConfig(KisToolChangesTrackerDataSP state); void slotMoveDiscreteLeft(); void slotMoveDiscreteRight(); void slotMoveDiscreteUp(); void slotMoveDiscreteDown(); void slotMoveDiscreteLeftMore(); void slotMoveDiscreteRightMore(); void slotMoveDiscreteUpMore(); void slotMoveDiscreteDownMore(); private: MoveToolOptionsWidget* m_optionsWidget {0}; QPoint m_dragStart; ///< Point where current cursor dragging began QPoint m_accumulatedOffset; ///< Total offset including multiple clicks, up/down/left/right keys, etc. added together KisStrokeId m_strokeId; KisNodeList m_currentlyProcessingNodes; int m_resolution; QAction *m_showCoordinatesAction {0}; QPoint m_dragPos; QRect m_handlesRect; KisToolChangesTracker m_changesTracker; QPoint m_lastCursorPos; KisSignalCompressor m_updateCursorCompressor; KisSignalAutoConnectionsStore m_actionConnections; }; class KisToolMoveFactory : public KisToolPaintFactoryBase { public: KisToolMoveFactory() : KisToolPaintFactoryBase("KritaTransform/KisToolMove") { setToolTip(i18n("Move Tool")); setSection(TOOL_TYPE_TRANSFORM); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); setPriority(3); setIconName(koIconNameCStr("krita_tool_move")); setShortcut(QKeySequence(Qt::Key_T)); } ~KisToolMoveFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolMove(canvas); } QList createActionsImpl() override; }; #endif // KIS_TOOL_MOVE_H_ diff --git a/plugins/tools/basictools/kis_tool_rectangle.cc b/plugins/tools/basictools/kis_tool_rectangle.cc index fadc347dcb..2f983d336e 100644 --- a/plugins/tools/basictools/kis_tool_rectangle.cc +++ b/plugins/tools/basictools/kis_tool_rectangle.cc @@ -1,100 +1,98 @@ /* * kis_tool_rectangle.cc - part of Krita * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_rectangle.h" #include #include #include "KoCanvasBase.h" #include "kis_shape_tool_helper.h" #include "kis_figure_painting_tool_helper.h" #include #include KisToolRectangle::KisToolRectangle(KoCanvasBase * canvas) : KisToolRectangleBase(canvas, KisToolRectangleBase::PAINT, KisCursor::load("tool_rectangle_cursor.png", 6, 6)) { setSupportOutline(true); setObjectName("tool_rectangle"); } KisToolRectangle::~KisToolRectangle() { } void KisToolRectangle::resetCursorStyle() { KisToolRectangleBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolRectangle::finishRect(const QRectF &rect, qreal roundCornersX, qreal roundCornersY) { - if (rect.isNull() || !blockUntilOperationsFinished()) + if (rect.isNull()) return; const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Rectangle"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), fillStyle()); QPainterPath path; if (roundCornersX > 0 || roundCornersY > 0) { path.addRoundedRect(rect, roundCornersX, roundCornersY); } else { path.addRect(rect); } helper.paintPainterPath(path); } else { const QRectF r = convertToPt(rect); const qreal docRoundCornersX = convertToPt(roundCornersX); const qreal docRoundCornersY = convertToPt(roundCornersY); KoShape* shape = KisShapeToolHelper::createRectangleShape(r, docRoundCornersX, docRoundCornersY); KoShapeStrokeSP border; if (strokeStyle() == KisPainter::StrokeStyleBrush) { border = toQShared(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); } shape->setStroke(border); info.markAsSelectionShapeIfNeeded(shape); addShape(shape); } - - notifyModified(); } diff --git a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp index 02d8d4875a..8a0e1f8485 100644 --- a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp +++ b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp @@ -1,177 +1,180 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "move_selection_stroke_strategy.h" #include #include #include #include "kis_image.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include MoveSelectionStrokeStrategy::MoveSelectionStrokeStrategy(KisPaintLayerSP paintLayer, KisSelectionSP selection, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move Selection"), false, undoFacade), m_paintLayer(paintLayer), m_selection(selection), m_updatesFacade(updatesFacade) { /** * Selection might have some update projection jobs pending, so we should ensure * all of them are completed before we start our stroke. */ enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER); enableJob(KisSimpleStrokeStrategy::JOB_FINISH); enableJob(KisSimpleStrokeStrategy::JOB_CANCEL); } MoveSelectionStrokeStrategy::MoveSelectionStrokeStrategy(const MoveSelectionStrokeStrategy &rhs) - : KisStrokeStrategyUndoCommandBased(rhs), + : QObject(), + KisStrokeStrategyUndoCommandBased(rhs), m_paintLayer(rhs.m_paintLayer), m_selection(rhs.m_selection), m_updatesFacade(rhs.m_updatesFacade) { } void MoveSelectionStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice(); KisPaintDeviceSP movedDevice = new KisPaintDevice(m_paintLayer.data(), paintDevice->colorSpace()); QRect copyRect = m_selection->selectedRect(); KisPainter gc(movedDevice); gc.setSelection(m_selection); gc.bitBlt(copyRect.topLeft(), paintDevice, copyRect); gc.end(); KisTransaction cutTransaction(name(), paintDevice); paintDevice->clearSelection(m_selection); runAndSaveCommand(KUndo2CommandSP(cutTransaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); indirect->setTemporaryTarget(movedDevice); indirect->setTemporaryCompositeOp(COMPOSITE_OVER); indirect->setTemporaryOpacity(OPACITY_OPAQUE_U8); m_initialDeviceOffset = QPoint(movedDevice->x(), movedDevice->y()); m_selection->setVisible(false); + + emit sigHandlesRectCalculated(movedDevice->exactBounds()); } void MoveSelectionStrokeStrategy::finishStrokeCallback() { KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); KisTransaction transaction(name(), m_paintLayer->paintDevice()); indirect->mergeToLayer(m_paintLayer, (KisPostExecutionUndoAdapter*)0, KUndo2MagicString()); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); indirect->setTemporaryTarget(0); QPoint selectionOffset(m_selection->x(), m_selection->y()); m_updatesFacade->blockUpdates(); KUndo2CommandSP moveSelectionCommand( new KisSelectionMoveCommand2(m_selection, selectionOffset, selectionOffset + m_finalOffset)); runAndSaveCommand( moveSelectionCommand, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); m_updatesFacade->unblockUpdates(); m_selection->setVisible(true); KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void MoveSelectionStrokeStrategy::cancelStrokeCallback() { KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); if (indirect) { KisPaintDeviceSP t = indirect->temporaryTarget(); if (t) { QRegion dirtyRegion = t->region(); indirect->setTemporaryTarget(0); m_selection->setVisible(true); m_paintLayer->setDirty(dirtyRegion); } } KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } #include "tool/strokes/move_stroke_strategy.h" void MoveSelectionStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { MoveStrokeStrategy::Data *d = dynamic_cast(data); if (d) { KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); KisPaintDeviceSP movedDevice = indirect->temporaryTarget(); QRegion dirtyRegion = movedDevice->region(); QPoint currentDeviceOffset(movedDevice->x(), movedDevice->y()); QPoint newDeviceOffset(m_initialDeviceOffset + d->offset); dirtyRegion |= dirtyRegion.translated(newDeviceOffset - currentDeviceOffset); movedDevice->setX(newDeviceOffset.x()); movedDevice->setY(newDeviceOffset.y()); m_finalOffset = d->offset; m_paintLayer->setDirty(dirtyRegion); } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } KisStrokeStrategy* MoveSelectionStrokeStrategy::createLodClone(int levelOfDetail) { Q_UNUSED(levelOfDetail); MoveSelectionStrokeStrategy *clone = new MoveSelectionStrokeStrategy(*this); return clone; } diff --git a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h index 782757169b..638b0ff385 100644 --- a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h +++ b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.h @@ -1,55 +1,62 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __MOVE_SELECTION_STROKE_STRATEGY_H #define __MOVE_SELECTION_STROKE_STRATEGY_H #include "kis_stroke_strategy_undo_command_based.h" #include "kis_types.h" +#include "kis_selection.h" +#include "kis_paint_layer.h" class KisPostExecutionUndoAdapter; class KisUpdatesFacade; -class MoveSelectionStrokeStrategy : public KisStrokeStrategyUndoCommandBased +class MoveSelectionStrokeStrategy : public QObject, public KisStrokeStrategyUndoCommandBased { + Q_OBJECT + public: MoveSelectionStrokeStrategy(KisPaintLayerSP paintLayer, KisSelectionSP selection, KisUpdatesFacade *updatesFacade, KisStrokeUndoFacade *undoFacade); void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; +Q_SIGNALS: + void sigHandlesRectCalculated(const QRect &handlesRect); + private: MoveSelectionStrokeStrategy(const MoveSelectionStrokeStrategy &rhs); KisStrokeStrategy* createLodClone(int levelOfDetail) override; private: KisPaintLayerSP m_paintLayer; KisSelectionSP m_selection; KisUpdatesFacade *m_updatesFacade; QPoint m_finalOffset; QPoint m_initialDeviceOffset; }; #endif /* __MOVE_SELECTION_STROKE_STRATEGY_H */ diff --git a/plugins/tools/tool_polygon/kis_tool_polygon.cc b/plugins/tools/tool_polygon/kis_tool_polygon.cc index b9fd36c773..9644f53682 100644 --- a/plugins/tools/tool_polygon/kis_tool_polygon.cc +++ b/plugins/tools/tool_polygon/kis_tool_polygon.cc @@ -1,83 +1,81 @@ /* * kis_tool_polygon.cc -- part of Krita * * Copyright (c) 2004 Michael Thaler * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * Copyright (C) 2010 Boudewijn Rempt * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_polygon.h" #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" KisToolPolygon::KisToolPolygon(KoCanvasBase *canvas) : KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polygon_cursor.png", 6, 6)) { setObjectName("tool_polygon"); setSupportOutline(true); } KisToolPolygon::~KisToolPolygon() { } void KisToolPolygon::resetCursorStyle() { KisToolPolylineBase::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolPolygon::finishPolyline(const QVector& points) { - if (!blockUntilOperationsFinished()) return; - const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polygon"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPolygon(points); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(points[0])); for (int i = 1; i < points.count(); i++) path->lineTo(resolutionMatrix.map(points[i])); path->close(); path->normalize(); info.markAsSelectionShapeIfNeeded(path); addShape(path); } } diff --git a/plugins/tools/tool_polyline/kis_tool_polyline.cc b/plugins/tools/tool_polyline/kis_tool_polyline.cc index e7c38d1128..274dae1250 100644 --- a/plugins/tools/tool_polyline/kis_tool_polyline.cc +++ b/plugins/tools/tool_polyline/kis_tool_polyline.cc @@ -1,85 +1,82 @@ /* * Copyright (c) 2004 Michael Thaler * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2010 Cyrille Berger * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_polyline.h" #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" KisToolPolyline::KisToolPolyline(KoCanvasBase * canvas) : KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polyline_cursor.png", 6, 6)) { setObjectName("tool_polyline"); setSupportOutline(true); } KisToolPolyline::~KisToolPolyline() { } void KisToolPolyline::resetCursorStyle() { KisToolPolylineBase::resetCursorStyle(); overrideCursorIfNotEditable(); } QWidget* KisToolPolyline::createOptionWidget() { return KisToolPolylineBase::createOptionWidget(); } void KisToolPolyline::finishPolyline(const QVector& points) { - if (!blockUntilOperationsFinished()) return; - const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if (!info.shouldAddShape || info.shouldAddSelectionShape) { KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image(), currentNode(), canvas()->resourceManager(), strokeStyle(), fillStyle()); helper.paintPolyline(points); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(points[0])); for (int i = 1; i < points.count(); i++) path->lineTo(resolutionMatrix.map(points[i])); path->normalize(); addShape(path); } - notifyModified(); } diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 5545dc4950..31e3656630 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1322 +1,1135 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "krita_container_utils.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; warpAction = new KisAction(i18n("Warp")); liquifyAction = new KisAction(i18n("Liquify")); cageAction = new KisAction(i18n("Cage")); freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); applyTransformation = new KisAction(i18n("Apply")); resetTransformation = new KisAction(i18n("Reset")); m_contextMenu.reset(new QMenu()); connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); - if (!m_strokeData.strokeId()) return; + if (!m_strokeId || !m_transaction.rootNode()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } - if (!m_strokeData.strokeId()) { + if (!m_strokeId) { useCursor(KisCursor::pointingHandCursor()); + } else if (m_strokeId && !m_transaction.rootNode()) { + // we are in the middle of stroke initialization + useCursor(KisCursor::waitCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } - if (!m_strokeData.strokeId()) { + if (!m_strokeId) { startStroke(m_currentArgs.mode(), false); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); m_contextMenu->addSection(i18n("Transform Tool Actions")); m_contextMenu->addSeparator(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == FreeTransformMode) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } m_contextMenu->addSeparator(); m_contextMenu->addAction(applyTransformation); m_contextMenu->addAction(resetTransformation); } return m_contextMenu.data(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } -bool KisToolTransform::tryInitArgsFromNode(KisNodeSP node) -{ - bool result = false; - - if (KisTransformMaskSP mask = - dynamic_cast(node.data())) { - - KisTransformMaskParamsInterfaceSP savedParams = - mask->transformParams(); - - KisTransformMaskAdapter *adapter = - dynamic_cast(savedParams.data()); - - if (adapter) { - m_currentArgs = adapter->transformArgs(); - result = true; - } - } - - return result; -} - -bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) -{ - bool result = false; - - const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); - KisNodeSP oldRootNode; - KisNodeList oldTransformedNodes; - - if (lastCommand && - TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode, &oldTransformedNodes) && - args->mode() == mode && - oldRootNode == currentNode) { - - KisNodeList perspectiveTransformedNodes = fetchNodesList(mode, currentNode, m_workRecursively); - - if (KritaUtils::compareListsUnordered(oldTransformedNodes, perspectiveTransformedNodes)) { - args->saveContinuedState(); - image()->undoAdapter()->undoLastCommand(); - - // FIXME: can we make it async? - image()->waitForDone(); - forceRepaintDelayedLayers(oldRootNode); - - result = true; - } - } - - return result; -} - -void KisToolTransform::resetArgsForMode(ToolTransformArgs::TransformMode mode) -{ - // NOTE: we are requesting an old value of m_currentArgs variable - // here, which is global, don't forget about this on higher - // levels. - QString filterId = m_currentArgs.filterId(); - - m_currentArgs = ToolTransformArgs(); - m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); - m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); - - if (mode == ToolTransformArgs::FREE_TRANSFORM) { - m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); - } else if (mode == ToolTransformArgs::WARP) { - m_currentArgs.setMode(ToolTransformArgs::WARP); - m_optionsWidget->setDefaultWarpPoints(); - m_currentArgs.setEditingTransformPoints(false); - } else if (mode == ToolTransformArgs::CAGE) { - m_currentArgs.setMode(ToolTransformArgs::CAGE); - m_currentArgs.setEditingTransformPoints(true); - } else if (mode == ToolTransformArgs::LIQUIFY) { - m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); - const QRect srcRect = m_transaction.originalRect().toAlignedRect(); - if (!srcRect.isEmpty()) { - m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); - } - } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { - m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); - } -} - void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { - resetArgsForMode(mode); + m_currentArgs = KisTransformUtils::resetArgsForMode(mode, m_currentArgs.filterId(), m_transaction); initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); + setFunctionalCursor(); } -void KisToolTransform::updateSelectionPath() +void KisToolTransform::updateSelectionPath(const QPainterPath &selectionOutline) { - m_selectionPath = QPainterPath(); - - KisResourcesSnapshotSP resources = - new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); - - QPainterPath selectionOutline; - KisSelectionSP selection = resources->activeSelection(); - - if (selection && selection->outlineCacheValid()) { - selectionOutline = selection->outlineCache(); - } else if (m_selectedPortionCache) { - selectionOutline.addRect(m_selectedPortionCache->exactBounds()); - } + m_selectionPath = selectionOutline; const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (m_selectedPortionCache) { if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { - if (!m_strokeData.strokeId()) return; + if (!m_strokeId) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { if (m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { - Q_ASSERT(!m_strokeData.strokeId()); - - /** - * FIXME: The transform tool is not completely asynchronous, it - * needs the content of the layer for creation of the stroke - * strategy. It means that we cannot start a new stroke until the - * previous one is finished. Ideally, we should create the - * m_selectedPortionCache and m_selectionPath somewhere in the - * stroke and pass it to the tool somehow. But currently, we will - * just disable starting a new stroke asynchronously - */ - if (!blockUntilOperationsFinished()) { - return; - } + Q_ASSERT(!m_strokeId); // set up and null checks before we do anything KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) return; + m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); + m_currentArgs = ToolTransformArgs(); // some layer types cannot be transformed. Give a message and return if a user tries it if (currentNode->inherits("KisColorizeMask") || currentNode->inherits("KisFileLayer") || currentNode->inherits("KisCloneLayer")) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Layer type cannot use the transform tool"), koIcon("object-locked"), 4000, KisFloatingMessage::High); - // force-reset transform mode to default - initTransformMode(mode); - return; } - // this goes after the floating message check since the fetchNodesList() - // says a file layer is "empty" and messes up the floating message check above - QList nodesList = fetchNodesList(mode, currentNode, m_workRecursively); - if (nodesList.isEmpty()) return; - - - /** - * We must ensure that the currently selected subtree - * has finished all its updates. - */ - forceRepaintDelayedLayers(currentNode); - - ToolTransformArgs fetchedArgs; - bool fetchedFromCommand = false; - - if (!forceReset) { - fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); - } - if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } KisSelectionSP selection = resources->activeSelection(); /** * When working with transform mask, selections are not * taken into account. */ if (selection && dynamic_cast(currentNode.data())) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); selection = 0; } - TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, nodesList, selection, image().data()); - connect(strategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP))); + TransformStrokeStrategy *strategy = new TransformStrokeStrategy(mode, m_workRecursively, m_currentArgs.filterId(), forceReset, currentNode, selection, image().data(), image().data()); + connect(strategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP, QPainterPath)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP, QPainterPath))); + connect(strategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs))); + m_strokeId = image()->startStroke(strategy); - QRect srcRect; + KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty()); - if (selection) { - srcRect = selection->selectedExactRect(); - } else { - srcRect = QRect(); - Q_FOREACH (KisNodeSP node, nodesList) { - // group layers may have a projection of layers - // that are locked and will not be transformed - if (node->inherits("KisGroupLayer")) continue; - - if (const KisTransformMask *mask = dynamic_cast(node.data())) { - srcRect |= mask->sourceDataBounds(); - } else { - srcRect |= node->exactBounds(); - } - } - } + slotPreviewDeviceGenerated(0, QPainterPath()); +} - m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList); +void KisToolTransform::endStroke() +{ + if (!m_strokeId) return; - if (!forceReset && fetchedFromCommand) { - m_currentArgs = fetchedArgs; - } else if (forceReset || !tryInitArgsFromNode(currentNode)) { - resetArgsForMode(mode); + if (!m_currentArgs.isIdentity()) { + image()->addJob(m_strokeId, + new TransformStrokeStrategy::TransformAllData(m_currentArgs)); + image()->endStroke(m_strokeId); + } else { + image()->cancelStroke(m_strokeId); } - m_strokeData = StrokeData(image()->startStroke(strategy)); - image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::PreparePreviewData()); + m_strokeId.clear(); + m_changesTracker.reset(); + m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); + outlineChanged(); +} - bool haveInvisibleNodes = clearDevices(nodesList); - if (haveInvisibleNodes) { +void KisToolTransform::slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args) +{ + if (transaction.transformedNodes().isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", - "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), - QIcon(), 4000, KisFloatingMessage::Low); + "Cannot transform empty layer "), + QIcon(), 1000, KisFloatingMessage::Medium); + + cancelStroke(); + return; } + m_transaction = transaction; + m_currentArgs = args; + m_transaction.setCurrentConfigLocation(&m_currentArgs); - Q_ASSERT(m_changesTracker.isEmpty()); + KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty()); commitChanges(); - slotPreviewDeviceGenerated(0); -} - -void KisToolTransform::endStroke() -{ - if (!m_strokeData.strokeId()) return; - - if (!m_currentArgs.isIdentity()) { - transformClearedDevices(); - - image()->addJob(m_strokeData.strokeId(), - new TransformStrokeStrategy::TransformData( - TransformStrokeStrategy::TransformData::SELECTION, - m_currentArgs, - m_transaction.rootNode())); // root node is used for progress only + initGuiAfterTransformMode(); - image()->endStroke(m_strokeData.strokeId()); - } else { - image()->cancelStroke(m_strokeData.strokeId()); + if (m_transaction.hasInvisibleNodes()) { + KisCanvas2 *kisCanvas = dynamic_cast(canvas()); + kisCanvas->viewManager()-> + showFloatingMessage( + i18nc("floating message in transformation tool", + "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), + QIcon(), 4000, KisFloatingMessage::Low); } - - m_strokeData.clear(); - m_changesTracker.reset(); - m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); - outlineChanged(); } -void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device) +void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device, const QPainterPath &selectionOutline) { if (device && device->exactBounds().isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); cancelStroke(); } else { initThumbnailImage(device); - updateSelectionPath(); + updateSelectionPath(selectionOutline); initGuiAfterTransformMode(); } } void KisToolTransform::cancelStroke() { - if (!m_strokeData.strokeId()) return; + if (!m_strokeId) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { - image()->cancelStroke(m_strokeData.strokeId()); - m_strokeData.clear(); + image()->cancelStroke(m_strokeId); + m_strokeId.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } } void KisToolTransform::commitChanges() { - if (!m_strokeData.strokeId()) return; + if (!m_strokeId) return; m_changesTracker.commitConfig(toQShared(m_currentArgs.clone())); } void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status) { const ToolTransformArgs *newArgs = dynamic_cast(status.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs); *m_transaction.currentConfig() = *newArgs; slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } -bool KisToolTransform::clearDevices(const QList &nodes) -{ - bool haveInvisibleNodes = false; - - Q_FOREACH (KisNodeSP node, nodes) { - haveInvisibleNodes |= !node->visible(false); - - image()->addJob(m_strokeData.strokeId(), - new TransformStrokeStrategy::ClearSelectionData(node)); - - /** - * It might happen that the editablity state of the node would - * change during the stroke, so we need to save the set of - * applicable nodes right in the beginning of the processing - */ - m_strokeData.addClearedNode(node); - } - - return haveInvisibleNodes; -} - -void KisToolTransform::transformClearedDevices() -{ - Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) { - KIS_ASSERT_RECOVER_RETURN(node); - - image()->addJob(m_strokeData.strokeId(), - new TransformStrokeStrategy::TransformData( - TransformStrokeStrategy::TransformData::PAINT_DEVICE, - m_currentArgs, - node)); - } -} - QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform())); connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { - m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); - cancelStroke(); - image()->waitForDone(); - forceRepaintDelayedLayers(root); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { - if (!m_strokeData.strokeId()) return; + if (!m_strokeId) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); - image()->waitForDone(); - forceRepaintDelayedLayers(root); startStroke(savedArgs.mode(), true); } - -void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root) -{ - KIS_SAFE_ASSERT_RECOVER_RETURN(root); - - KisLayerUtils::forceAllDelayedNodesUpdate(root); - image()->waitForDone(); -} - void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } - diff --git a/plugins/tools/tool_transform2/kis_tool_transform.h b/plugins/tools/tool_transform2/kis_tool_transform.h index e7d8b8ffb9..52c9ccf65e 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.h +++ b/plugins/tools/tool_transform2/kis_tool_transform.h @@ -1,381 +1,353 @@ /* * kis_tool_transform.h - part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_TOOL_TRANSFORM_H_ #define KIS_TOOL_TRANSFORM_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tool_transform_args.h" #include "KisToolChangesTracker.h" #include "kis_tool_transform_config_widget.h" #include "transform_transaction_properties.h" class QTouchEvent; class KisTransformStrategyBase; class KisWarpTransformStrategy; class KisCageTransformStrategy; class KisLiquifyTransformStrategy; class KisFreeTransformStrategy; class KisPerspectiveTransformStrategy; /** * Transform tool * This tool offers several modes. * - Free Transform mode allows the user to translate, scale, shear, rotate and * apply a perspective transformation to a selection or the whole canvas. * - Warp mode allows the user to warp the selection of the canvas by grabbing * and moving control points placed on the image. The user can either work * with default control points, like a grid whose density can be modified, or * place the control points manually. The modifications made on the selected * pixels are applied only when the user clicks the Apply button : the * semi-transparent image displayed until the user click that button is only a * preview. * - Cage transform is similar to warp transform with control points exactly * placed on the outer boundary. The user draws a boundary polygon, the * vertices of which become control points. * - Perspective transform applies a two-point perspective transformation. The * user can manipulate the corners of the selection. If the vanishing points * of the resulting quadrilateral are on screen, the user can manipulate those * as well. * - Liquify transform transforms the selection by painting motions, as if the * user were fingerpainting. */ class KisToolTransform : public KisTool { Q_OBJECT Q_PROPERTY(TransformToolMode transformMode READ transformMode WRITE setTransformMode NOTIFY transformModeChanged) Q_PROPERTY(double translateX READ translateX WRITE setTranslateX NOTIFY freeTransformChanged) Q_PROPERTY(double translateY READ translateY WRITE setTranslateY NOTIFY freeTransformChanged) Q_PROPERTY(double rotateX READ rotateX WRITE setRotateX NOTIFY freeTransformChanged) Q_PROPERTY(double rotateY READ rotateY WRITE setRotateY NOTIFY freeTransformChanged) Q_PROPERTY(double rotateZ READ rotateZ WRITE setRotateZ NOTIFY freeTransformChanged) Q_PROPERTY(double scaleX READ scaleX WRITE setScaleX NOTIFY freeTransformChanged) Q_PROPERTY(double scaleY READ scaleY WRITE setScaleY NOTIFY freeTransformChanged) Q_PROPERTY(double shearX READ shearX WRITE setShearX NOTIFY freeTransformChanged) Q_PROPERTY(double shearY READ shearY WRITE setShearY NOTIFY freeTransformChanged) Q_PROPERTY(WarpType warpType READ warpType WRITE setWarpType NOTIFY warpTransformChanged) Q_PROPERTY(double warpFlexibility READ warpFlexibility WRITE setWarpFlexibility NOTIFY warpTransformChanged) Q_PROPERTY(int warpPointDensity READ warpPointDensity WRITE setWarpPointDensity NOTIFY warpTransformChanged) public: enum TransformToolMode { FreeTransformMode, WarpTransformMode, CageTransformMode, LiquifyTransformMode, PerspectiveTransformMode }; Q_ENUMS(TransformToolMode) enum WarpType { RigidWarpType, AffineWarpType, SimilitudeWarpType }; Q_ENUMS(WarpType) KisToolTransform(KoCanvasBase * canvas); ~KisToolTransform() override; /** * @brief wantsAutoScroll * reimplemented from KoToolBase * there's an issue where autoscrolling with this tool never makes the * stroke end, so we return false here so that users don't get stuck with * the tool. See bug 362659 * @return false */ bool wantsAutoScroll() const override { return false; } QWidget* createOptionWidget() override; void mousePressEvent(KoPointerEvent *e) override; void mouseMoveEvent(KoPointerEvent *e) override; void mouseReleaseEvent(KoPointerEvent *e) override; void beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); void continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); void endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); QMenu* popupActionsMenu() override; - void activatePrimaryAction() override; void deactivatePrimaryAction() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void activateAlternateAction(AlternateAction action) override; void deactivateAlternateAction(AlternateAction action) override; void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override; void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override; void endAlternateAction(KoPointerEvent *event, AlternateAction action) override; void paint(QPainter& gc, const KoViewConverter &converter) override; TransformToolMode transformMode() const; double translateX() const; double translateY() const; double rotateX() const; double rotateY() const; double rotateZ() const; double scaleX() const; double scaleY() const; double shearX() const; double shearY() const; WarpType warpType() const; double warpFlexibility() const; int warpPointDensity() const; public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; // Applies the current transformation to the original paint device and commits it to the undo stack void applyTransform(); void setTransformMode( KisToolTransform::TransformToolMode newMode ); void setTranslateX(double translateX); void setTranslateY(double translateY); void setRotateX(double rotation); void setRotateY(double rotation); void setRotateZ(double rotation); void setScaleX(double scaleX); void setScaleY(double scaleY); void setShearX(double shearX); void setShearY(double shearY); void setWarpType(WarpType type); void setWarpFlexibility(double flexibility); void setWarpPointDensity(int density); protected Q_SLOTS: void resetCursorStyle() override; Q_SIGNALS: void transformModeChanged(); void freeTransformChanged(); void warpTransformChanged(); public Q_SLOTS: void requestUndoDuringStroke() override; void requestStrokeEnd() override; void requestStrokeCancellation() override; void canvasUpdateRequested(); void cursorOutlineUpdateRequested(const QPointF &imagePos); // Update the widget according to m_currentArgs void updateOptionWidget(); void resetRotationCenterButtonsRequested(); void imageTooBigRequested(bool value); private: QList fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive); QScopedPointer m_contextMenu; - bool clearDevices(const QList &nodes); - void transformClearedDevices(); - void startStroke(ToolTransformArgs::TransformMode mode, bool forceReset); void endStroke(); void cancelStroke(); private: void outlineChanged(); // Sets the cursor according to mouse position (doesn't take shearing into account well yet) void setFunctionalCursor(); // Sets m_function according to mouse position and modifier void setTransformFunction(QPointF mousePos, Qt::KeyboardModifiers modifiers); void commitChanges(); - - bool tryInitArgsFromNode(KisNodeSP node); - bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode); - - void resetArgsForMode(ToolTransformArgs::TransformMode mode); void initTransformMode(ToolTransformArgs::TransformMode mode); void initGuiAfterTransformMode(); void initThumbnailImage(KisPaintDeviceSP previewDevice); - void updateSelectionPath(); + void updateSelectionPath(const QPainterPath &selectionOutline); void updateApplyResetAvailability(); - void forceRepaintDelayedLayers(KisNodeSP root); - private: ToolTransformArgs m_currentArgs; bool m_actuallyMoveWhileSelected; // true <=> selection has been moved while clicked KisPaintDeviceSP m_selectedPortionCache; - - struct StrokeData { - StrokeData() {} - StrokeData(KisStrokeId strokeId) : m_strokeId(strokeId) {} - - void clear() { - m_strokeId.clear(); - m_clearedNodes.clear(); - } - - const KisStrokeId strokeId() const { return m_strokeId; } - void addClearedNode(KisNodeSP node) { m_clearedNodes.append(node); } - const QVector& clearedNodes() const { return m_clearedNodes; } - - private: - KisStrokeId m_strokeId; - QVector m_clearedNodes; - }; - StrokeData m_strokeData; + KisStrokeId m_strokeId; bool m_workRecursively; QPainterPath m_selectionPath; // original (unscaled) selection outline, used for painting decorations KisToolTransformConfigWidget *m_optionsWidget; QPointer m_canvas; TransformTransactionProperties m_transaction; KisToolChangesTracker m_changesTracker; /// actions for the context click menu KisAction* warpAction; KisAction* liquifyAction; KisAction* cageAction; KisAction* freeTransformAction; KisAction* perspectiveAction; KisAction* applyTransformation; KisAction* resetTransformation; // a few extra context click options if free transform is active KisAction* mirrorHorizontalAction; KisAction* mirrorVericalAction; KisAction* rotateNinteyCWAction; KisAction* rotateNinteyCCWAction; /** * This artificial rect is used to store the image to flake * transformation. We check against this rect to get to know * whether zoom has changed. */ QRectF m_refRect; QScopedPointer m_warpStrategy; QScopedPointer m_cageStrategy; QScopedPointer m_liquifyStrategy; QScopedPointer m_freeStrategy; QScopedPointer m_perspectiveStrategy; KisTransformStrategyBase* currentStrategy() const; QPainterPath m_cursorOutline; private Q_SLOTS: void slotTrackerChangedConfig(KisToolChangesTrackerDataSP status); void slotUiChangedConfig(); void slotApplyTransform(); void slotResetTransform(); void slotRestartTransform(); void slotEditingFinished(); - void slotPreviewDeviceGenerated(KisPaintDeviceSP device); + void slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args); + void slotPreviewDeviceGenerated(KisPaintDeviceSP device, const QPainterPath &selectionOutline); // context menu options for updating the transform type // this is to help with discoverability since come people can't find the tool options void slotUpdateToWarpType(); void slotUpdateToPerspectiveType(); void slotUpdateToFreeTransformType(); void slotUpdateToLiquifyType(); void slotUpdateToCageType(); }; class KisToolTransformFactory : public KoToolFactoryBase { public: KisToolTransformFactory() : KoToolFactoryBase("KisToolTransform") { setToolTip(i18n("Transform a layer or a selection")); setSection(TOOL_TYPE_TRANSFORM); setIconName(koIconNameCStr("krita_tool_transform")); setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); setPriority(2); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolTransformFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolTransform(canvas); } }; #endif // KIS_TOOL_TRANSFORM_H_ diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp index e824a5855c..941cc3a6f7 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp +++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp @@ -1,1330 +1,1299 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform_config_widget.h" #include #include "rotation_icons.h" #include "kis_canvas2.h" #include #include "kis_liquify_properties.h" #include "KisMainWindow.h" #include "KisViewManager.h" #include "kis_transform_utils.h" template inline T sign(T x) { return x > 0 ? 1 : x == (T)0 ? 0 : -1; } const int KisToolTransformConfigWidget::DEFAULT_POINTS_PER_LINE = 3; KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionProperties *transaction, KisCanvas2 *canvas, bool workRecursively, QWidget *parent) : QWidget(parent), m_transaction(transaction), m_notificationsBlocked(0), m_uiSlotsBlocked(0), m_configChanged(false) { setupUi(this); showDecorationsBox->setIcon(KisIconUtils::loadIcon("krita_tool_transform")); chkWorkRecursively->setIcon(KisIconUtils::loadIcon("krita_tool_transform_recursive")); flipXButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_x")); flipYButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_y")); rotateCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_cw")); rotateCCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_ccw")); chkWorkRecursively->setChecked(workRecursively); connect(chkWorkRecursively, SIGNAL(toggled(bool)), this, SIGNAL(sigRestartTransform())); // Granularity can only be specified in the power of 2's QStringList granularityValues{"4","8","16","32"}; changeGranularity->addItems(granularityValues); changeGranularity->setCurrentIndex(1); granularityPreview->addItems(granularityValues); granularityPreview->setCurrentIndex(2); connect(changeGranularity,SIGNAL(currentIndexChanged(QString)), this,SLOT(slotGranularityChanged(QString))); connect(granularityPreview, SIGNAL(currentIndexChanged(QString)), this,SLOT(slotPreviewGranularityChanged(QString))); // Init Filter combo cmbFilter->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); cmbFilter->setCurrent("Bicubic"); cmbFilter->setToolTip(i18nc("@info:tooltip", "

    Select filtering mode:\n" "

      " "
    • Bilinear for areas with uniform color to avoid artifacts
    • " "
    • Bicubic for smoother results
    • " "
    • Lanczos3 for sharp results. May produce aerials.
    • " "

    ")); connect(cmbFilter, SIGNAL(activated(KoID)), this, SLOT(slotFilterChanged(KoID))); // Init Warp Type combo cmbWarpType->insertItem(KisWarpTransformWorker::AFFINE_TRANSFORM,i18n("Default (Affine)")); cmbWarpType->insertItem(KisWarpTransformWorker::RIGID_TRANSFORM,i18n("Strong (Rigid)")); cmbWarpType->insertItem(KisWarpTransformWorker::SIMILITUDE_TRANSFORM,i18n("Strongest (Similitude)")); cmbWarpType->setCurrentIndex(KisWarpTransformWorker::AFFINE_TRANSFORM); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWarpTypeChanged(int))); // Init Rotation Center buttons m_handleDir[0] = QPointF(1, 0); m_handleDir[1] = QPointF(1, -1); m_handleDir[2] = QPointF(0, -1); m_handleDir[3] = QPointF(-1, -1); m_handleDir[4] = QPointF(-1, 0); m_handleDir[5] = QPointF(-1, 1); m_handleDir[6] = QPointF(0, 1); m_handleDir[7] = QPointF(1, 1); m_handleDir[8] = QPointF(0, 0); // also add the center m_rotationCenterButtons = new QButtonGroup(0); // we set the ids to match m_handleDir m_rotationCenterButtons->addButton(middleRightButton, 0); m_rotationCenterButtons->addButton(topRightButton, 1); m_rotationCenterButtons->addButton(middleTopButton, 2); m_rotationCenterButtons->addButton(topLeftButton, 3); m_rotationCenterButtons->addButton(middleLeftButton, 4); m_rotationCenterButtons->addButton(bottomLeftButton, 5); m_rotationCenterButtons->addButton(middleBottomButton, 6); m_rotationCenterButtons->addButton(bottomRightButton, 7); m_rotationCenterButtons->addButton(centerButton, 8); QToolButton *nothingSelected = new QToolButton(0); nothingSelected->setCheckable(true); nothingSelected->setAutoExclusive(true); nothingSelected->hide(); // a convenient button for when no button is checked in the group m_rotationCenterButtons->addButton(nothingSelected, 9); // initialize values for free transform sliders shearXBox->setSuffix(i18n(" px")); shearYBox->setSuffix(i18n(" px")); shearXBox->setRange(-5.0, 5.0, 2); shearYBox->setRange(-5.0, 5.0, 2); shearXBox->setSingleStep(0.01); shearYBox->setSingleStep(0.01); shearXBox->setValue(0.0); shearYBox->setValue(0.0); translateXBox->setSuffix(i18n(" px")); translateYBox->setSuffix(i18n(" px")); translateXBox->setRange(-10000, 10000); translateYBox->setRange(-10000, 10000); scaleXBox->setSuffix("%"); scaleYBox->setSuffix("%"); scaleXBox->setRange(-10000, 10000); scaleYBox->setRange(-10000, 10000); scaleXBox->setValue(100.0); scaleYBox->setValue(100.0); m_scaleRatio = 1.0; aXBox->setSuffix(QChar(Qt::Key_degree)); aYBox->setSuffix(QChar(Qt::Key_degree)); aZBox->setSuffix(QChar(Qt::Key_degree)); aXBox->setRange(0.0, 360.0, 2); aYBox->setRange(0.0, 360.0, 2); aZBox->setRange(0.0, 360.0, 2); aXBox->setValue(0.0); aYBox->setValue(0.0); aZBox->setValue(0.0); aXBox->setSingleStep(1.0); aYBox->setSingleStep(1.0); aZBox->setSingleStep(1.0); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(slotRotationCenterChanged(int))); connect(btnTransformAroundPivotPoint, SIGNAL(clicked(bool)), this, SLOT(slotTransformAroundRotationCenter(bool))); // Init Free Transform Values connect(scaleXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleX(int))); connect(scaleYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleY(int))); connect(shearXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearX(qreal))); connect(shearYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearY(qreal))); connect(translateXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateX(int))); connect(translateYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateY(int))); connect(aXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAX(qreal))); connect(aYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAY(qreal))); connect(aZBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAZ(qreal))); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotSetKeepAspectRatio(bool))); connect(flipXButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipX())); connect(flipYButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipY())); connect(rotateCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCW())); connect(rotateCCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCCW())); // toggle visibility of different free buttons connect(freeMoveRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeRotationRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeScaleRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeShearRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); // only first group for free transform rotationGroup->hide(); moveGroup->show(); scaleGroup->hide(); shearGroup->hide(); // Init Warp Transform Values alphaBox->setSingleStep(0.1); alphaBox->setRange(0, 10, 1); connect(alphaBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetWarpAlpha(qreal))); connect(densityBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetWarpDensity(int))); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpDefaultPointsButtonClicked(bool))); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpCustomPointsButtonClicked(bool))); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpLockPointsButtonClicked())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpResetPointsButtonClicked())); // Init Cage Transform Values cageTransformButtonGroup->setId(cageAddEditRadio, 0); // we need to set manually since Qt Designer generates negative by default cageTransformButtonGroup->setId(cageDeformRadio, 1); connect(cageTransformButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCageOptionsChanged(int))); // Init Liquify Transform Values liquifySizeSlider->setRange(KisLiquifyProperties::minSize(), KisLiquifyProperties::maxSize(), 2); liquifySizeSlider->setExponentRatio(4); liquifySizeSlider->setValue(60.0); connect(liquifySizeSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySizeChanged(qreal))); liquifySizeSlider->setToolTip(i18nc("@info:tooltip", "Size of the deformation brush")); liquifyAmountSlider->setRange(0.0, 1.0, 2); liquifyAmountSlider->setValue(0.05); connect(liquifyAmountSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyAmountChanged(qreal))); liquifyAmountSlider->setToolTip(i18nc("@info:tooltip", "Amount of the deformation you get")); liquifyFlowSlider->setRange(0.0, 1.0, 2); liquifyFlowSlider->setValue(1.0); connect(liquifyFlowSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyFlowChanged(qreal))); liquifyFlowSlider->setToolTip(i18nc("@info:tooltip", "When in non-buildup mode, shows how fast the deformation limit is reached.")); buidupModeComboBox->setCurrentIndex(0); // set to build-up mode by default connect(buidupModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(liquifyBuildUpChanged(int))); buidupModeComboBox->setToolTip(i18nc("@info:tooltip", "Switch between Build Up and Wash mode of painting. Build Up mode adds deformations one on top of the other without any limits. Wash mode gradually deforms the piece to the selected deformation level.")); liquifySpacingSlider->setRange(0.0, 3.0, 2); liquifySizeSlider->setExponentRatio(3); liquifySpacingSlider->setSingleStep(0.01); liquifySpacingSlider->setValue(0.2); connect(liquifySpacingSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySpacingChanged(qreal))); liquifySpacingSlider->setToolTip(i18nc("@info:tooltip", "Space between two sequential applications of the deformation")); liquifySizePressureBox->setChecked(true); connect(liquifySizePressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifySizePressureChanged(bool))); liquifySizePressureBox->setToolTip(i18nc("@info:tooltip", "Scale Size value according to current stylus pressure")); liquifyAmountPressureBox->setChecked(true); connect(liquifyAmountPressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifyAmountPressureChanged(bool))); liquifyAmountPressureBox->setToolTip(i18nc("@info:tooltip", "Scale Amount value according to current stylus pressure")); liquifyReverseDirectionChk->setChecked(false); connect(liquifyReverseDirectionChk, SIGNAL(toggled(bool)), this, SLOT(liquifyReverseDirectionChanged(bool))); liquifyReverseDirectionChk->setToolTip(i18nc("@info:tooltip", "Reverse direction of the current deformation tool")); QSignalMapper *liquifyModeMapper = new QSignalMapper(this); connect(liquifyMove, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyScale, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyRotate, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyOffset, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyUndo, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); liquifyModeMapper->setMapping(liquifyMove, (int)KisLiquifyProperties::MOVE); liquifyModeMapper->setMapping(liquifyScale, (int)KisLiquifyProperties::SCALE); liquifyModeMapper->setMapping(liquifyRotate, (int)KisLiquifyProperties::ROTATE); liquifyModeMapper->setMapping(liquifyOffset, (int)KisLiquifyProperties::OFFSET); liquifyModeMapper->setMapping(liquifyUndo, (int)KisLiquifyProperties::UNDO); connect(liquifyModeMapper, SIGNAL(mapped(int)), SLOT(slotLiquifyModeChanged(int))); liquifyMove->setToolTip(i18nc("@info:tooltip", "Move: drag the image along the brush stroke")); liquifyScale->setToolTip(i18nc("@info:tooltip", "Scale: grow/shrink image under cursor")); liquifyRotate->setToolTip(i18nc("@info:tooltip", "Rotate: twirl image under cursor")); liquifyOffset->setToolTip(i18nc("@info:tooltip", "Offset: shift the image to the right of the stroke direction")); liquifyUndo->setToolTip(i18nc("@info:tooltip", "Undo: erase actions of other tools")); // Connect all edit boxes to the Editing Finished signal connect(densityBox, SIGNAL(editingFinished()), this, SLOT(notifyEditingFinished())); // Connect other widget (not having editingFinished signal) to // the same slot. From Qt 4.6 onwards the sequence of the signal // delivery is definite. connect(cmbFilter, SIGNAL(activated(KoID)), this, SLOT(notifyEditingFinished())); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(notifyEditingFinished())); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(notifyEditingFinished())); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(notifyEditingFinished())); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); // Liquify // // liquify brush options do not affect the image directly and are not // saved to undo, so we don't emit notifyEditingFinished() for them // Connect Apply/Reset buttons connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(slotButtonBoxClicked(QAbstractButton*))); // Mode switch buttons connect(freeTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetFreeTransformModeButtonClicked(bool))); connect(warpButton, SIGNAL(clicked(bool)), this, SLOT(slotSetWarpModeButtonClicked(bool))); connect(cageButton, SIGNAL(clicked(bool)), this, SLOT(slotSetCageModeButtonClicked(bool))); connect(perspectiveTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetPerspectiveModeButtonClicked(bool))); connect(liquifyButton, SIGNAL(clicked(bool)), this, SLOT(slotSetLiquifyModeButtonClicked(bool))); // Connect Decorations switcher connect(showDecorationsBox, SIGNAL(toggled(bool)), canvas, SLOT(updateCanvas())); tooBigLabelWidget->hide(); connect(canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); slotUpdateIcons(); } void KisToolTransformConfigWidget::slotUpdateIcons() { freeTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_main")); warpButton->setIcon(KisIconUtils::loadIcon("transform_icons_warp")); cageButton->setIcon(KisIconUtils::loadIcon("transform_icons_cage")); perspectiveTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_perspective")); liquifyButton->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_main")); liquifyMove->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_move")); liquifyScale->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_resize")); liquifyRotate->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_rotate")); liquifyOffset->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_offset")); liquifyUndo->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_erase")); middleRightButton->setIcon(KisIconUtils::loadIcon("arrow-right")); topRightButton->setIcon(KisIconUtils::loadIcon("arrow-topright")); middleTopButton->setIcon(KisIconUtils::loadIcon("arrow-up")); topLeftButton->setIcon(KisIconUtils::loadIcon("arrow-topleft")); middleLeftButton->setIcon(KisIconUtils::loadIcon("arrow-left")); bottomLeftButton->setIcon(KisIconUtils::loadIcon("arrow-downleft")); middleBottomButton->setIcon(KisIconUtils::loadIcon("arrow-down")); bottomRightButton->setIcon(KisIconUtils::loadIcon("arrow-downright")); btnTransformAroundPivotPoint->setIcon(KisIconUtils::loadIcon("pivot-point")); // pressure icons liquifySizePressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); liquifyAmountPressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } double KisToolTransformConfigWidget::radianToDegree(double rad) { double piX2 = 2 * M_PI; if (rad < 0 || rad >= piX2) { rad = fmod(rad, piX2); if (rad < 0) { rad += piX2; } } return (rad * 360. / piX2); } double KisToolTransformConfigWidget::degreeToRadian(double degree) { if (degree < 0. || degree >= 360.) { degree = fmod(degree, 360.); if (degree < 0) degree += 360.; } return (degree * M_PI / 180.); } void KisToolTransformConfigWidget::updateLiquifyControls() { blockUiSlots(); ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); const bool useWashMode = props->useWashMode(); liquifySizeSlider->setValue(props->size()); liquifyAmountSlider->setValue(props->amount()); liquifyFlowSlider->setValue(props->flow()); buidupModeComboBox->setCurrentIndex(useWashMode); liquifySpacingSlider->setValue(props->spacing()); liquifySizePressureBox->setChecked(props->sizeHasPressure()); liquifyAmountPressureBox->setChecked(props->amountHasPressure()); liquifyReverseDirectionChk->setChecked(props->reverseDirection()); KisLiquifyProperties::LiquifyMode mode = static_cast(props->mode()); bool canInverseDirection = mode != KisLiquifyProperties::UNDO; bool canUseWashMode = mode != KisLiquifyProperties::UNDO; liquifyReverseDirectionChk->setEnabled(canInverseDirection); liquifyFlowSlider->setEnabled(canUseWashMode && useWashMode); buidupModeComboBox->setEnabled(canUseWashMode); const qreal maxAmount = canUseWashMode ? 5.0 : 1.0; liquifyAmountSlider->setRange(0.0, maxAmount, 2); unblockUiSlots(); } void KisToolTransformConfigWidget::slotLiquifyModeChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); KisLiquifyProperties::LiquifyMode mode = static_cast(value); if (mode == props->mode()) return; props->setMode(mode); props->loadMode(); updateLiquifyControls(); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizeChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSize(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmount(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyFlowChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setFlow(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyBuildUpChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setUseWashMode(value); // 0 == build up mode / 1 == wash mode notifyConfigChanged(); // we need to enable/disable flow slider updateLiquifyControls(); } void KisToolTransformConfigWidget::liquifySpacingChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSpacing(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizePressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSizeHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountPressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmountHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyReverseDirectionChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setReverseDirection(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config) { blockUiSlots(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM || config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { quickTransformGroup->setEnabled(config.mode() == ToolTransformArgs::FREE_TRANSFORM); stackedWidget->setCurrentIndex(0); bool freeTransformIsActive = config.mode() == ToolTransformArgs::FREE_TRANSFORM; if (freeTransformIsActive) { freeTransformButton->setChecked(true); } else { perspectiveTransformButton->setChecked(true); } aXBox->setEnabled(freeTransformIsActive); aYBox->setEnabled(freeTransformIsActive); aZBox->setEnabled(freeTransformIsActive); freeRotationRadioButton->setEnabled(freeTransformIsActive); scaleXBox->setValue(config.scaleX() * 100.); scaleYBox->setValue(config.scaleY() * 100.); shearXBox->setValue(config.shearX()); shearYBox->setValue(config.shearY()); const QPointF anchorPoint = config.originalCenter() + config.rotationCenterOffset(); const KisTransformUtils::MatricesPack m(config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); translateXBox->setValue(anchorPointView.x()); translateYBox->setValue(anchorPointView.y()); aXBox->setValue(radianToDegree(config.aX())); aYBox->setValue(radianToDegree(config.aY())); aZBox->setValue(radianToDegree(config.aZ())); aspectButton->setKeepAspectRatio(config.keepAspectRatio()); cmbFilter->setCurrent(config.filterId()); QPointF pt = m_transaction->currentConfig()->rotationCenterOffset(); pt.rx() /= m_transaction->originalHalfWidth(); pt.ry() /= m_transaction->originalHalfHeight(); for (int i = 0; i < 9; i++) { if (qFuzzyCompare(m_handleDir[i].x(), pt.x()) && qFuzzyCompare(m_handleDir[i].y(), pt.y())) { m_rotationCenterButtons->button(i)->setChecked(true); break; } } btnTransformAroundPivotPoint->setChecked(config.transformAroundRotationCenter()); } else if (config.mode() == ToolTransformArgs::WARP) { stackedWidget->setCurrentIndex(1); warpButton->setChecked(true); if (config.defaultPoints()) { densityBox->setValue(std::sqrt(config.numPoints())); } cmbWarpType->setCurrentIndex((int)config.warpType()); defaultRadioButton->setChecked(config.defaultPoints()); customRadioButton->setChecked(!config.defaultPoints()); densityBox->setEnabled(config.defaultPoints()); customWarpWidget->setEnabled(!config.defaultPoints()); updateLockPointsButtonCaption(); } else if (config.mode() == ToolTransformArgs::CAGE) { // default UI options resetUIOptions(); // we need at least 3 point before we can start actively deforming if (config.origPoints().size() >= 3) { cageTransformDirections->setText(i18n("Switch between editing and deforming cage")); cageAddEditRadio->setVisible(true); cageDeformRadio->setVisible(true); // update to correct radio button if (config.isEditingTransformPoints()) cageAddEditRadio->setChecked(true); else cageDeformRadio->setChecked(true); changeGranularity->setCurrentIndex(log2(config.pixelPrecision()) - 2); granularityPreview->setCurrentIndex(log2(config.previewPixelPrecision()) - 2); } } else if (config.mode() == ToolTransformArgs::LIQUIFY) { stackedWidget->setCurrentIndex(3); liquifyButton->setChecked(true); const KisLiquifyProperties *props = config.liquifyProperties(); switch (props->mode()) { case KisLiquifyProperties::MOVE: liquifyMove->setChecked(true); break; case KisLiquifyProperties::SCALE: liquifyScale->setChecked(true); break; case KisLiquifyProperties::ROTATE: liquifyRotate->setChecked(true); break; case KisLiquifyProperties::OFFSET: liquifyOffset->setChecked(true); break; case KisLiquifyProperties::UNDO: liquifyUndo->setChecked(true); break; case KisLiquifyProperties::N_MODES: qFatal("Unsupported mode"); } updateLiquifyControls(); } unblockUiSlots(); } void KisToolTransformConfigWidget::updateLockPointsButtonCaption() { ToolTransformArgs *config = m_transaction->currentConfig(); if (config->isEditingTransformPoints()) { lockUnlockPointsButton->setText(i18n("Lock Points")); } else { lockUnlockPointsButton->setText(i18n("Unlock Points")); } } void KisToolTransformConfigWidget::setApplyResetDisabled(bool disabled) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); Q_ASSERT(applyButton); Q_ASSERT(resetButton); applyButton->setDisabled(disabled); resetButton->setDisabled(disabled); } void KisToolTransformConfigWidget::resetRotationCenterButtons() { int checkedId = m_rotationCenterButtons->checkedId(); if (checkedId >= 0 && checkedId <= 8) { // uncheck the current checked button m_rotationCenterButtons->button(9)->setChecked(true); } } bool KisToolTransformConfigWidget::workRecursively() const { return chkWorkRecursively->isChecked(); } void KisToolTransformConfigWidget::setTooBigLabelVisible(bool value) { tooBigLabelWidget->setVisible(value); } bool KisToolTransformConfigWidget::showDecorations() const { return showDecorationsBox->isChecked(); } void KisToolTransformConfigWidget::blockNotifications() { m_notificationsBlocked++; } void KisToolTransformConfigWidget::unblockNotifications() { m_notificationsBlocked--; } void KisToolTransformConfigWidget::notifyConfigChanged() { if (!m_notificationsBlocked) { emit sigConfigChanged(); } m_configChanged = true; } void KisToolTransformConfigWidget::blockUiSlots() { m_uiSlotsBlocked++; } void KisToolTransformConfigWidget::unblockUiSlots() { m_uiSlotsBlocked--; } void KisToolTransformConfigWidget::notifyEditingFinished() { if (m_uiSlotsBlocked || m_notificationsBlocked || !m_configChanged) return; emit sigEditingFinished(); m_configChanged = false; } void KisToolTransformConfigWidget::resetUIOptions() { // reset tool states since we are done (probably can encapsulate this later) ToolTransformArgs *config = m_transaction->currentConfig(); if (config->mode() == ToolTransformArgs::CAGE) { cageAddEditRadio->setVisible(false); cageAddEditRadio->setChecked(true); cageDeformRadio->setVisible(false); cageTransformDirections->setText(i18n("Create 3 points on the canvas to begin")); // ensure we are on the right options view stackedWidget->setCurrentIndex(2); } } void KisToolTransformConfigWidget::slotButtonBoxClicked(QAbstractButton *button) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); resetUIOptions(); if (button == applyButton) { emit sigApplyTransform(); } else if (button == resetButton) { emit sigResetTransform(); } } void KisToolTransformConfigWidget::slotSetFreeTransformModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(freeTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::FREE_TRANSFORM); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetWarpModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(warpButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::WARP); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetCageModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(cageButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::CAGE); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetLiquifyModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(liquifyButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::LIQUIFY); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetPerspectiveModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(perspectiveTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::PERSPECTIVE_4POINT); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotFilterChanged(const KoID &filterId) { ToolTransformArgs *config = m_transaction->currentConfig(); config->setFilterId(filterId.id()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotRotationCenterChanged(int index) { if (m_uiSlotsBlocked) return; if (index >= 0 && index <= 8) { ToolTransformArgs *config = m_transaction->currentConfig(); double i = m_handleDir[index].x(); double j = m_handleDir[index].y(); config->setRotationCenterOffset(QPointF(i * m_transaction->originalHalfWidth(), j * m_transaction->originalHalfHeight())); notifyConfigChanged(); updateConfig(*config); } } void KisToolTransformConfigWidget::slotTransformAroundRotationCenter(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setTransformAroundRotationCenter(value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(value / 100.); } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int( value/ m_scaleRatio ); scaleYBox->blockSignals(true); scaleYBox->setValue(calculatedValue); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(calculatedValue / 100.); } scaleYBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(value / 100.); } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int(m_scaleRatio * value); scaleXBox->blockSignals(true); scaleXBox->setValue(calculatedValue); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(calculatedValue / 100.); } scaleXBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setShearX((double)value); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setShearY((double)value); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetTranslateX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(value, anchorPointView.y()); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetTranslateY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(anchorPointView.x(), value); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetAX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAX(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAY(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAZ(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(degreeToRadian((double)value)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipX() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleX(config->scaleX() * -1); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipY() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setScaleY(config->scaleY() * -1); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCW() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(normalizeAngle(config->aZ() + M_PI_2)); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCCW() { ToolTransformArgs *config = m_transaction->currentConfig(); { KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); config->setAZ(normalizeAngle(config->aZ() - M_PI_2)); } notifyConfigChanged(); notifyEditingFinished(); } // change free transform setting we want to alter (all radio buttons call this) void KisToolTransformConfigWidget::slotTransformAreaVisible(bool value) { Q_UNUSED(value); //QCheckBox sender = (QCheckBox)(*)(QObject::sender()); QString senderName = QObject::sender()->objectName(); // only show setting with what we have selected rotationGroup->hide(); shearGroup->hide(); scaleGroup->hide(); moveGroup->hide(); if ("freeMoveRadioButton" == senderName) { moveGroup->show(); } else if ("freeShearRadioButton" == senderName) { shearGroup->show(); } else if ("freeScaleRadioButton" == senderName) { scaleGroup->show(); } else { rotationGroup->show(); } } void KisToolTransformConfigWidget::slotSetKeepAspectRatio(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setKeepAspectRatio(value); if (value) { blockNotifications(); int tmpXScaleBox = scaleXBox->value(); int tmpYScaleBox = scaleYBox->value(); m_scaleRatio = (tmpXScaleBox / (double) tmpYScaleBox); unblockNotifications(); } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetWarpAlpha(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAlpha((double)value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetWarpDensity(int value) { if (m_uiSlotsBlocked) return; setDefaultWarpPoints(value); } void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine) { - if (pointsPerLine < 0) { - pointsPerLine = DEFAULT_POINTS_PER_LINE; - } - - int nbPoints = pointsPerLine * pointsPerLine; - QVector origPoints(nbPoints); - QVector transfPoints(nbPoints); - qreal gridSpaceX, gridSpaceY; - - if (nbPoints == 1) { - //there is actually no grid - origPoints[0] = m_transaction->originalCenterGeometric(); - transfPoints[0] = m_transaction->originalCenterGeometric(); - } - else if (nbPoints > 1) { - gridSpaceX = m_transaction->originalRect().width() / (pointsPerLine - 1); - gridSpaceY = m_transaction->originalRect().height() / (pointsPerLine - 1); - double y = m_transaction->originalRect().top(); - for (int i = 0; i < pointsPerLine; ++i) { - double x = m_transaction->originalRect().left(); - for (int j = 0 ; j < pointsPerLine; ++j) { - origPoints[i * pointsPerLine + j] = QPointF(x, y); - transfPoints[i * pointsPerLine + j] = QPointF(x, y); - x += gridSpaceX; - } - y += gridSpaceY; - } - } - ToolTransformArgs *config = m_transaction->currentConfig(); - config->setDefaultPoints(nbPoints > 0); - config->setPoints(origPoints, transfPoints); - + KisTransformUtils::setDefaultWarpPoints(pointsPerLine, m_transaction, config); notifyConfigChanged(); } void KisToolTransformConfigWidget::activateCustomWarpPoints(bool enabled) { ToolTransformArgs *config = m_transaction->currentConfig(); densityBox->setEnabled(!enabled); customWarpWidget->setEnabled(enabled); if (!enabled) { config->setEditingTransformPoints(false); setDefaultWarpPoints(densityBox->value()); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::GRID); } else { config->setEditingTransformPoints(true); config->setWarpCalculation(KisWarpTransformWorker::WarpCalculation::DRAW); setDefaultWarpPoints(0); } updateLockPointsButtonCaption(); } void KisToolTransformConfigWidget::slotWarpDefaultPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(!value); } void KisToolTransformConfigWidget::slotWarpCustomPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(value); } void KisToolTransformConfigWidget::slotWarpResetPointsButtonClicked() { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(true); } void KisToolTransformConfigWidget::slotWarpLockPointsButtonClicked() { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setEditingTransformPoints(!config->isEditingTransformPoints()); if (config->isEditingTransformPoints()) { // reinit the transf points to their original value ToolTransformArgs *config = m_transaction->currentConfig(); int nbPoints = config->origPoints().size(); for (int i = 0; i < nbPoints; ++i) { config->transfPoint(i) = config->origPoint(i); } } updateLockPointsButtonCaption(); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotWarpTypeChanged(int index) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); switch (index) { case KisWarpTransformWorker::AFFINE_TRANSFORM: case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: case KisWarpTransformWorker::RIGID_TRANSFORM: config->setWarpType((KisWarpTransformWorker::WarpType)index); break; default: config->setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotCageOptionsChanged(int value) { if ( value == 0) { slotEditCagePoints(true); } else { slotEditCagePoints(false); } notifyEditingFinished(); } void KisToolTransformConfigWidget::slotEditCagePoints(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->refTransformedPoints() = config->origPoints(); config->setEditingTransformPoints(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotGranularityChanged(QString value) { if (m_uiSlotsBlocked) return; KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1); ToolTransformArgs *config = m_transaction->currentConfig(); config->setPixelPrecision(value.toInt()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotPreviewGranularityChanged(QString value) { if (m_uiSlotsBlocked) return; KIS_SAFE_ASSERT_RECOVER_RETURN(value.toInt() > 1); ToolTransformArgs *config = m_transaction->currentConfig(); config->setPreviewPixelPrecision(value.toInt()); notifyConfigChanged(); } diff --git a/plugins/tools/tool_transform2/kis_transform_utils.cpp b/plugins/tools/tool_transform2/kis_transform_utils.cpp index f860e9f3c1..1fbbd525be 100644 --- a/plugins/tools/tool_transform2/kis_transform_utils.cpp +++ b/plugins/tools/tool_transform2/kis_transform_utils.cpp @@ -1,397 +1,490 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_transform_utils.h" #include #include #include #include "tool_transform_args.h" #include "kis_paint_device.h" #include "kis_algebra_2d.h" +#include "transform_transaction_properties.h" + +struct TransformTransactionPropertiesRegistrar { + TransformTransactionPropertiesRegistrar() { + qRegisterMetaType("TransformTransactionProperties"); + } +}; +static TransformTransactionPropertiesRegistrar __registrar1; + +struct ToolTransformArgsRegistrar { + ToolTransformArgsRegistrar() { + qRegisterMetaType("ToolTransformArgs"); + } +}; +static ToolTransformArgsRegistrar __registrar2; + +struct QPainterPathRegistrar { + QPainterPathRegistrar() { + qRegisterMetaType("QPainterPath"); + } +}; +static QPainterPathRegistrar __registrar3; const int KisTransformUtils::rotationHandleVisualRadius = 12; const int KisTransformUtils::rotationHandleRadius = 8; const int KisTransformUtils::handleVisualRadius = 12; const int KisTransformUtils::handleRadius = 8; QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter) { return converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); } qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) { return KoUnit::approxTransformScale(t); } qreal KisTransformUtils::scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(1.0, 0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(0, 1.0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::effectiveSize(const QRectF &rc) { return 0.5 * (rc.width() + rc.height()); } bool KisTransformUtils::thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect) { return KisAlgebra2D::minDimension(resultThumbTransform.mapRect(originalImageRect)) < 32; } QRectF handleRectImpl(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint, qreal *dOutX, qreal *dOutY) { const qreal handlesExtraScaleX = KisTransformUtils::scaleFromPerspectiveMatrixX(t, basePoint); const qreal handlesExtraScaleY = KisTransformUtils::scaleFromPerspectiveMatrixY(t, basePoint); const qreal maxD = 0.2 * KisTransformUtils::effectiveSize(limitingRect); const qreal dX = qMin(maxD, radius / handlesExtraScaleX); const qreal dY = qMin(maxD, radius / handlesExtraScaleY); QRectF handleRect(-0.5 * dX, -0.5 * dY, dX, dY); if (dOutX) { *dOutX = dX; } if (dOutY) { *dOutY = dY; } return handleRect; } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY) { return handleRectImpl(radius, t, limitingRect, limitingRect.center(), dOutX, dOutY); } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint) { return handleRectImpl(radius, t, limitingRect, basePoint, 0, 0); } QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r) { QPointF center = r.center(); QPointF t = p - center; r.translate(- center); if (t.y() != 0) { if (t.x() != 0) { double slope = t.y() / t.x(); if (t.x() < r.left()) { t.setY(r.left() * slope); t.setX(r.left()); } else if (t.x() > r.right()) { t.setY(r.right() * slope); t.setX(r.right()); } if (t.y() < r.top()) { t.setX(r.top() / slope); t.setY(r.top()); } else if (t.y() > r.bottom()) { t.setX(r.bottom() / slope); t.setY(r.bottom()); } } else { if (t.y() < r.top()) t.setY(r.top()); else if (t.y() > r.bottom()) t.setY(r.bottom()); } } else { if (t.x() < r.left()) t.setX(r.left()); else if (t.x() > r.right()) t.setX(r.right()); } t += center; return t; } KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args) { TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y()); SC = QTransform::fromScale(args.scaleX(), args.scaleY()); S.shear(0, args.shearY()); S.shear(args.shearX(), 0); if (args.mode() == ToolTransformArgs::FREE_TRANSFORM) { P.rotate(180. * args.aX() / M_PI, QVector3D(1, 0, 0)); P.rotate(180. * args.aY() / M_PI, QVector3D(0, 1, 0)); P.rotate(180. * args.aZ() / M_PI, QVector3D(0, 0, 1)); projectedP = P.toTransform(args.cameraPos().z()); } else if (args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { projectedP = args.flattenedPerspectiveTransform(); P = QMatrix4x4(projectedP); } QPointF translation = args.transformedCenter(); T = QTransform::fromTranslate(translation.x(), translation.y()); } QTransform KisTransformUtils::MatricesPack::finalTransform() const { return TS * SC * S * projectedP * T; } bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, const MatricesPack &m) { bool imageTooBig = false; QMatrix4x4 unprojectedMatrix = QMatrix4x4(m.T) * m.P * QMatrix4x4(m.TS * m.SC * m.S); QVector points; points << bounds.topLeft(); points << bounds.topRight(); points << bounds.bottomRight(); points << bounds.bottomLeft(); Q_FOREACH (const QPointF &pt, points) { QVector4D v(pt.x(), pt.y(), 0, 1); v = unprojectedMatrix * v; qreal z = v.z() / v.w(); imageTooBig = z > 1024.0; if (imageTooBig) { break; } } return imageTooBig; } #include #include #include #include #include KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs &config, KisPaintDeviceSP device, KoUpdaterPtr updater, QVector3D *transformedCenter /* OUT */) { { KisTransformWorker t(0, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), 0, // set X and Y translation 0, // to null for calculation 0, config.filter()); *transformedCenter = QVector3D(t.transform().map(config.originalCenter())); } QPointF translation = config.transformedCenter() - (*transformedCenter).toPointF(); KisTransformWorker transformWorker(device, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), (int)(translation.x()), (int)(translation.y()), updater, config.filter()); return transformWorker; } void KisTransformUtils::transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper) { if (config.mode() == ToolTransformArgs::WARP) { KoUpdaterPtr updater = helper->updater(); KisWarpTransformWorker worker(config.warpType(), device, config.origPoints(), config.transfPoints(), config.alpha(), updater); worker.run(); } else if (config.mode() == ToolTransformArgs::CAGE) { KoUpdaterPtr updater = helper->updater(); KisCageTransformWorker worker(device, config.origPoints(), updater, config.pixelPrecision()); worker.prepareTransform(); worker.setTransformedCage(config.transfPoints()); worker.run(); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { KoUpdaterPtr updater = helper->updater(); //FIXME: Q_UNUSED(updater); config.liquifyWorker()->run(device); } else { QVector3D transformedCenter; KoUpdaterPtr updater1 = helper->updater(); KoUpdaterPtr updater2 = helper->updater(); KisTransformWorker transformWorker = createTransformWorker(config, device, updater1, &transformedCenter); transformWorker.run(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) { KisPerspectiveTransformWorker perspectiveWorker(device, config.transformedCenter(), config.aX(), config.aY(), config.cameraPos().z(), updater2); perspectiveWorker.run(); } else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { QTransform T = QTransform::fromTranslate(config.transformedCenter().x(), config.transformedCenter().y()); KisPerspectiveTransformWorker perspectiveWorker(device, T.inverted() * config.flattenedPerspectiveTransform() * T, updater2); perspectiveWorker.run(); } } } QRect KisTransformUtils::needRect(const ToolTransformArgs &config, const QRect &rc, const QRect &srcBounds) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, config.pixelPrecision()); worker.setTransformedCage(config.transfPoints()); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxNeedRect(rc, srcBounds) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } QRect KisTransformUtils::changeRect(const ToolTransformArgs &config, const QRect &rc) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, config.pixelPrecision()); worker.setTransformedCage(config.transfPoints()); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxChangeRect(rc) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } KisTransformUtils::AnchorHolder::AnchorHolder(bool enabled, ToolTransformArgs *config) : m_enabled(enabled), m_config(config) { if (!m_enabled) return; m_staticPoint = m_config->originalCenter() + m_config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*m_config); m_oldStaticPointInView = m.finalTransform().map(m_staticPoint); } KisTransformUtils::AnchorHolder::~AnchorHolder() { if (!m_enabled) return; const KisTransformUtils::MatricesPack m(*m_config); const QPointF newStaticPointInView = m.finalTransform().map(m_staticPoint); const QPointF diff = m_oldStaticPointInView - newStaticPointInView; m_config->setTransformedCenter(m_config->transformedCenter() + diff); } + +void KisTransformUtils::setDefaultWarpPoints(int pointsPerLine, + const TransformTransactionProperties *transaction, + ToolTransformArgs *config) +{ + static const int DEFAULT_POINTS_PER_LINE = 3; + + if (pointsPerLine < 0) { + pointsPerLine = DEFAULT_POINTS_PER_LINE; + } + + int nbPoints = pointsPerLine * pointsPerLine; + QVector origPoints(nbPoints); + QVector transfPoints(nbPoints); + qreal gridSpaceX, gridSpaceY; + + if (nbPoints == 1) { + //there is actually no grid + origPoints[0] = transaction->originalCenterGeometric(); + transfPoints[0] = transaction->originalCenterGeometric(); + } + else if (nbPoints > 1) { + gridSpaceX = transaction->originalRect().width() / (pointsPerLine - 1); + gridSpaceY = transaction->originalRect().height() / (pointsPerLine - 1); + double y = transaction->originalRect().top(); + for (int i = 0; i < pointsPerLine; ++i) { + double x = transaction->originalRect().left(); + for (int j = 0 ; j < pointsPerLine; ++j) { + origPoints[i * pointsPerLine + j] = QPointF(x, y); + transfPoints[i * pointsPerLine + j] = QPointF(x, y); + x += gridSpaceX; + } + y += gridSpaceY; + } + } + + config->setDefaultPoints(nbPoints > 0); + config->setPoints(origPoints, transfPoints); +} + +ToolTransformArgs KisTransformUtils::resetArgsForMode(ToolTransformArgs::TransformMode mode, + const QString &filterId, + const TransformTransactionProperties &transaction) +{ + ToolTransformArgs args; + + args.setOriginalCenter(transaction.originalCenterGeometric()); + args.setTransformedCenter(transaction.originalCenterGeometric()); + args.setFilterId(filterId); + + if (mode == ToolTransformArgs::FREE_TRANSFORM) { + args.setMode(ToolTransformArgs::FREE_TRANSFORM); + } else if (mode == ToolTransformArgs::WARP) { + args.setMode(ToolTransformArgs::WARP); + KisTransformUtils::setDefaultWarpPoints(-1, &transaction, &args); + args.setEditingTransformPoints(false); + } else if (mode == ToolTransformArgs::CAGE) { + args.setMode(ToolTransformArgs::CAGE); + args.setEditingTransformPoints(true); + } else if (mode == ToolTransformArgs::LIQUIFY) { + args.setMode(ToolTransformArgs::LIQUIFY); + const QRect srcRect = transaction.originalRect().toAlignedRect(); + if (!srcRect.isEmpty()) { + args.initLiquifyTransformMode(transaction.originalRect().toAlignedRect()); + } + } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { + args.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); + } + + return args; +} diff --git a/plugins/tools/tool_transform2/kis_transform_utils.h b/plugins/tools/tool_transform2/kis_transform_utils.h index 1299e1cf66..652243f190 100644 --- a/plugins/tools/tool_transform2/kis_transform_utils.h +++ b/plugins/tools/tool_transform2/kis_transform_utils.h @@ -1,157 +1,169 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TRANSFORM_UTILS_H #define __KIS_TRANSFORM_UTILS_H #include #include "kis_coordinates_converter.h" #include #include #include #include // for kisSquareDistance only #include "kis_global.h" +#include "tool_transform_args.h" + class ToolTransformArgs; class KisTransformWorker; +class TransformTransactionProperties; class KisTransformUtils { public: static const int rotationHandleVisualRadius; static const int handleVisualRadius; static const int handleRadius; static const int rotationHandleRadius; template static T flakeToImage(const KisCoordinatesConverter *converter, T object) { return converter->documentToImage(converter->flakeToDocument(object)); } template static T imageToFlake(const KisCoordinatesConverter *converter, T object) { return converter->documentToFlake(converter->imageToDocument(object)); } static QTransform imageToFlakeTransform(const KisCoordinatesConverter *converter); static qreal effectiveHandleGrabRadius(const KisCoordinatesConverter *converter); static qreal effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter); static qreal scaleFromAffineMatrix(const QTransform &t); static qreal scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt); static qreal scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt); static qreal effectiveSize(const QRectF &rc); static bool thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect); static QRectF handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY); static QRectF handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint); static QPointF clipInRect(QPointF p, QRectF r); struct MatricesPack { MatricesPack(const ToolTransformArgs &args); QTransform TS; QTransform SC; QTransform S; QMatrix4x4 P; QTransform projectedP; QTransform T; // the final transformation looks like // transform = TS * SC * S * projectedP * T QTransform finalTransform() const; }; static bool checkImageTooBig(const QRectF &bounds, const MatricesPack &m); static KisTransformWorker createTransformWorker(const ToolTransformArgs &config, KisPaintDeviceSP device, KoUpdaterPtr updater, QVector3D *transformedCenter /* OUT */); static void transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper); static QRect needRect(const ToolTransformArgs &config, const QRect &rc, const QRect &srcBounds); static QRect changeRect(const ToolTransformArgs &config, const QRect &rc); template class HandleChooser { public: HandleChooser(const QPointF &cursorPos, Function defaultFunction) : m_cursorPos(cursorPos), m_minDistance(std::numeric_limits::max()), m_function(defaultFunction) { } bool addFunction(const QPointF &pt, qreal radius, Function function) { bool result = false; qreal distance = kisSquareDistance(pt, m_cursorPos); if (distance < pow2(radius) && distance < m_minDistance) { m_minDistance = distance; m_function = function; result = true; } return result; } Function function() const { return m_function; } private: QPointF m_cursorPos; qreal m_minDistance; Function m_function; }; /** * A special class that ensures that the view position of the anchor point of the * transformation is unchanged during the lifetime of the object. On destruction * of the keeper the position of the anchor point will be restored. */ struct AnchorHolder { AnchorHolder(bool enabled, ToolTransformArgs *config); ~AnchorHolder(); private: bool m_enabled; ToolTransformArgs *m_config; QPointF m_staticPoint; QPointF m_oldStaticPointInView; }; + + static void setDefaultWarpPoints(int pointsPerLine, + const TransformTransactionProperties *transaction, + ToolTransformArgs *config); + + static ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode, + const QString &filterId, + const TransformTransactionProperties &transaction); + }; #endif /* __KIS_TRANSFORM_UTILS_H */ diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp index ee44b28e2e..fde66f74a2 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp @@ -1,428 +1,636 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transform_stroke_strategy.h" #include #include "kundo2commandextradata.h" #include "kis_node_progress_proxy.h" #include #include #include #include #include #include #include #include #include "kis_transform_mask_adapter.h" #include "kis_transform_utils.h" #include "kis_abstract_projection_plane.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_projection_leaf.h" #include "kis_modify_transform_mask_command.h" #include "kis_sequential_iterator.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_layer_utils.h" #include #include - -TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode, - KisNodeList processedNodes, +#include "transform_transaction_properties.h" +#include "krita_container_utils.h" +#include "commands_new/kis_saved_commands.h" +#include "kis_command_ids.h" +#include "KisRunnableStrokeJobUtils.h" +#include "commands_new/KisHoldUIUpdatesCommand.h" + + +TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, + bool workRecursively, + const QString &filterId, + bool forceReset, + KisNodeSP rootNode, KisSelectionSP selection, - KisStrokeUndoFacade *undoFacade) + KisStrokeUndoFacade *undoFacade, + KisUpdatesFacade *updatesFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), + m_updatesFacade(updatesFacade), + m_mode(mode), + m_workRecursively(workRecursively), + m_filterId(filterId), + m_forceReset(forceReset), m_selection(selection) { KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast(rootNode.data())); - m_savedRootNode = rootNode; - m_savedProcessedNodes = processedNodes; + m_rootNode = rootNode; + setMacroId(KisCommandUtils::TransformToolId); } TransformStrokeStrategy::~TransformStrokeStrategy() { } KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev) { KisPaintDeviceSP cache; if (m_selection) { QRect srcRect = m_selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(m_selection); gc.bitBlt(srcRect.topLeft(), dev, srcRect); } else { cache = dev->createCompositionSourceDevice(dev); } return cache; } bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); return m_devicesCacheHash.contains(src.data()); } void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache) { QMutexLocker l(&m_devicesCacheMutex); m_devicesCacheHash.insert(src.data(), cache); } KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data()); if (!cache) { warnKrita << "WARNING: Transform Stroke: the device is absent in cache!"; } return cache; } bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const { return m_selection && (device == m_selection->pixelSelection().data() || device == m_selection->projection().data()); } void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { TransformData *td = dynamic_cast(data); ClearSelectionData *csd = dynamic_cast(data); PreparePreviewData *ppd = dynamic_cast(data); + TransformAllData *runAllData = dynamic_cast(data); + + + if (runAllData) { + m_savedTransformArgs = runAllData->config; - if (ppd) { - KisNodeSP rootNode = m_savedRootNode; - KisNodeList processedNodes = m_savedProcessedNodes; + QVector mutatedJobs; + Q_FOREACH (KisNodeSP node, m_processedNodes) { + mutatedJobs << new TransformData(TransformData::PAINT_DEVICE, + runAllData->config, + node); + } + mutatedJobs << new TransformData(TransformData::SELECTION, + runAllData->config, + m_rootNode); + addMutatedJobs(mutatedJobs); + + } else if (ppd) { + KisNodeSP rootNode = m_rootNode; + KisNodeList processedNodes = m_processedNodes; KisPaintDeviceSP previewDevice; if (rootNode->childCount() || !rootNode->paintDevice()) { if (KisTransformMask* tmask = dynamic_cast(rootNode.data())) { previewDevice = createDeviceCache(tmask->buildPreviewDevice()); KIS_SAFE_ASSERT_RECOVER(!m_selection) { m_selection = 0; } } else if (KisGroupLayer *group = dynamic_cast(rootNode.data())) { const QRect bounds = group->image()->bounds(); KisImageSP clonedImage = new KisImage(0, bounds.width(), bounds.height(), group->colorSpace(), "transformed_image"); KisGroupLayerSP clonedGroup = dynamic_cast(group->clone().data()); // In case the group is pass-through, it needs to be disabled for the preview, // otherwise it will crash (no parent for a preview leaf). // Also it needs to be done before setting the root layer for clonedImage. // Result: preview for pass-through group is the same as for standard group // (i.e. filter layers in the group won't affect the layer stack for a moment). clonedGroup->setPassThroughMode(false); clonedImage->setRootLayer(clonedGroup); QQueue linearizedSrcNodes; KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) { linearizedSrcNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) { KisNodeSP srcNode = linearizedSrcNodes.dequeue(); if (!processedNodes.contains(srcNode)) { node->setVisible(false); } }); clonedImage->refreshGraph(); KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup); clonedImage->waitForDone(); previewDevice = createDeviceCache(clonedImage->projection()); previewDevice->setDefaultBounds(group->projection()->defaultBounds()); // we delete the cloned image in GUI thread to ensure // no signals are still pending makeKisDeleteLaterWrapper(clonedImage)->deleteLater(); } else { rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); previewDevice = createDeviceCache(rootNode->projection()); } } else { KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice()); if (dynamic_cast(rootNode.data())) { KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID && cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) { cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); } previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect srcRect = cacheDevice->exactBounds(); KisSequentialConstIterator srcIt(cacheDevice, srcRect); KisSequentialIterator dstIt(previewDevice, srcRect); const int pixelSize = previewDevice->colorSpace()->pixelSize(); KisImageConfig cfg(true); KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace()); const qreal coeff = 1.0 / 255.0; const qreal baseOpacity = 0.5; while (srcIt.nextPixel() && dstIt.nextPixel()) { qreal gray = srcIt.rawDataConst()[0]; qreal alpha = srcIt.rawDataConst()[1]; pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff)); memcpy(dstIt.rawData(), pixel.data(), pixelSize); } } else { previewDevice = cacheDevice; } putDeviceCache(rootNode->paintDevice(), cacheDevice); } + QPainterPath selectionOutline; + if (m_selection && m_selection->outlineCacheValid()) { + selectionOutline = m_selection->outlineCache(); + } else if (previewDevice) { + selectionOutline.addRect(previewDevice->exactBounds()); + } - emit sigPreviewDeviceReady(previewDevice); + emit sigPreviewDeviceReady(previewDevice, selectionOutline); } else if(td) { - m_savedTransformArgs = td->config; - if (td->destination == TransformData::PAINT_DEVICE) { QRect oldExtent = td->node->extent(); KisPaintDeviceSP device = td->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { KisPaintDeviceSP cachedPortion = getDeviceCache(device); Q_ASSERT(cachedPortion); KisTransaction transaction(device); KisProcessingVisitor::ProgressHelper helper(td->node); transformAndMergeDevice(td->config, cachedPortion, device, &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); td->node->setDirty(oldExtent | td->node->extent()); } else if (KisExternalLayer *extLayer = dynamic_cast(td->node.data())) { if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM || (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && extLayer->supportsPerspectiveTransform())) { QVector3D transformedCenter; KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter); QTransform t = w.transform(); runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (KisTransformMask *transformMask = dynamic_cast(td->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisTransformMaskAdapter(td->config)))), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (m_selection) { /** * We use usual transaction here, because we cannot calsulate * transformation for perspective and warp workers. */ KisTransaction transaction(m_selection->pixelSelection()); KisProcessingVisitor::ProgressHelper helper(td->node); KisTransformUtils::transformDevice(td->config, m_selection->pixelSelection(), &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (csd) { KisPaintDeviceSP device = csd->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { if (!haveDeviceInCache(device)) { putDeviceCache(device, createDeviceCache(device)); } clearSelection(device); /** * Selection masks might have an overlay enabled, we should disable that */ if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) { KisSelectionSP selection = mask->selection(); if (selection) { selection->setVisible(false); m_deactivatedSelections.append(selection); mask->setDirty(); } } } else if (KisExternalLayer *externalLayer = dynamic_cast(csd->node.data())) { externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true); externalLayer->setDirty(); m_hiddenProjectionLeaves.append(csd->node); } else if (KisTransformMask *transformMask = dynamic_cast(csd->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(true)))), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device) { KisTransaction transaction(device); if (m_selection) { device->clearSelection(m_selection); } else { QRect oldExtent = device->extent(); device->clear(); device->setDirty(oldExtent); } runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper) { KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0; KisTransformUtils::transformDevice(config, src, helper); if (src != dst) { QRect mergeRect = src->extent(); KisPainter painter(dst); painter.setProgress(mergeUpdater); painter.bitBlt(mergeRect.topLeft(), src, mergeRect); painter.end(); } } struct TransformExtraData : public KUndo2CommandExtraData { ToolTransformArgs savedTransformArgs; KisNodeSP rootNode; KisNodeList transformedNodes; + + KUndo2CommandExtraData* clone() const override { + return new TransformExtraData(*this); + } }; void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) { TransformExtraData *data = new TransformExtraData(); data->savedTransformArgs = m_savedTransformArgs; - data->rootNode = m_savedRootNode; - data->transformedNodes = m_savedProcessedNodes; + data->rootNode = m_rootNode; + data->transformedNodes = m_processedNodes; command->setExtraData(data); + + KisSavedMacroCommand *macroCommand = dynamic_cast(command); + KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand); + + if (m_overriddenCommand && macroCommand) { + macroCommand->setOverrideInfo(m_overriddenCommand, m_skippedWhileMergeCommands); + } + + KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command); } bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes) { const TransformExtraData *data = dynamic_cast(command->extraData()); if (data) { *args = data->savedTransformArgs; *rootNode = data->rootNode; *transformedNodes = data->transformedNodes; } return bool(data); } +QList TransformStrokeStrategy::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) +{ + QList result; + + auto fetchFunc = + [&result, mode, root] (KisNodeSP node) { + if (node->isEditable(node == root) && + (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && + !node->inherits("KisFileLayer") && + (!node->inherits("KisTransformMask") || node == root)) { + + result << node; + } + }; + + if (recursive) { + KisLayerUtils::recursiveApplyNodes(root, fetchFunc); + } else { + fetchFunc(root); + } + + return result; +} + +bool TransformStrokeStrategy::tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args) +{ + bool result = false; + + if (KisTransformMaskSP mask = + dynamic_cast(node.data())) { + + KisTransformMaskParamsInterfaceSP savedParams = + mask->transformParams(); + + KisTransformMaskAdapter *adapter = + dynamic_cast(savedParams.data()); + + if (adapter) { + *args = adapter->transformArgs(); + result = true; + } + } + + return result; +} + +bool TransformStrokeStrategy::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs, + ToolTransformArgs::TransformMode mode, + KisNodeSP currentNode, + KisNodeList selectedNodes, + QVector *undoJobs) +{ + bool result = false; + + const KUndo2Command *lastCommand = undoFacade()->lastExecutedCommand(); + KisNodeSP oldRootNode; + KisNodeList oldTransformedNodes; + + ToolTransformArgs args; + + if (lastCommand && + TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, &args, &oldRootNode, &oldTransformedNodes) && + args.mode() == mode && + oldRootNode == currentNode) { + + if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) { + args.saveContinuedState(); + + *outArgs = args; + + const KisSavedMacroCommand *command = dynamic_cast(lastCommand); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false); + + command->getCommandExecutionJobs(undoJobs, true); + + m_overriddenCommand = command; + Q_FOREACH (KisStrokeJobData *commonData, *undoJobs) { + Data *data = dynamic_cast(commonData); + KIS_SAFE_ASSERT_RECOVER(data) { continue; } + + m_skippedWhileMergeCommands << data->command.data(); + } + + + result = true; + } + } + + return result; +} + void TransformStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_selection) { m_selection->setVisible(false); m_deactivatedSelections.append(m_selection); } + + ToolTransformArgs initialTransformArgs; + m_processedNodes = fetchNodesList(m_mode, m_rootNode, m_workRecursively); + + bool argsAreInitialized = false; + QVector lastCommandUndoJobs; + + if (!m_forceReset && tryFetchArgsFromCommandAndUndo(&initialTransformArgs, + m_mode, + m_rootNode, + m_processedNodes, + &lastCommandUndoJobs)) { + argsAreInitialized = true; + } else if (!m_forceReset && tryInitArgsFromNode(m_rootNode, &initialTransformArgs)) { + argsAreInitialized = true; + } + + QVector extraInitJobs; + + extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER); + + extraInitJobs << lastCommandUndoJobs; + + KritaUtils::addJobSequential(extraInitJobs, [this]() { + /** + * We must ensure that the currently selected subtree + * has finished all its updates. + */ + KisLayerUtils::forceAllDelayedNodesUpdate(m_rootNode); + }); + + KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable { + QRect srcRect; + + if (m_selection) { + srcRect = m_selection->selectedExactRect(); + } else { + srcRect = QRect(); + Q_FOREACH (KisNodeSP node, m_processedNodes) { + // group layers may have a projection of layers + // that are locked and will not be transformed + if (node->inherits("KisGroupLayer")) continue; + + if (const KisTransformMask *mask = dynamic_cast(node.data())) { + srcRect |= mask->sourceDataBounds(); + } else { + srcRect |= node->exactBounds(); + } + } + } + + TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNode, m_processedNodes); + if (!argsAreInitialized) { + initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction); + } + + emit this->sigTransactionGenerated(transaction, initialTransformArgs); + }); + + extraInitJobs << new PreparePreviewData(); + + Q_FOREACH (KisNodeSP node, m_processedNodes) { + extraInitJobs << new ClearSelectionData(node); + } + + extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER); + + addMutatedJobs(extraInitJobs); } void TransformStrokeStrategy::finishStrokeCallback() { Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } void TransformStrokeStrategy::cancelStrokeCallback() { KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h index 75b86e167c..9b33fe0412 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h @@ -1,136 +1,171 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TRANSFORM_STROKE_STRATEGY_H #define __TRANSFORM_STROKE_STRATEGY_H #include #include #include #include #include #include "tool_transform_args.h" #include #include - class KisPostExecutionUndoAdapter; +class TransformTransactionProperties; +class KisUpdatesFacade; class TransformStrokeStrategy : public QObject, public KisStrokeStrategyUndoCommandBased { Q_OBJECT public: + struct TransformAllData : public KisStrokeJobData { + TransformAllData(const ToolTransformArgs &_config) + : KisStrokeJobData(SEQUENTIAL, NORMAL), + config(_config) {} + + ToolTransformArgs config; + }; + + class TransformData : public KisStrokeJobData { public: enum Destination { PAINT_DEVICE, SELECTION, }; public: TransformData(Destination _destination, const ToolTransformArgs &_config, KisNodeSP _node) : KisStrokeJobData(CONCURRENT, NORMAL), destination(_destination), config(_config), node(_node) { } Destination destination; ToolTransformArgs config; KisNodeSP node; }; class ClearSelectionData : public KisStrokeJobData { public: ClearSelectionData(KisNodeSP _node) : KisStrokeJobData(SEQUENTIAL, NORMAL), node(_node) { } KisNodeSP node; }; class PreparePreviewData : public KisStrokeJobData { public: PreparePreviewData() : KisStrokeJobData(BARRIER, NORMAL) { } }; public: - TransformStrokeStrategy(KisNodeSP rootNode, KisNodeList processedNodes, + TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, + bool workRecursively, + const QString &filterId, + bool forceReset, + KisNodeSP rootNode, KisSelectionSP selection, - KisStrokeUndoFacade *undoFacade); + KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade); ~TransformStrokeStrategy() override; void initStrokeCallback() override; void finishStrokeCallback() override; void cancelStrokeCallback() override; void doStrokeCallback(KisStrokeJobData *data) override; static bool fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes); Q_SIGNALS: - void sigPreviewDeviceReady(KisPaintDeviceSP device); + void sigTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args); + void sigPreviewDeviceReady(KisPaintDeviceSP device, const QPainterPath &selectionOutline); protected: void postProcessToplevelCommand(KUndo2Command *command) override; private: KoUpdaterPtr fetchUpdater(KisNodeSP node); void transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); void transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper); void clearSelection(KisPaintDeviceSP device); //void transformDevice(KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper); bool checkBelongsToSelection(KisPaintDeviceSP device) const; KisPaintDeviceSP createDeviceCache(KisPaintDeviceSP src); bool haveDeviceInCache(KisPaintDeviceSP src); void putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache); KisPaintDeviceSP getDeviceCache(KisPaintDeviceSP src); + QList fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive); + ToolTransformArgs resetArgsForMode(ToolTransformArgs::TransformMode mode, + const QString &filterId, + const TransformTransactionProperties &transaction); + bool tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args); + bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, + ToolTransformArgs::TransformMode mode, + KisNodeSP currentNode, + KisNodeList selectedNodes, QVector *undoJobs); + + private: + KisUpdatesFacade *m_updatesFacade; + ToolTransformArgs::TransformMode m_mode; + bool m_workRecursively; + QString m_filterId; + bool m_forceReset; + KisSelectionSP m_selection; QMutex m_devicesCacheMutex; QHash m_devicesCacheHash; KisTransformMaskSP writeToTransformMask; ToolTransformArgs m_savedTransformArgs; - KisNodeSP m_savedRootNode; - KisNodeList m_savedProcessedNodes; + KisNodeSP m_rootNode; + KisNodeList m_processedNodes; QList m_deactivatedSelections; QList m_hiddenProjectionLeaves; + + const KisSavedMacroCommand *m_overriddenCommand = 0; + QVector m_skippedWhileMergeCommands; }; #endif /* __TRANSFORM_STROKE_STRATEGY_H */ diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc index cb1aa82879..ce49669134 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.cc +++ b/plugins/tools/tool_transform2/tool_transform_args.cc @@ -1,510 +1,510 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "tool_transform_args.h" #include #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_dom_utils.h" ToolTransformArgs::ToolTransformArgs() : m_mode(FREE_TRANSFORM) , m_defaultPoints(true) , m_origPoints {QVector()} , m_transfPoints {QVector()} , m_warpType(KisWarpTransformWorker::RIGID_TRANSFORM) , m_alpha(1.0) , m_transformedCenter(QPointF(0, 0)) , m_originalCenter(QPointF(0, 0)) , m_rotationCenterOffset(QPointF(0, 0)) , m_aX(0) , m_aY(0) , m_aZ(0) , m_scaleX(1.0) , m_scaleY(1.0) , m_shearX(0.0) , m_shearY(0.0) , m_liquifyProperties(new KisLiquifyProperties()) , m_pixelPrecision(8) , m_previewPixelPrecision(16) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); QString savedFilterId = configGroup.readEntry("filterId", "Bicubic"); setFilterId(savedFilterId); m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt(); } void ToolTransformArgs::setFilterId(const QString &id) { m_filter = KisFilterStrategyRegistry::instance()->value(id); if (m_filter) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("filterId", id); } } void ToolTransformArgs::setTransformAroundRotationCenter(bool value) { m_transformAroundRotationCenter = value; KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("transformAroundRotationCenter", int(value)); } void ToolTransformArgs::init(const ToolTransformArgs& args) { m_mode = args.mode(); m_transformedCenter = args.transformedCenter(); m_originalCenter = args.originalCenter(); m_rotationCenterOffset = args.rotationCenterOffset(); m_transformAroundRotationCenter = args.transformAroundRotationCenter(); m_cameraPos = args.m_cameraPos; m_aX = args.aX(); m_aY = args.aY(); m_aZ = args.aZ(); m_scaleX = args.scaleX(); m_scaleY = args.scaleY(); m_shearX = args.shearX(); m_shearY = args.shearY(); m_origPoints = args.origPoints(); //it's a copy m_transfPoints = args.transfPoints(); m_warpType = args.warpType(); m_alpha = args.alpha(); m_defaultPoints = args.defaultPoints(); m_keepAspectRatio = args.keepAspectRatio(); m_filter = args.m_filter; m_flattenedPerspectiveTransform = args.m_flattenedPerspectiveTransform; m_editTransformPoints = args.m_editTransformPoints; m_pixelPrecision = args.pixelPrecision(); m_previewPixelPrecision = args.previewPixelPrecision(); if (args.m_liquifyWorker) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(*args.m_liquifyWorker.data())); } m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0); } void ToolTransformArgs::clear() { m_origPoints.clear(); m_transfPoints.clear(); } ToolTransformArgs::ToolTransformArgs(const ToolTransformArgs& args) - : m_liquifyProperties(args.m_liquifyProperties) + : m_liquifyProperties(new KisLiquifyProperties(*args.m_liquifyProperties.data())) { init(args); } KisToolChangesTrackerData *ToolTransformArgs::clone() const { return new ToolTransformArgs(*this); } ToolTransformArgs& ToolTransformArgs::operator=(const ToolTransformArgs& args) { clear(); m_liquifyProperties.reset(new KisLiquifyProperties(*args.m_liquifyProperties.data())); init(args); return *this; } bool ToolTransformArgs::operator==(const ToolTransformArgs& other) const { return m_mode == other.m_mode && m_defaultPoints == other.m_defaultPoints && m_origPoints == other.m_origPoints && m_transfPoints == other.m_transfPoints && m_warpType == other.m_warpType && m_alpha == other.m_alpha && m_transformedCenter == other.m_transformedCenter && m_originalCenter == other.m_originalCenter && m_rotationCenterOffset == other.m_rotationCenterOffset && m_transformAroundRotationCenter == other.m_transformAroundRotationCenter && m_aX == other.m_aX && m_aY == other.m_aY && m_aZ == other.m_aZ && m_cameraPos == other.m_cameraPos && m_scaleX == other.m_scaleX && m_scaleY == other.m_scaleY && m_shearX == other.m_shearX && m_shearY == other.m_shearY && m_keepAspectRatio == other.m_keepAspectRatio && m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform && m_editTransformPoints == other.m_editTransformPoints && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties) && // pointer types ((m_filter && other.m_filter && m_filter->id() == other.m_filter->id()) || m_filter == other.m_filter) && ((m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker) && m_pixelPrecision == other.m_pixelPrecision && m_previewPixelPrecision == other.m_previewPixelPrecision; } bool ToolTransformArgs::isSameMode(const ToolTransformArgs& other) const { if (m_mode != other.m_mode) return false; bool result = true; if (m_mode == FREE_TRANSFORM) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_aX == other.m_aX; result &= m_aY == other.m_aY; result &= m_aZ == other.m_aZ; } else if (m_mode == PERSPECTIVE_4POINT) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform; } else if(m_mode == WARP || m_mode == CAGE) { result &= m_origPoints == other.m_origPoints; result &= m_transfPoints == other.m_transfPoints; } else if (m_mode == LIQUIFY) { result &= m_liquifyProperties && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties); result &= (m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker; } else { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } return result; } ToolTransformArgs::ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId, int pixelPrecision, int previewPixelPrecision) : m_mode(mode) , m_defaultPoints(defaultPoints) , m_origPoints {QVector()} , m_transfPoints {QVector()} , m_warpType(warpType) , m_alpha(alpha) , m_transformedCenter(transformedCenter) , m_originalCenter(originalCenter) , m_rotationCenterOffset(rotationCenterOffset) , m_transformAroundRotationCenter(transformAroundRotationCenter) , m_aX(aX) , m_aY(aY) , m_aZ(aZ) , m_scaleX(scaleX) , m_scaleY(scaleY) , m_shearX(shearX) , m_shearY(shearY) , m_liquifyProperties(new KisLiquifyProperties()) , m_pixelPrecision(pixelPrecision) , m_previewPixelPrecision(previewPixelPrecision) { setFilterId(filterId); } ToolTransformArgs::~ToolTransformArgs() { clear(); } void ToolTransformArgs::translate(const QPointF &offset) { if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { m_originalCenter += offset; m_rotationCenterOffset += offset; m_transformedCenter += offset; } else if(m_mode == WARP || m_mode == CAGE) { for (auto &pt : m_origPoints) { pt += offset; } for (auto &pt : m_transfPoints) { pt += offset; } } else if (m_mode == LIQUIFY) { KIS_ASSERT_RECOVER_RETURN(m_liquifyWorker); m_liquifyWorker->translate(offset); } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } } bool ToolTransformArgs::isIdentity() const { if (m_mode == FREE_TRANSFORM) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_aX == 0 && m_aY == 0 && m_aZ == 0); } else if (m_mode == PERSPECTIVE_4POINT) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_flattenedPerspectiveTransform.isIdentity()); } else if(m_mode == WARP || m_mode == CAGE) { for (int i = 0; i < m_origPoints.size(); ++i) if (m_origPoints[i] != m_transfPoints[i]) return false; return true; } else if (m_mode == LIQUIFY) { // Not implemented! return false; } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); return true; } } void ToolTransformArgs::initLiquifyTransformMode(const QRect &srcRect) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(srcRect, 0, 8)); m_liquifyProperties->loadAndResetMode(); } void ToolTransformArgs::saveLiquifyTransformMode() const { m_liquifyProperties->saveMode(); } void ToolTransformArgs::toXML(QDomElement *e) const { e->setAttribute("mode", (int) m_mode); QDomDocument doc = e->ownerDocument(); if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl = doc.createElement("free_transform"); e->appendChild(freeEl); KisDomUtils::saveValue(&freeEl, "transformedCenter", m_transformedCenter); KisDomUtils::saveValue(&freeEl, "originalCenter", m_originalCenter); KisDomUtils::saveValue(&freeEl, "rotationCenterOffset", m_rotationCenterOffset); KisDomUtils::saveValue(&freeEl, "transformAroundRotationCenter", m_transformAroundRotationCenter); KisDomUtils::saveValue(&freeEl, "aX", m_aX); KisDomUtils::saveValue(&freeEl, "aY", m_aY); KisDomUtils::saveValue(&freeEl, "aZ", m_aZ); KisDomUtils::saveValue(&freeEl, "cameraPos", m_cameraPos); KisDomUtils::saveValue(&freeEl, "scaleX", m_scaleX); KisDomUtils::saveValue(&freeEl, "scaleY", m_scaleY); KisDomUtils::saveValue(&freeEl, "shearX", m_shearX); KisDomUtils::saveValue(&freeEl, "shearY", m_shearY); KisDomUtils::saveValue(&freeEl, "keepAspectRatio", m_keepAspectRatio); KisDomUtils::saveValue(&freeEl, "flattenedPerspectiveTransform", m_flattenedPerspectiveTransform); KisDomUtils::saveValue(&freeEl, "filterId", m_filter->id()); } else if (m_mode == WARP || m_mode == CAGE) { QDomElement warpEl = doc.createElement("warp_transform"); e->appendChild(warpEl); KisDomUtils::saveValue(&warpEl, "defaultPoints", m_defaultPoints); KisDomUtils::saveValue(&warpEl, "originalPoints", m_origPoints); KisDomUtils::saveValue(&warpEl, "transformedPoints", m_transfPoints); KisDomUtils::saveValue(&warpEl, "warpType", (int)m_warpType); // limited! KisDomUtils::saveValue(&warpEl, "alpha", m_alpha); if(m_mode == CAGE){ KisDomUtils::saveValue(&warpEl,"pixelPrecision",m_pixelPrecision); KisDomUtils::saveValue(&warpEl,"previewPixelPrecision",m_previewPixelPrecision); } } else if (m_mode == LIQUIFY) { QDomElement liqEl = doc.createElement("liquify_transform"); e->appendChild(liqEl); m_liquifyProperties->toXML(&liqEl); m_liquifyWorker->toXML(&liqEl); } else { KIS_ASSERT_RECOVER_RETURN(0 && "Unknown transform mode"); } // m_editTransformPoints should not be saved since it is reset explicitly } ToolTransformArgs ToolTransformArgs::fromXML(const QDomElement &e) { ToolTransformArgs args; int newMode = e.attribute("mode", "0").toInt(); if (newMode < 0 || newMode >= N_MODES) return ToolTransformArgs(); args.m_mode = (TransformMode) newMode; // reset explicitly args.m_editTransformPoints = false; bool result = false; if (args.m_mode == FREE_TRANSFORM || args.m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl; QString filterId; result = KisDomUtils::findOnlyElement(e, "free_transform", &freeEl) && KisDomUtils::loadValue(freeEl, "transformedCenter", &args.m_transformedCenter) && KisDomUtils::loadValue(freeEl, "originalCenter", &args.m_originalCenter) && KisDomUtils::loadValue(freeEl, "rotationCenterOffset", &args.m_rotationCenterOffset) && KisDomUtils::loadValue(freeEl, "aX", &args.m_aX) && KisDomUtils::loadValue(freeEl, "aY", &args.m_aY) && KisDomUtils::loadValue(freeEl, "aZ", &args.m_aZ) && KisDomUtils::loadValue(freeEl, "cameraPos", &args.m_cameraPos) && KisDomUtils::loadValue(freeEl, "scaleX", &args.m_scaleX) && KisDomUtils::loadValue(freeEl, "scaleY", &args.m_scaleY) && KisDomUtils::loadValue(freeEl, "shearX", &args.m_shearX) && KisDomUtils::loadValue(freeEl, "shearY", &args.m_shearY) && KisDomUtils::loadValue(freeEl, "keepAspectRatio", &args.m_keepAspectRatio) && KisDomUtils::loadValue(freeEl, "flattenedPerspectiveTransform", &args.m_flattenedPerspectiveTransform) && KisDomUtils::loadValue(freeEl, "filterId", &filterId); // transformAroundRotationCenter is a new parameter introduced in Krita 4.0, // so it might be not present in older transform masks if (!KisDomUtils::loadValue(freeEl, "transformAroundRotationCenter", &args.m_transformAroundRotationCenter)) { args.m_transformAroundRotationCenter = false; } if (result) { args.m_filter = KisFilterStrategyRegistry::instance()->value(filterId); result = (bool) args.m_filter; } } else if (args.m_mode == WARP || args.m_mode == CAGE) { QDomElement warpEl; int warpType = 0; result = KisDomUtils::findOnlyElement(e, "warp_transform", &warpEl) && KisDomUtils::loadValue(warpEl, "defaultPoints", &args.m_defaultPoints) && KisDomUtils::loadValue(warpEl, "originalPoints", &args.m_origPoints) && KisDomUtils::loadValue(warpEl, "transformedPoints", &args.m_transfPoints) && KisDomUtils::loadValue(warpEl, "warpType", &warpType) && KisDomUtils::loadValue(warpEl, "alpha", &args.m_alpha); if(args.m_mode == CAGE){ // Pixel precision is a parameter introduced in Krita 4.2, so we should // expect it not being present in older files. In case it is not found, // just use the defalt value initialized by c-tor (that is, do nothing). (void) KisDomUtils::loadValue(warpEl, "pixelPrecision", &args.m_pixelPrecision); (void) KisDomUtils::loadValue(warpEl, "previewPixelPrecision", &args.m_previewPixelPrecision); } if (result && warpType >= 0 && warpType < KisWarpTransformWorker::N_MODES) { args.m_warpType = (KisWarpTransformWorker::WarpType_) warpType; } else { result = false; } } else if (args.m_mode == LIQUIFY) { QDomElement liquifyEl; result = KisDomUtils::findOnlyElement(e, "liquify_transform", &liquifyEl); *args.m_liquifyProperties = KisLiquifyProperties::fromXML(e); args.m_liquifyWorker.reset(KisLiquifyTransformWorker::fromXML(e)); } else { KIS_ASSERT_RECOVER_NOOP(0 && "Unknown transform mode"); } KIS_SAFE_ASSERT_RECOVER(result) { args = ToolTransformArgs(); } return args; } void ToolTransformArgs::saveContinuedState() { m_continuedTransformation.reset(); m_continuedTransformation.reset(new ToolTransformArgs(*this)); } void ToolTransformArgs::restoreContinuedState() { QScopedPointer tempTransformation( new ToolTransformArgs(*m_continuedTransformation)); *this = *tempTransformation; m_continuedTransformation.swap(tempTransformation); } const ToolTransformArgs* ToolTransformArgs::continuedTransform() const { return m_continuedTransformation.data(); } diff --git a/plugins/tools/tool_transform2/transform_transaction_properties.h b/plugins/tools/tool_transform2/transform_transaction_properties.h index 2005f15220..388d881d3b 100644 --- a/plugins/tools/tool_transform2/transform_transaction_properties.h +++ b/plugins/tools/tool_transform2/transform_transaction_properties.h @@ -1,146 +1,157 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TRANSFORM_TRANSACTION_PROPERTIES_H #define __TRANSFORM_TRANSACTION_PROPERTIES_H #include #include #include "kis_node.h" #include "kis_layer_utils.h" #include "kis_external_layer_iface.h" class ToolTransformArgs; - class TransformTransactionProperties { public: TransformTransactionProperties() { } TransformTransactionProperties(const QRectF &originalRect, ToolTransformArgs *currentConfig, KisNodeSP rootNode, const QList &transformedNodes) : m_originalRect(originalRect), m_currentConfig(currentConfig), m_rootNode(rootNode), - m_shouldAvoidPerspectiveTransform(false), - m_transformedNodes(transformedNodes) + m_transformedNodes(transformedNodes), + m_shouldAvoidPerspectiveTransform(false) { - Q_FOREACH (KisNodeSP node, m_transformedNodes) { + m_hasInvisibleNodes = false; + Q_FOREACH (KisNodeSP node, transformedNodes) { if (KisExternalLayer *extLayer = dynamic_cast(node.data())) { if (!extLayer->supportsPerspectiveTransform()) { m_shouldAvoidPerspectiveTransform = true; break; } } + + m_hasInvisibleNodes |= !node->visible(false); } } qreal originalHalfWidth() const { return m_originalRect.width() / 2.0; } qreal originalHalfHeight() const { return m_originalRect.height() / 2.0; } QRectF originalRect() const { return m_originalRect; } QPointF originalCenterGeometric() const { return m_originalRect.center(); } QPointF originalTopLeft() const { return m_originalRect.topLeft(); } QPointF originalBottomLeft() const { return m_originalRect.bottomLeft(); } QPointF originalBottomRight() const { return m_originalRect.bottomRight(); } QPointF originalTopRight() const { return m_originalRect.topRight(); } QPointF originalMiddleLeft() const { return QPointF(m_originalRect.left(), (m_originalRect.top() + m_originalRect.bottom()) / 2.0); } QPointF originalMiddleRight() const { return QPointF(m_originalRect.right(), (m_originalRect.top() + m_originalRect.bottom()) / 2.0); } QPointF originalMiddleTop() const { return QPointF((m_originalRect.left() + m_originalRect.right()) / 2.0, m_originalRect.top()); } QPointF originalMiddleBottom() const { return QPointF((m_originalRect.left() + m_originalRect.right()) / 2.0, m_originalRect.bottom()); } QPoint originalTopLeftAligned() const { return m_originalRect.toAlignedRect().topLeft(); } QPoint originalBottomRightAligned() const { return m_originalRect.toAlignedRect().bottomRight(); } ToolTransformArgs* currentConfig() const { return m_currentConfig; } KisNodeSP rootNode() const { return m_rootNode; } + KisNodeList transformedNodes() const { + return m_transformedNodes; + } + qreal basePreviewOpacity() const { return 0.9 * qreal(m_rootNode->opacity()) / 255.0; } bool shouldAvoidPerspectiveTransform() const { return m_shouldAvoidPerspectiveTransform; } - QList nodesList() const { - return m_transformedNodes; + bool hasInvisibleNodes() const { + return m_hasInvisibleNodes; + } + + void setCurrentConfigLocation(ToolTransformArgs *config) { + m_currentConfig = config; } private: /** * Information about the original selected rect * (before any transformations) */ QRectF m_originalRect; ToolTransformArgs *m_currentConfig; KisNodeSP m_rootNode; + KisNodeList m_transformedNodes; bool m_shouldAvoidPerspectiveTransform; - QList m_transformedNodes; + bool m_hasInvisibleNodes; }; #endif /* __TRANSFORM_TRANSACTION_PROPERTIES_H */