diff --git a/.gitignore b/.gitignore index 71bc26627e..50dd5a3dce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,34 @@ +compile_commands.json *.orig *.rej __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/ build_dir .flatpak-builder diff --git a/3rdparty/ext_qt/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch b/3rdparty/ext_qt/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch new file mode 100644 index 0000000000..e53b704e64 --- /dev/null +++ b/3rdparty/ext_qt/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch @@ -0,0 +1,123 @@ +Enables warnings for overwriting files in case the name comes from the selected filter. +See bug 412651. + + +diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp +index eb3479b3e..b84441ce2 100644 +--- a/src/widgets/dialogs/qfiledialog.cpp ++++ b/src/widgets/dialogs/qfiledialog.cpp +@@ -1160,6 +1160,19 @@ Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path) + } + #endif + ++static inline QString selectedNameFilterSuffix(const QFileDialog *dialog) ++{ ++ const QString nameFilter = dialog->selectedNameFilter(); ++ if (nameFilter.isEmpty()) ++ return QString(); ++ foreach (const QString &filter, QPlatformFileDialogHelper::cleanFilterList(nameFilter)) { ++ ++ if (filter.startsWith(QLatin1String("*.")) && filter.indexOf(QLatin1Char('*'), 2) < 0) ++ return filter.right(filter.size() - 2); ++ } ++ return QString(); ++} ++ + /** + Returns the text in the line edit which can be one or more file names + */ +@@ -1198,7 +1211,11 @@ QStringList QFileDialogPrivate::typedFiles() const + #endif + } + } +- return addDefaultSuffixToFiles(files); ++ ++ QString suffix = q->defaultSuffix(); ++ if (suffix.isNull()) // Intended, setting an empty string should suppress the name filter. ++ suffix = selectedNameFilterSuffix(q); ++ return addSuffixToFiles(files, suffix); + } + + // Return selected files without defaulting to the root of the file system model +@@ -1209,7 +1226,7 @@ QList QFileDialogPrivate::userSelectedFiles() const + QList files; + + if (!usingWidgets()) +- return addDefaultSuffixToUrls(selectedFiles_sys()); ++ return addSuffixToUrls(selectedFiles_sys(), options->defaultSuffix()); + + const QModelIndexList selectedRows = qFileDialogUi->listView->selectionModel()->selectedRows(); + files.reserve(selectedRows.size()); +@@ -1226,16 +1243,16 @@ QList QFileDialogPrivate::userSelectedFiles() const + return files; + } + +-QStringList QFileDialogPrivate::addDefaultSuffixToFiles(const QStringList &filesToFix) const ++QStringList QFileDialogPrivate::addSuffixToFiles(const QStringList &filesToFix, const QString &suffix) const + { + QStringList files; + for (int i=0; idefaultSuffix(); +- if (!defaultSuffix.isEmpty() && !info.isDir() && name.lastIndexOf(QLatin1Char('.')) == -1) +- name += QLatin1Char('.') + defaultSuffix; ++ // if the filename has no suffix, add the desired suffix ++ if (!suffix.isEmpty() && !info.isDir() && name.lastIndexOf(QLatin1Char('.')) == -1) { ++ name += QLatin1Char('.') + suffix; ++ } + if (info.isAbsolute()) { + files.append(name); + } else { +@@ -1252,17 +1269,17 @@ QStringList QFileDialogPrivate::addDefaultSuffixToFiles(const QStringList &files + return files; + } + +-QList QFileDialogPrivate::addDefaultSuffixToUrls(const QList &urlsToFix) const ++QList QFileDialogPrivate::addSuffixToUrls(const QList &urlsToFix, const QString suffix) const + { + QList urls; + const int numUrlsToFix = urlsToFix.size(); + urls.reserve(numUrlsToFix); + for (int i = 0; i < numUrlsToFix; ++i) { + QUrl url = urlsToFix.at(i); +- // if the filename has no suffix, add the default suffix +- const QString defaultSuffix = options->defaultSuffix(); +- if (!defaultSuffix.isEmpty() && !url.path().endsWith(QLatin1Char('/')) && url.path().lastIndexOf(QLatin1Char('.')) == -1) +- url.setPath(url.path() + QLatin1Char('.') + defaultSuffix); ++ // if the filename has no suffix, add the desired suffix ++ if (!suffix.isEmpty() && !url.path().endsWith(QLatin1Char('/')) && url.path().lastIndexOf(QLatin1Char('.')) == -1) { ++ url.setPath(url.path() + QLatin1Char('.') + suffix); ++ } + urls.append(url); + } + return urls; +@@ -1929,6 +1946,12 @@ bool QFileDialog::confirmOverwrite() const + file). + + If the first character is a dot ('.'), it is removed. ++ ++ If the suffix is not set, the first usable suffix of the selected name ++ filter is appended. This can be suppressed by the property to a non-null, ++ empty string. ++ ++ \sa selectedNameFilter(), QString::isNull() + */ + void QFileDialog::setDefaultSuffix(const QString &suffix) + { +diff --git a/src/widgets/dialogs/qfiledialog_p.h b/src/widgets/dialogs/qfiledialog_p.h +index 2e49696b7..0a029efaa 100644 +--- a/src/widgets/dialogs/qfiledialog_p.h ++++ b/src/widgets/dialogs/qfiledialog_p.h +@@ -133,8 +133,8 @@ public: + static QString initialSelection(const QUrl &path); + QStringList typedFiles() const; + QList userSelectedFiles() const; +- QStringList addDefaultSuffixToFiles(const QStringList &filesToFix) const; +- QList addDefaultSuffixToUrls(const QList &urlsToFix) const; ++ QStringList addSuffixToFiles(const QStringList &filesToFix, const QString& suffix) const; ++ QList addSuffixToUrls(const QList &urlsToFix, const QString suffix) const; + bool removeDirectory(const QString &path); + void setLabelTextControl(QFileDialog::DialogLabel label, const QString &text); + inline void updateLookInLabel(); diff --git a/3rdparty/ext_qt/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch b/3rdparty/ext_qt/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch new file mode 100644 index 0000000000..f9fa338b3c --- /dev/null +++ b/3rdparty/ext_qt/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch @@ -0,0 +1,161 @@ +diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp +index 9fccee8c..8c868fce 100644 +--- a/src/widgets/dialogs/qfiledialog.cpp ++++ b/src/widgets/dialogs/qfiledialog.cpp +@@ -963,11 +963,7 @@ void QFileDialog::setDirectory(const QString &directory) + QDir QFileDialog::directory() const + { + Q_D(const QFileDialog); +- if (d->nativeDialogInUse) { +- QString dir = d->directory_sys().toLocalFile(); +- return QDir(dir.isEmpty() ? d->options->initialDirectory().toLocalFile() : dir); +- } +- return d->rootPath(); ++ return d->_q_directory(); + } + + /*! +@@ -1160,13 +1156,38 @@ Q_AUTOTEST_EXPORT QString qt_tildeExpansion(const QString &path) + } + #endif + +-static inline QString selectedNameFilterSuffix(const QFileDialog *dialog) ++static inline QString selectedNameFilterSuffix(const QString nameFilter, const QStringList filesList, const QString directory, const QString currentExtension = "") + { +- const QString nameFilter = dialog->selectedNameFilter(); ++ QString directoryPath = directory + QDir::separator(); + if (nameFilter.isEmpty()) + return QString(); +- foreach (const QString &filter, QPlatformFileDialogHelper::cleanFilterList(nameFilter)) { + ++ // special usecase to avoid jpg/jpeg issues ++ const QString jpeg = "jpeg"; ++ const QString jpg = "jpg"; ++ ++ if (nameFilter.contains(".kra")) { ++ return "kra"; ++ } ++ ++ if (nameFilter.contains(jpg, Qt::CaseInsensitive) || nameFilter.contains(jpeg, Qt::CaseInsensitive)) { ++ ++ foreach (const QString &file, filesList) { ++ if (QFile::exists(directoryPath + file + QString(".") + jpg)) { ++ return QString(jpg); ++ } ++ if (QFile::exists(directoryPath + file + QString(".") + jpeg)) { ++ return QString(jpeg); ++ } ++ } ++ ++ if (currentExtension == jpg || currentExtension == jpeg) { ++ return currentExtension; ++ } ++ ++ } ++ ++ foreach (const QString &filter, QPlatformFileDialogHelper::cleanFilterList(nameFilter)) { + if (filter.startsWith(QLatin1String("*.")) && filter.indexOf(QLatin1Char('*'), 2) < 0) + return filter.right(filter.size() - 2); + } +@@ -1214,7 +1235,7 @@ QStringList QFileDialogPrivate::typedFiles() const + + QString suffix = q->defaultSuffix(); + if (suffix.isNull()) // Intended, setting an empty string should suppress the name filter. +- suffix = selectedNameFilterSuffix(q); ++ suffix = selectedNameFilterSuffix(q->selectedNameFilter(), files, q->directory().path()); + return addSuffixToFiles(files, suffix); + } + +@@ -1252,7 +1273,7 @@ QStringList QFileDialogPrivate::addSuffixToFiles(const QStringList &filesToFix, + // if the filename has no suffix, add the desired suffix + if (!suffix.isEmpty() && !info.isDir() && name.lastIndexOf(QLatin1Char('.')) == -1) { + name += QLatin1Char('.') + suffix; +- } ++ } + if (info.isAbsolute()) { + files.append(name); + } else { +@@ -1276,12 +1297,12 @@ QList QFileDialogPrivate::addSuffixToUrls(const QList &urlsToFix, co + urls.reserve(numUrlsToFix); + for (int i = 0; i < numUrlsToFix; ++i) { + QUrl url = urlsToFix.at(i); +- // if the filename has no suffix, add the desired suffix ++ // if the filename has no suffix, add the desired suffix + if (!suffix.isEmpty() && !url.path().endsWith(QLatin1Char('/')) && url.path().lastIndexOf(QLatin1Char('.')) == -1) { + url.setPath(url.path() + QLatin1Char('.') + suffix); +- } ++ } + urls.append(url); +- } ++ } + return urls; + } + +@@ -3731,6 +3752,17 @@ void QFileDialogPrivate::_q_goToDirectory(const QString &path) + } + } + ++ ++QDir QFileDialogPrivate::_q_directory() const ++{ ++ if (nativeDialogInUse) { ++ QString dir = directory_sys().toLocalFile(); ++ return QDir(dir.isEmpty() ? options->initialDirectory().toLocalFile() : dir); ++ } ++ return rootPath(); ++} ++ ++ + /*! + \internal + +@@ -3748,19 +3780,27 @@ void QFileDialogPrivate::_q_useNameFilter(int index) + + QString nameFilter = nameFilters.at(index); + QStringList newNameFilters = QPlatformFileDialogHelper::cleanFilterList(nameFilter); ++ + if (q_func()->acceptMode() == QFileDialog::AcceptSave) { + QString newNameFilterExtension; +- if (newNameFilters.count() > 0) +- newNameFilterExtension = QFileInfo(newNameFilters.at(0)).suffix(); +- + QString fileName = lineEdit()->text(); + const QString fileNameExtension = QFileInfo(fileName).suffix(); +- if (!fileNameExtension.isEmpty() && !newNameFilterExtension.isEmpty()) { +- const int fileNameExtensionLength = fileNameExtension.count(); +- fileName.replace(fileName.count() - fileNameExtensionLength, +- fileNameExtensionLength, newNameFilterExtension); +- qFileDialogUi->listView->clearSelection(); +- lineEdit()->setText(fileName); ++ ++ if (!fileNameExtension.isEmpty()) { ++ ++ QString fileNameWithoutExtension(fileName); ++ int fileNameExtensionLength = fileNameExtension.count(); ++ fileNameWithoutExtension.remove(fileName.count() - (fileNameExtensionLength + 1), fileNameExtensionLength + 1); // +1 to count the dot, too ++ ++ QStringList filesList = {fileNameWithoutExtension}; ++ newNameFilterExtension = selectedNameFilterSuffix(nameFilter, filesList, _q_directory().path(), fileNameExtension); ++ ++ if (!newNameFilterExtension.isEmpty()) { ++ fileName.replace(fileName.count() - fileNameExtensionLength, ++ fileNameExtensionLength, newNameFilterExtension); ++ qFileDialogUi->listView->clearSelection(); ++ lineEdit()->setText(fileName); ++ } + } + } + +diff --git a/src/widgets/dialogs/qfiledialog_p.h b/src/widgets/dialogs/qfiledialog_p.h +index 0a029efa..3979c6eb 100644 +--- a/src/widgets/dialogs/qfiledialog_p.h ++++ b/src/widgets/dialogs/qfiledialog_p.h +@@ -226,6 +226,8 @@ public: + void _q_rowsInserted(const QModelIndex & parent); + void _q_fileRenamed(const QString &path, const QString &oldName, const QString &newName); + ++ QDir _q_directory() const; ++ + // layout + #if QT_CONFIG(proxymodel) + QAbstractProxyModel *proxyModel; diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt index c5c367800a..83e613f9e3 100644 --- a/3rdparty/ext_qt/CMakeLists.txt +++ b/3rdparty/ext_qt/CMakeLists.txt @@ -1,299 +1,309 @@ 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}/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 COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0009-Fix-Rec2020-display-format.patch ) # AMD random generation patch set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND} COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.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}/0080-Sync-buffers-of-the-destination-file-after-QFile-cop.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.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.5/single/qt-everywhere-src-5.12.5.zip URL_MD5 157fa5d9c897737ce06ba4f9a20151e0 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 COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/fix-android-menu64bit.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.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.5/single/qt-everywhere-src-5.12.5.tar.xz URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c 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 COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.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 -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 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.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 + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch + COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.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/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c 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 1bb3e7ae15..cd95efc2cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,926 +1,926 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.44.0) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) 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.12 -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_compile_options($<$:-Wno-suggest-override> -Wextra) + add_compile_options($<$:-Wno-suggest-override> -Wextra -Wno-class-memaccess) 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 ) 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) else() set(HAVE_DBUS FALSE) set(HAVE_X11 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.14.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00) #if(${Qt5_VERSION} VERSION_GREATER "5.10.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00) #if(${Qt5_VERSION} VERSION_GREATER "5.9.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00) #else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) #endif() add_definitions(-DQT_DEPRECATED_WARNINGS) 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") find_package(OpenJPEG "2.3.0") set_package_properties(OpenJPEG PROPERTIES DESCRIPTION "Library for loading and saving jp2000 files." URL "http://www.openjpeg.org/" TYPE OPTIONAL PURPOSE "Required by the Krita JP2000 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() 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/krita/data/CMakeLists.txt b/krita/data/CMakeLists.txt index 1013dfb365..02fc9bec22 100644 --- a/krita/data/CMakeLists.txt +++ b/krita/data/CMakeLists.txt @@ -1,26 +1,27 @@ add_subdirectory( actions ) add_subdirectory( brushes ) add_subdirectory( bundles ) add_subdirectory( patterns ) add_subdirectory( gradients ) add_subdirectory( profiles ) add_subdirectory( templates ) add_subdirectory( workspaces ) add_subdirectory( themes ) add_subdirectory( predefined_image_sizes ) add_subdirectory( input ) add_subdirectory( shortcuts ) add_subdirectory( paintoppresets ) add_subdirectory( palettes ) add_subdirectory( symbols ) add_subdirectory( preset_icons ) add_subdirectory( metadata/schemas ) add_subdirectory( gamutmasks ) +add_subdirectory( windowlayouts ) ########### install files ############### install( FILES kritarc DESTINATION ${CONFIG_INSTALL_DIR} ) diff --git a/krita/data/aboutdata/libraries.txt b/krita/data/aboutdata/libraries.txt index 998cec61af..58e9becf1a 100644 --- a/krita/data/aboutdata/libraries.txt +++ b/krita/data/aboutdata/libraries.txt @@ -1,33 +1,33 @@ Boost,https://www.boost.org,Boost Software License - Version 1.0 DrMingw,https://github.com/jrfonseca/drmingw,LGPLv2.1 Eigen3,http://eigen.tuxfamily.org,MPL2 Exiv2,http://www.exiv2.org,GPLv2 Expat,https://github.com/libexpat/libexpat,MIT ffmpeg,https://www.ffmpeg.org,LGPL2.1+ fftw3,http://www.fftw.org/,GPLv2+ fontconfig,https://www.freedesktop.org/wiki/Software/fontconfig/,BSD freetype,https://www.freetype.org/,FTL or GPLv2 gettext,https://www.gnu.org/software/gettext/,GPLv3 giflib,http://giflib.sourceforge.net/,BSD gmic,http://gmic.eu/,CeCILLv2.1 gmic-qt,http://gmic.eu/,GPLv3 GNU Scientific Library,http://www.gnu.org/software/gsl,GPLv3 iconv,https://www.gnu.org/software/libiconv/,LGPLv2 or GPLv3 ilmbase,http://www.openexr.com,Modified BSD KDE Frameworks 5,https://www.kde.org,LGPLv2.1+ libheif,https://github.com/strukturag/libheif,LGPLv3 libjpeg-turbo,http://www.libjpeg-turbo.org,BSD libpng,http://www.libpng.org,libpng license libraw,http://www.libraw.org,LGPLv2.1 or CDDL 1.0 libtiff,http://libtiff.org/,BSD littlecms2,http://www.littlecms.com,MIT OpenColorIO,http://www.opencolorio.org,BSD OpenEXR,http://www.openexr.com,Modified BSD poppler,http://poppler.freedesktop.org,GPLv2 and GPLv3 PyQt,https://www.riverbankcomputing.com/software/pyqt/download5,GPLv3 Python,http://www.python.org,Python Software Foundation License v2 Qt,https://www.qt.io,GPLv2 + GPLv3 + LGPLv2.1 + LGPLv3 -Quazip,https://github.com/stachenov/quazip,LBPLv2.1 +Quazip,https://github.com/stachenov/quazip,LGPLv2.1 SIP,https://www.riverbankcomputing.com/software/sip/download,GPLv3 Vc,https://github.com/VcDevel/Vc,BSD zlib,http://www.zlib.net/,BSD diff --git a/krita/data/sessions/Painting with references.ksn b/krita/data/sessions/Painting with references.ksn new file mode 100644 index 0000000000..f8aaa9534a --- /dev/null +++ b/krita/data/sessions/Painting with references.ksn @@ -0,0 +1,36 @@ + + + + + + + + + + + false + file:///C:/samples/Painting.kra + QWRuUXl3QUNBQUFBQUFBQUFBQUFBQUFBQTNFQUFBUDZBQUFBQUFBQUFBRC8vLy8rLy8vLy9nQUFBQUVDQUFBQUE1QT0= + false + 355 + 502.5 + 0 + + false + 1.1937001140250856 + 2 + + + false + file:///C:/samples/References.kra + QWRuUXl3QUNBQUFBQUFBQUFBQUFBQUFBQTNFQUFBUzhBQUFBQUFBQUFBRC8vLy8rLy8vLy9nQUFBQUFDQUFBQUE1QT0= + false + 424 + 599.5 + 0 + + false + 1.424125807677689 + 2 + + diff --git a/krita/data/sessions/Working with references.ksn b/krita/data/sessions/Working with references.ksn new file mode 100644 index 0000000000..ae1b8c5bc7 --- /dev/null +++ b/krita/data/sessions/Working with references.ksn @@ -0,0 +1,36 @@ + + + + + + + + + + + false + file:///C:/samples/Painting.kra + QWRuUXl3QUNBQUFBQUFBQUFBQUFBQUFBQW1zQUFBUzhBQUFBQUFBQUFBRC8vLy8rLy8vLy9nQUFBQUFDQUFBQUE1QT0= + false + 34 + 351.6932373046875 + 0 + + false + 59.04048681259156 + 0 + + + false + file:///C:/samples/References.kra + QWRuUXl3QUNBQUFBQUFBQUFBQUFBQUFBQTNNQUFBUzhBQUFBQUFBQUFBRC8vLy8rLy8vLy9nQUFBQUVDQUFBQUE1QT0= + false + 345.6188510879705 + 432.48219934451225 + 0 + + false + 0.9617056710520412 + 0 + + diff --git a/krita/data/windowlayouts/CMakeLists.txt b/krita/data/windowlayouts/CMakeLists.txt new file mode 100644 index 0000000000..31c5e6aac0 --- /dev/null +++ b/krita/data/windowlayouts/CMakeLists.txt @@ -0,0 +1,7 @@ +########### install files ############### + +install( FILES + "Cockpit (laptop mode).kwl" + "Dual screen editing.kwl" +DESTINATION ${DATA_INSTALL_DIR}/krita/windowlayouts) + diff --git a/krita/data/windowlayouts/Cockpit (laptop mode).kwl b/krita/data/windowlayouts/Cockpit (laptop mode).kwl new file mode 100644 index 0000000000..de54412e91 --- /dev/null +++ b/krita/data/windowlayouts/Cockpit (laptop mode).kwl @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/krita/data/windowlayouts/Dual screen editing.kwl b/krita/data/windowlayouts/Dual screen editing.kwl new file mode 100644 index 0000000000..e465eb8a57 --- /dev/null +++ b/krita/data/windowlayouts/Dual screen editing.kwl @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/krita/main.cc b/krita/main.cc index a75c3a8f34..d2d84e3d89 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,596 +1,598 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #ifdef Q_OS_ANDROID #include #endif #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include #else #include #endif #include "config-high-dpi-scale-factor-rounding-policy.h" #include "config-set-has-border-in-full-screen-default.h" #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT #include #endif #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif #ifdef Q_OS_WIN namespace { typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif #ifdef Q_OS_ANDROID extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisPart *kisPart = KisPart::instance(); QList> list = kisPart->documents(); for (QPointer &doc: list) { doc->autoSaveOnPause(); } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); mainWindow->viewFullscreen(false); } __attribute__ ((visibility ("default"))) #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY // This rounding policy depends on a series of patches to Qt related to // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied // in ext_qt for WIndows (patches 0031-0036). // // The rounding policy can be set externally by setting the environment // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following: // Round: Round up for .5 and above. // Ceil: Always round up. // Floor: Always round down. // RoundPreferFloor: Round up for .75 and above. // PassThrough: Don't round. // // The default is set to RoundPreferFloor for better behaviour than before, // but can be overridden by the above environment variable. QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif #ifdef Q_OS_ANDROID const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE"; const QStringList permissions = { write_permission }; const QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList(permissions)); if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) { // TODO: show a dialog and graciously exit dbgKrita << "Permission denied by the user"; } else { dbgKrita << "Permission granted"; } #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); #ifdef Q_OS_WIN const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString(); #else const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); #endif preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); const KisOpenGL::RendererConfig config = KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); KisOpenGL::setDefaultSurfaceConfig(config); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KisUsageLogger::writeHeader(); KisOpenGL::initialize(); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs() || args.exportSequence(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } - +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + app.setAttribute(Qt::AA_DisableWindowContextHelpButton); +#endif app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information KisUsageLogger::write("\nHardware Information\n"); KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); KisConfig(true).logImportantSettings(); if (!app.start(args)) { KisUsageLogger::log("Could not start Krita Application"); return 1; } int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp index bb955d7e9d..641de90986 100644 --- a/libs/flake/KoPathShape.cpp +++ b/libs/flake/KoPathShape.cpp @@ -1,1656 +1,1661 @@ /* This file is part of the KDE project Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann Copyright (C) 2006-2011 Jan Hambrecht Copyright (C) 2007-2009 Thomas Zander Copyright (C) 2011 Jean-Nicolas Artaud 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 "KoPathShape.h" #include "KoPathShape_p.h" #include "KoPathSegment.h" #include "KoOdfWorkaround.h" #include "KoPathPoint.h" #include "KoShapeStrokeModel.h" #include "KoViewConverter.h" #include "KoPathShapeLoader.h" #include "KoShapeSavingContext.h" #include "KoShapeLoadingContext.h" #include "KoShapeShadow.h" #include "KoShapeBackground.h" #include "KoShapeContainer.h" #include "KoFilterEffectStack.h" #include "KoMarker.h" #include "KoShapeStroke.h" #include "KoInsets.h" #include #include #include #include #include #include #include #include "KisQPainterStateSaver.h" #include #include #include "kis_global.h" #include // for qIsNaN static bool qIsNaNPoint(const QPointF &p) { return qIsNaN(p.x()) || qIsNaN(p.y()); } KoPathShape::Private::Private() : QSharedData() , fillRule(Qt::OddEvenFill) , autoFillMarkers(false) { } KoPathShape::Private::Private(const Private &rhs) : QSharedData() , fillRule(rhs.fillRule) , markersNew(rhs.markersNew) , autoFillMarkers(rhs.autoFillMarkers) { } QRectF KoPathShape::Private::handleRect(const QPointF &p, qreal radius) const { return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); } void KoPathShape::Private::applyViewboxTransformation(const KoXmlElement &element) { // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { // load the desired size QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // load the desired position QPointF pos; pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); viewMatrix.translate(pos.x(), pos.y()); // transform the path data map(viewMatrix); } } KoPathShape::KoPathShape() : KoTosContainer() , d(new Private) { } KoPathShape::KoPathShape(const KoPathShape &rhs) : KoTosContainer(rhs) , d(rhs.d) { KoSubpathList subpaths; Q_FOREACH (KoSubpath *subPath, rhs.d->subpaths) { KoSubpath *clonedSubPath = new KoSubpath(); Q_FOREACH (KoPathPoint *point, *subPath) { *clonedSubPath << new KoPathPoint(*point, this); } subpaths << clonedSubPath; } d->subpaths = subpaths; } KoPathShape::~KoPathShape() { clear(); } KoShape *KoPathShape::cloneShape() const { return new KoPathShape(*this); } void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const { if (d->subpaths.length() <= 1) { QTransform matrix; matrix.scale(scaleFactor.width(), scaleFactor.height()); QString points; KoSubpath *subPath = d->subpaths.first(); KoSubpath::const_iterator pointIt(subPath->constBegin()); KoPathPoint *currPoint= 0; // iterate over all points for (; pointIt != subPath->constEnd(); ++pointIt) { currPoint = *pointIt; if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { break; } const QPointF p = matrix.map(currPoint->point()); points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); } if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { context.xmlWriter().startElement("draw:contour-polygon"); context.xmlWriter().addAttribute("svg:width", size().width()); context.xmlWriter().addAttribute("svg:height", size().height()); const QSizeF s(size()); QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); context.xmlWriter().addAttribute("svg:viewBox", viewBox); context.xmlWriter().addAttribute("draw:points", points); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); return; } } // if we get here we couldn't save as polygon - let-s try contour-path context.xmlWriter().startElement("draw:contour-path"); saveOdfAttributes(context, OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); context.xmlWriter().endElement(); } void KoPathShape::saveOdf(KoShapeSavingContext & context) const { context.xmlWriter().startElement("draw:path"); saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); context.xmlWriter().addAttribute("svg:d", toString()); context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); saveOdfCommonChildElements(context); saveText(context); context.xmlWriter().endElement(); } bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) { // first clear the path data from the default path clear(); if (element.localName() == "contour-polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } close(); } else if (element.localName() == "contour-path") { KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } // apply viewbox transformation const QRect viewBox = KoPathShape::loadOdfViewbox(element); if (! viewBox.isEmpty()) { QSizeF size; size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); // create matrix to transform original path data into desired size and position QTransform viewMatrix; viewMatrix.translate(-viewBox.left(), -viewBox.top()); viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); // transform the path data d->map(viewMatrix); } setTransformation(QTransform()); return true; } bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements); // first clear the path data from the default path clear(); if (element.localName() == "line") { QPointF start; start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); QPointF end; end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); moveTo(start); lineTo(end); } else if (element.localName() == "polyline" || element.localName() == "polygon") { QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); points.replace(',', ' '); points.remove('\r'); points.remove('\n'); bool firstPoint = true; const QStringList coordinateList = points.split(' '); for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { QPointF point; point.setX((*it).toDouble()); ++it; point.setY((*it).toDouble()); if (firstPoint) { moveTo(point); firstPoint = false; } else lineTo(point); } if (element.localName() == "polygon") close(); } else { // path loading KoPathShapeLoader loader(this); loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); d->loadNodeTypes(element); } d->applyViewboxTransformation(element); QPointF pos = normalize(); setTransformation(QTransform()); if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); } setPosition(pos); loadOdfAttributes(element, context, OdfTransformation); // now that the correct transformation is set up // apply that matrix to the path geometry so that // we don't transform the stroke d->map(transformation()); setTransformation(QTransform()); normalize(); loadText(element, context); return true; } QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const { style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth) return KoTosContainer::saveStyle(style, context); } void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) { KoTosContainer::loadStyle(element, context); KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); styleStack.setTypeProperties("graphic"); if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; } else { d->fillRule = Qt::WindingFill; #ifndef NWORKAROUND_ODF_BUGS KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); #endif } QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); qreal lineWidth = 0; if (lineBorder) { lineWidth = lineBorder->lineWidth(); } Q_UNUSED(lineWidth); } QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) { QRect viewbox; QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); if (! data.isEmpty()) { data.replace(QLatin1Char(','), QLatin1Char(' ')); const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); if (coordinates.count() == 4) { viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), coordinates.at(2).toInt(), coordinates.at(3).toInt()); } } return viewbox; } void KoPathShape::clear() { Q_FOREACH (KoSubpath *subpath, d->subpaths) { Q_FOREACH (KoPathPoint *point, *subpath) delete point; delete subpath; } d->subpaths.clear(); notifyPointsChanged(); } void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { KisQPainterStateSaver saver(&painter); applyConversion(painter, converter); QPainterPath path(outline()); path.setFillRule(d->fillRule); if (background()) { background()->paint(painter, converter, paintContext, path); } //d->paintDebug(painter); } #ifndef NDEBUG void KoPathShape::Private::paintDebug(QPainter &painter) { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); int i = 0; QPen pen(Qt::black, 0); painter.save(); painter.setPen(pen); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { ++i; KoPathPoint *point = (*it); QRectF r(point->point(), QSizeF(5, 5)); r.translate(-2.5, -2.5); QPen pen(Qt::black, 0); painter.setPen(pen); if (point->activeControlPoint1() && point->activeControlPoint2()) { QBrush b(Qt::red); painter.setBrush(b); } else if (point->activeControlPoint1()) { QBrush b(Qt::yellow); painter.setBrush(b); } else if (point->activeControlPoint2()) { QBrush b(Qt::darkYellow); painter.setBrush(b); } painter.drawEllipse(r); } } painter.restore(); debugFlake << "nop =" << i; } void KoPathShape::Private::debugPath() const { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); } } } #endif void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper) { KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) (*it)->paint(handlesHelper, KoPathPoint::Node); } } QRectF KoPathShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoPathShape::outline() const { QPainterPath path; for (auto subpathIt = d->subpaths.constBegin(); subpathIt != d->subpaths.constEnd(); ++subpathIt) { const KoSubpath * subpath = *subpathIt; const KoPathPoint * lastPoint = subpath->constFirst(); bool activeCP = false; for (auto pointIt = subpath->constBegin(); pointIt != subpath->constEnd(); ++pointIt) { const KoPathPoint * currPoint = *pointIt; KoPathPoint::PointProperties currProperties = currPoint->properties(); if (currPoint == subpath->constFirst()) { if (currProperties & KoPathPoint::StartSubpath) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.moveTo(currPoint->point()); } } else if (activeCP && currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.cubicTo( - lastPoint->controlPoint2(), - currPoint->controlPoint1(), - currPoint->point()); + lastPoint->controlPoint2(), + currPoint->controlPoint1(), + currPoint->point()); } else if (activeCP || currPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( - activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), - currPoint->point()); + activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), + currPoint->point()); } else { Q_ASSERT(!qIsNaNPoint(currPoint->point())); path.lineTo(currPoint->point()); } if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { // add curve when there is a curve on the way to the first point KoPathPoint * firstPoint = subpath->first(); Q_ASSERT(!qIsNaNPoint(firstPoint->point())); if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { path.cubicTo( - currPoint->controlPoint2(), - firstPoint->controlPoint1(), - firstPoint->point()); + currPoint->controlPoint2(), + firstPoint->controlPoint1(), + firstPoint->point()); } else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { Q_ASSERT(!qIsNaNPoint(currPoint->point())); Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); path.quadTo( - currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), - firstPoint->point()); + currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), + firstPoint->point()); } path.closeSubpath(); } if (currPoint->activeControlPoint2()) { activeCP = true; } else { activeCP = false; } lastPoint = currPoint; } } return path; } QRectF KoPathShape::boundingRect() const { const QTransform transform = absoluteTransformation(0); /** * First we approximate the insets of the stroke by rendering a fat bezier curve * with width set to the maximum inset of miters and markers. The are swept by this * curve will be a good approximation of the real curve bounding rect. */ qreal outlineSweepWidth = 0; const QSharedPointer lineBorder = qSharedPointerDynamicCast(stroke()); if (lineBorder) { outlineSweepWidth = lineBorder->lineWidth(); } if (stroke()) { KoInsets inset; stroke()->strokeInsets(this, inset); const qreal maxInset = std::max({inset.left, inset.top, inset.right, inset.bottom}); // insets extend outside the shape, but width extends both inside and outside, // so we should multiply insets by 2.0 outlineSweepWidth = std::max({outlineSweepWidth, 2.0 * maxInset, 2.0 * stroke()->strokeMaxMarkersInset(this)}); } QPen pen(Qt::black, outlineSweepWidth); // select round joins and caps to ensure it sweeps exactly // 'outlineSweepWidth' pixels in every possible pen.setJoinStyle(Qt::RoundJoin); pen.setCapStyle(Qt::RoundCap); QRectF bb = transform.map(pathStroke(pen)).boundingRect(); if (shadow()) { KoInsets insets; shadow()->insets(insets); bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); } if (filterEffectStack()) { QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); bb |= transform.mapRect(clipRect); } return bb; } QSizeF KoPathShape::size() const { // don't call boundingRect here as it uses absoluteTransformation // which itself uses size() -> leads to infinite recursion return outlineRect().size(); } void KoPathShape::setSize(const QSizeF &newSize) { QTransform matrix(resizeMatrix(newSize)); KoShape::setSize(newSize); d->map(matrix); } QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const { QSizeF oldSize = size(); if (oldSize.width() == 0.0) { oldSize.setWidth(0.000001); } if (oldSize.height() == 0.0) { oldSize.setHeight(0.000001); } QSizeF sizeNew(newSize); if (sizeNew.width() == 0.0) { sizeNew.setWidth(0.000001); } if (sizeNew.height() == 0.0) { sizeNew.setHeight(0.000001); } return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); } KoPathPoint * KoPathShape::moveTo(const QPointF &p) { KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); KoSubpath * path = new KoSubpath; path->push_back(point); d->subpaths.push_back(path); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::lineTo(const QPointF &p) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c1); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); point->setControlPoint1(c2); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) { if (d->subpaths.empty()) moveTo(QPointF(0, 0)); KoPathPoint * lastPoint = d->subpaths.last()->last(); updateLastPriv(&lastPoint); lastPoint->setControlPoint2(c); KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); d->subpaths.last()->push_back(point); notifyPointsChanged(); return point; } KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) { if (d->subpaths.empty()) { moveTo(QPointF(0, 0)); } KoPathPoint * lastPoint = d->subpaths.last()->last(); if (lastPoint->properties() & KoPathPoint::CloseSubpath) { lastPoint = d->subpaths.last()->first(); } QPointF startpoint(lastPoint->point()); KoPathPoint * newEndPoint = lastPoint; QPointF curvePoints[12]; int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); for (int i = 0; i < pointCnt; i += 3) { newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); } return newEndPoint; } int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const { int pointCnt = 0; // check Parameters if (sweepAngle == 0.0) return pointCnt; sweepAngle = qBound(-360.0, sweepAngle, 360.0); if (rx == 0 || ry == 0) { //TODO } // split angles bigger than 90° so that it gives a good approximation to the circle qreal parts = ceil(qAbs(sweepAngle / 90.0)); qreal sa_rad = startAngle * M_PI / 180.0; qreal partangle = sweepAngle / parts; qreal endangle = startAngle + partangle; qreal se_rad = endangle * M_PI / 180.0; qreal sinsa = sin(sa_rad); qreal cossa = cos(sa_rad); qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); // startpoint is at the last point is the path but when it is closed // it is at the first point QPointF startpoint(offset); //center berechnen QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); //debugFlake <<"kappa" << kappa <<"parts" << parts; for (int part = 0; part < parts; ++part) { // start tangent curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); qreal sinse = sin(se_rad); qreal cosse = cos(se_rad); // end point QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); // end tangent curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); curvePoints[pointCnt++] = endpoint; // set the endpoint as next start point startpoint = endpoint; sinsa = sinse; cossa = cosse; endangle += partangle; se_rad = endangle * M_PI / 180.0; } return pointCnt; } void KoPathShape::close() { if (d->subpaths.empty()) { return; } closeSubpathPriv(d->subpaths.last()); } void KoPathShape::closeMerge() { if (d->subpaths.empty()) { return; } closeMergeSubpathPriv(d->subpaths.last()); } QPointF KoPathShape::normalize() { QPointF tl(outline().boundingRect().topLeft()); QTransform matrix; matrix.translate(-tl.x(), -tl.y()); d->map(matrix); // keep the top left point of the object applyTransformation(matrix.inverted()); shapeChangedPriv(ContentChanged); return tl; } void KoPathShape::Private::map(const QTransform &matrix) { KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { // It's possible there are null points in the map... if (*it) { (*it)->map(matrix); } } } } void KoPathShape::updateLastPriv(KoPathPoint **lastPoint) { // check if we are about to add a new point to a closed subpath if ((*lastPoint)->properties() & KoPathPoint::StopSubpath && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { // get the first point of the subpath KoPathPoint *subpathStart = d->subpaths.last()->first(); // clone the first point of the subpath... KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, this); // ... and make it a normal point newLastPoint->setProperties(KoPathPoint::Normal); // now start a new subpath with the cloned start point KoSubpath *path = new KoSubpath; path->push_back(newLastPoint); d->subpaths.push_back(path); *lastPoint = newLastPoint; } else { // the subpath was not closed so the formerly last point // of the subpath is no end point anymore (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); } (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); } QList KoPathShape::pointsAt(const QRectF &r) const { QList result; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (r.contains((*it)->point())) result.append(*it); else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) result.append(*it); else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) result.append(*it); } } return result; } QList KoPathShape::segmentsAt(const QRectF &r) const { QList segments; int subpathCount = d->subpaths.count(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { KoSubpath * subpath = d->subpaths[subpathIndex]; int pointCount = subpath->count(); bool subpathClosed = isClosedSubpath(subpathIndex); for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { if (pointIndex == (pointCount - 1) && ! subpathClosed) break; KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); QRectF controlRect = s.controlPointRect(); if (! r.intersects(controlRect) && ! controlRect.contains(r)) continue; QRectF bound = s.boundingRect(); if (! r.intersects(bound) && ! bound.contains(r)) continue; segments.append(s); } } return segments; } KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const { for (int subpathIndex = 0; subpathIndex < d->subpaths.size(); ++subpathIndex) { KoSubpath * subpath = d->subpaths.at(subpathIndex); for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { if (subpath->at(pointPos) == point) { return KoPathPointIndex(subpathIndex, pointPos); } } } return KoPathPointIndex(-1, -1); } KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; return subpath->at(pointIndex.second); } KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const { KoPathSegment segment(0, 0); KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { KoPathPoint * point = subpath->at(pointIndex.second); int index = pointIndex.second; // check if we have a (closing) segment starting from the last point if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) index = 0; else ++index; if (index < subpath->size()) { segment = KoPathSegment(point, subpath->at(index)); } } return segment; } int KoPathShape::pointCount() const { int i = 0; KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { i += (*pathIt)->size(); } return i; } int KoPathShape::subpathCount() const { return d->subpaths.count(); } int KoPathShape::subpathPointCount(int subpathIndex) const { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return -1; return subpath->size(); } bool KoPathShape::isClosedSubpath(int subpathIndex) const { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; return firstClosed && lastClosed; } bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) return false; KoPathPoint::PointProperties properties = point->properties(); properties &= ~KoPathPoint::StartSubpath; properties &= ~KoPathPoint::StopSubpath; properties &= ~KoPathPoint::CloseSubpath; // check if new point starts subpath if (pointIndex.second == 0) { properties |= KoPathPoint::StartSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties |= KoPathPoint::CloseSubpath; } // old first point does not start the subpath anymore subpath->first()->unsetProperty(KoPathPoint::StartSubpath); } // check if new point stops subpath else if (pointIndex.second == subpath->size()) { properties |= KoPathPoint::StopSubpath; // subpath was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep the path closed properties = properties | KoPathPoint::CloseSubpath; } // old last point does not end subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); } point->setProperties(properties); point->setParent(this); subpath->insert(pointIndex.second , point); notifyPointsChanged(); return true; } KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) return 0; KoPathPoint * point = subpath->takeAt(pointIndex.second); point->setParent(0); //don't do anything (not even crash), if there was only one point if (pointCount()==0) { return point; } // check if we removed the first point else if (pointIndex.second == 0) { // first point removed, set new StartSubpath subpath->first()->setProperty(KoPathPoint::StartSubpath); // check if path was closed if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->first()->setProperty(KoPathPoint::CloseSubpath); } } // check if we removed the last point else if (pointIndex.second == subpath->size()) { // use size as point is already removed // last point removed, set new StopSubpath subpath->last()->setProperty(KoPathPoint::StopSubpath); // check if path was closed if (point->properties() & KoPathPoint::CloseSubpath) { // keep path closed subpath->last()->setProperty(KoPathPoint::CloseSubpath); } } notifyPointsChanged(); return point; } bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 - || isClosedSubpath(pointIndex.first)) + || isClosedSubpath(pointIndex.first)) return false; KoSubpath * newSubpath = new KoSubpath; int size = subpath->size(); for (int i = pointIndex.second + 1; i < size; ++i) { newSubpath->append(subpath->takeAt(pointIndex.second + 1)); } // now make the first point of the new subpath a starting node newSubpath->first()->setProperty(KoPathPoint::StartSubpath); // the last point of the old subpath is now an ending node subpath->last()->setProperty(KoPathPoint::StopSubpath); // insert the new subpath after the broken one d->subpaths.insert(pointIndex.first + 1, newSubpath); notifyPointsChanged(); return true; } bool KoPathShape::join(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) || isClosedSubpath(subpathIndex+1)) return false; // the last point of the subpath does not end the subpath anymore subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // the first point of the next subpath does not start a subpath anymore nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); // append the second subpath to the first Q_FOREACH (KoPathPoint * p, *nextSubpath) subpath->append(p); // remove the nextSubpath from path d->subpaths.removeAt(subpathIndex + 1); // delete it as it is no longer possible to use it delete nextSubpath; notifyPointsChanged(); return true; } bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) { KoSubpath *subpath = d->subPath(oldSubpathIndex); if (subpath == 0 || newSubpathIndex >= d->subpaths.size()) return false; if (oldSubpathIndex == newSubpathIndex) return true; d->subpaths.removeAt(oldSubpathIndex); d->subpaths.insert(newSubpathIndex, subpath); notifyPointsChanged(); return true; } KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() || !isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer closes the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } // make the first point a start node subpath->first()->setProperty(KoPathPoint::StartSubpath); // make the last point an end node subpath->last()->setProperty(KoPathPoint::StopSubpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) { KoSubpath *subpath = d->subPath(pointIndex.first); if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() - || isClosedSubpath(pointIndex.first)) + || isClosedSubpath(pointIndex.first)) return KoPathPointIndex(-1, -1); KoPathPoint * oldStartPoint = subpath->first(); // the old starting node no longer starts the subpath oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); // the old end node no longer ends the subpath subpath->last()->unsetProperty(KoPathPoint::StopSubpath); // reorder the subpath for (int i = 0; i < pointIndex.second; ++i) { subpath->append(subpath->takeFirst()); } subpath->first()->setProperty(KoPathPoint::StartSubpath); subpath->last()->setProperty(KoPathPoint::StopSubpath); closeSubpathPriv(subpath); notifyPointsChanged(); return pathPointIndex(oldStartPoint); } bool KoPathShape::reverseSubpath(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath == 0) return false; int size = subpath->size(); for (int i = 0; i < size; ++i) { KoPathPoint *p = subpath->takeAt(i); p->reverse(); subpath->prepend(p); } // adjust the position dependent properties KoPathPoint *first = subpath->first(); KoPathPoint *last = subpath->last(); KoPathPoint::PointProperties firstProps = first->properties(); KoPathPoint::PointProperties lastProps = last->properties(); firstProps |= KoPathPoint::StartSubpath; firstProps &= ~KoPathPoint::StopSubpath; lastProps |= KoPathPoint::StopSubpath; lastProps &= ~KoPathPoint::StartSubpath; if (firstProps & KoPathPoint::CloseSubpath) { firstProps |= KoPathPoint::CloseSubpath; lastProps |= KoPathPoint::CloseSubpath; } first->setProperties(firstProps); last->setProperties(lastProps); notifyPointsChanged(); return true; } KoSubpath * KoPathShape::removeSubpath(int subpathIndex) { KoSubpath *subpath = d->subPath(subpathIndex); if (subpath != 0) { Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.removeAt(subpathIndex); } notifyPointsChanged(); return subpath; } bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) { if (subpathIndex < 0 || subpathIndex > d->subpaths.size()) return false; Q_FOREACH (KoPathPoint* point, *subpath) { point->setParent(this); } d->subpaths.insert(subpathIndex, subpath); notifyPointsChanged(); return true; } int KoPathShape::combine(KoPathShape *path) { int insertSegmentPosition = -1; if (!path) return insertSegmentPosition; QTransform pathMatrix = path->absoluteTransformation(0); QTransform myMatrix = absoluteTransformation(0).inverted(); Q_FOREACH (KoSubpath* subpath, path->d->subpaths) { KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, this); newPoint->map(pathMatrix); newPoint->map(myMatrix); newSubpath->append(newPoint); } d->subpaths.append(newSubpath); if (insertSegmentPosition < 0) { insertSegmentPosition = d->subpaths.size() - 1; } } normalize(); notifyPointsChanged(); return insertSegmentPosition; } bool KoPathShape::separate(QList & separatedPaths) { if (! d->subpaths.size()) return false; QTransform myMatrix = absoluteTransformation(0); Q_FOREACH (KoSubpath* subpath, d->subpaths) { KoPathShape *shape = new KoPathShape(); shape->setStroke(stroke()); shape->setBackground(background()); shape->setShapeId(shapeId()); shape->setZIndex(zIndex()); KoSubpath *newSubpath = new KoSubpath(); Q_FOREACH (KoPathPoint* point, *subpath) { KoPathPoint *newPoint = new KoPathPoint(*point, shape); newPoint->map(myMatrix); newSubpath->append(newPoint); } shape->d->subpaths.append(newSubpath); shape->normalize(); // NOTE: shape cannot have any listeners yet, so no notification about // points modification is needed separatedPaths.append(shape); } return true; } void KoPathShape::closeSubpathPriv(KoSubpath *subpath) { if (! subpath) return; subpath->last()->setProperty(KoPathPoint::CloseSubpath); subpath->first()->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } void KoPathShape::closeMergeSubpathPriv(KoSubpath *subpath) { if (! subpath || subpath->size() < 2) return; KoPathPoint * lastPoint = subpath->last(); KoPathPoint * firstPoint = subpath->first(); // check if first and last points are coincident if (lastPoint->point() == firstPoint->point()) { // we are removing the current last point and // reuse its first control point if active firstPoint->setProperty(KoPathPoint::StartSubpath); firstPoint->setProperty(KoPathPoint::CloseSubpath); if (lastPoint->activeControlPoint1()) firstPoint->setControlPoint1(lastPoint->controlPoint1()); // remove last point delete subpath->takeLast(); // the new last point closes the subpath now lastPoint = subpath->last(); lastPoint->setProperty(KoPathPoint::StopSubpath); lastPoint->setProperty(KoPathPoint::CloseSubpath); notifyPointsChanged(); } else { closeSubpathPriv(subpath); } } const KoSubpathList &KoPathShape::subpaths() const { return d->subpaths; } KoSubpathList &KoPathShape::subpaths() { return d->subpaths; } void KoPathShape::map(const QTransform &matrix) { return d->map(matrix); } KoSubpath *KoPathShape::Private::subPath(int subpathIndex) const { if (subpathIndex < 0 || subpathIndex >= subpaths.size()) return 0; return subpaths.at(subpathIndex); } QString KoPathShape::pathShapeId() const { return KoPathShapeId; } QString KoPathShape::toString(const QTransform &matrix) const { QString pathString; // iterate over all subpaths KoSubpathList::const_iterator pathIt(d->subpaths.constBegin()); for (; pathIt != d->subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); // keep a pointer to the first point of the subpath KoPathPoint *firstPoint(*pointIt); // keep a pointer to the previous point of the subpath KoPathPoint *lastPoint = firstPoint; // keep track if the previous point has an active control point 2 bool activeControlPoint2 = false; // iterate over all points of the current subpath for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { KoPathPoint *currPoint(*pointIt); if (!currPoint) { qWarning() << "Found a zero point in the shape's path!"; continue; } // first point of subpath ? if (currPoint == firstPoint) { // are we starting a subpath ? if (currPoint->properties() & KoPathPoint::StartSubpath) { const QPointF p = matrix.map(currPoint->point()); pathString += QString("M%1 %2").arg(p.x()).arg(p.y()); } } // end point of curve segment ? else if (activeControlPoint2 || currPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) : KoPathSegment(lastPoint, currPoint).toCubic(); - const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); - const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); - const QPointF p = matrix.map(cubicSeg.second()->point()); - pathString += QString("C%1 %2 %3 %4 %5 %6") - .arg(cp1.x()).arg(cp1.y()) - .arg(cp2.x()).arg(cp2.y()) - .arg(p.x()).arg(p.y()); + if (cubicSeg.first() && cubicSeg.second()) { + const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); + const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); + const QPointF p = matrix.map(cubicSeg.second()->point()); + pathString += QString("C%1 %2 %3 %4 %5 %6") + .arg(cp1.x()).arg(cp1.y()) + .arg(cp2.x()).arg(cp2.y()) + .arg(p.x()).arg(p.y()); + } } // end point of line segment! else { const QPointF p = matrix.map(currPoint->point()); pathString += QString("L%1 %2").arg(p.x()).arg(p.y()); } // last point closes subpath ? if (currPoint->properties() & KoPathPoint::StopSubpath && currPoint->properties() & KoPathPoint::CloseSubpath) { // add curve when there is a curve on the way to the first point if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { // check if we have a cubic or quadratic curve const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) : KoPathSegment(currPoint, firstPoint).toCubic(); - const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); - const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); - const QPointF p = matrix.map(cubicSeg.second()->point()); - pathString += QString("C%1 %2 %3 %4 %5 %6") - .arg(cp1.x()).arg(cp1.y()) - .arg(cp2.x()).arg(cp2.y()) - .arg(p.x()).arg(p.y()); + if (cubicSeg.first() && cubicSeg.second()) { + const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); + const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); + + const QPointF p = matrix.map(cubicSeg.second()->point()); + pathString += QString("C%1 %2 %3 %4 %5 %6") + .arg(cp1.x()).arg(cp1.y()) + .arg(cp2.x()).arg(cp2.y()) + .arg(p.x()).arg(p.y()); + } } pathString += QString("Z"); } activeControlPoint2 = currPoint->activeControlPoint2(); lastPoint = currPoint; } } return pathString; } char nodeType(const KoPathPoint * point) { if (point->properties() & KoPathPoint::IsSmooth) { return 's'; } else if (point->properties() & KoPathPoint::IsSymmetric) { return 'z'; } else { return 'c'; } } QString KoPathShape::Private::nodeTypes() const { QString types; KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it) { if (it == (*pathIt)->constBegin()) { types.append('c'); } else { types.append(nodeType(*it)); } if ((*it)->properties() & KoPathPoint::StopSubpath - && (*it)->properties() & KoPathPoint::CloseSubpath) { + && (*it)->properties() & KoPathPoint::CloseSubpath) { KoPathPoint * firstPoint = (*pathIt)->first(); types.append(nodeType(firstPoint)); } } } return types; } void updateNodeType(KoPathPoint * point, const QChar & nodeType) { if (nodeType == 's') { point->setProperty(KoPathPoint::IsSmooth); } else if (nodeType == 'z') { point->setProperty(KoPathPoint::IsSymmetric); } } void KoPathShape::Private::loadNodeTypes(const KoXmlElement &element) { if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); QString::const_iterator nIt(nodeTypes.constBegin()); KoSubpathList::const_iterator pathIt(subpaths.constBegin()); for (; pathIt != subpaths.constEnd(); ++pathIt) { KoSubpath::const_iterator it((*pathIt)->constBegin()); for (; it != (*pathIt)->constEnd(); ++it, nIt++) { // be sure not to crash if there are not enough nodes in nodeTypes if (nIt == nodeTypes.constEnd()) { warnFlake << "not enough nodes in calligra:nodeTypes"; return; } // the first node is always of type 'c' if (it != (*pathIt)->constBegin()) { updateNodeType(*it, *nIt); } if ((*it)->properties() & KoPathPoint::StopSubpath - && (*it)->properties() & KoPathPoint::CloseSubpath) { + && (*it)->properties() & KoPathPoint::CloseSubpath) { ++nIt; updateNodeType((*pathIt)->first(), *nIt); } } } } } Qt::FillRule KoPathShape::fillRule() const { return d->fillRule; } void KoPathShape::setFillRule(Qt::FillRule fillRule) { d->fillRule = fillRule; } KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) { KoPathShape * shape = new KoPathShape(); int elementCount = path.elementCount(); for (int i = 0; i < elementCount; i++) { QPainterPath::Element element = path.elementAt(i); switch (element.type) { case QPainterPath::MoveToElement: shape->moveTo(QPointF(element.x, element.y)); break; case QPainterPath::LineToElement: shape->lineTo(QPointF(element.x, element.y)); break; case QPainterPath::CurveToElement: shape->curveTo(QPointF(element.x, element.y), QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); break; default: continue; } } shape->setShapeId(KoPathShapeId); //shape->normalize(); return shape; } bool KoPathShape::hitTest(const QPointF &position) const { if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) return false; QPointF point = absoluteTransformation(0).inverted().map(position); const QPainterPath outlinePath = outline(); if (stroke()) { KoInsets insets; stroke()->strokeInsets(this, insets); QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); roi.moveCenter(point); if (outlinePath.intersects(roi) || outlinePath.contains(roi)) return true; } else { if (outlinePath.contains(point)) return true; } // if there is no shadow we can as well just leave if (! shadow()) return false; // the shadow has an offset to the shape, so we simply // check if the position minus the shadow offset hits the shape point = absoluteTransformation(0).inverted().map(position - shadow()->offset()); return outlinePath.contains(point); } void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos) { if (!marker && d->markersNew.contains(pos)) { d->markersNew.remove(pos); } else { d->markersNew[pos] = marker; } } KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const { return d->markersNew[pos].data(); } bool KoPathShape::hasMarkers() const { return !d->markersNew.isEmpty(); } bool KoPathShape::autoFillMarkers() const { return d->autoFillMarkers; } void KoPathShape::setAutoFillMarkers(bool value) { d->autoFillMarkers = value; } void KoPathShape::recommendPointSelectionChange(const QList &newSelection) { Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->recommendPointSelectionChange(this, newSelection); } } } void KoPathShape::notifyPointsChanged() { Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) { PointSelectionChangeListener *pointListener = dynamic_cast(listener); if (pointListener) { pointListener->notifyPathPointsChanged(this); } } } QPainterPath KoPathShape::pathStroke(const QPen &pen) const { if (d->subpaths.isEmpty()) { return QPainterPath(); } QPainterPath pathOutline; QPainterPathStroker stroker; stroker.setWidth(0); stroker.setJoinStyle(Qt::MiterJoin); stroker.setWidth(pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); stroker.setCapStyle(pen.capStyle()); stroker.setDashOffset(pen.dashOffset()); stroker.setDashPattern(pen.dashPattern()); QPainterPath path = stroker.createStroke(outline()); pathOutline.addPath(path); pathOutline.setFillRule(Qt::WindingFill); return pathOutline; } void KoPathShape::PointSelectionChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) { Q_UNUSED(type); Q_UNUSED(shape); } diff --git a/libs/global/kis_assert.h b/libs/global/kis_assert.h index c37a383a80..c0047ef3f6 100644 --- a/libs/global/kis_assert.h +++ b/libs/global/kis_assert.h @@ -1,143 +1,143 @@ /* * 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 __KIS_ASSERT_H #define __KIS_ASSERT_H #include #include KRITAGLOBAL_EXPORT void kis_assert_exception(const char *assertion, const char *file, int line); KRITAGLOBAL_EXPORT void kis_assert_recoverable(const char *assertion, const char *file, int line); KRITAGLOBAL_EXPORT void kis_assert_x_exception(const char *assertion, const char *where, const char *what, const char *file, int line); KRITAGLOBAL_EXPORT void kis_safe_assert_recoverable(const char *assertion, const char *file, int line); /** * KIS_ASSERT family of macros allows the user to choose whether to * try to continue working in Krita or to abort an application and see * a backtrace. * * Note, the macro are present in Release mode by default! */ /** * Checks the condition and depending on the user action either aborts * the program or throws an exception, which restarts event loop. */ #define KIS_ASSERT(cond) ((!(cond)) ? kis_assert_exception(#cond,__FILE__,__LINE__) : qt_noop()) /** * Same as KIS_ASSERT, but allows to show more text to the user. * * \see KIS_ASSERT */ #define KIS_ASSERT_X(cond, where, what) ((!(cond)) ? kis_assert_x_exception(#cond,where, what,__FILE__,__LINE__) : qt_noop()) /** * This is a recoverable variant of KIS_ASSERT. It doesn't throw any * exceptions. It checks the condition, and either aborts the * application, or executes user-supplied code. The typical usecase is * the following: * * int fooBar = ...; * KIS_ASSERT_RECOVER (fooBar > 0) { * // the code which is executed in a case of emergency * } * */ #define KIS_ASSERT_RECOVER(cond) if (!(cond) && (kis_assert_recoverable(#cond,__FILE__,__LINE__), true)) /** * Equivalent of the following: * * KIS_ASSERT_RECOVER(cond) { * break; * } * */ -#define KIS_ASSERT_RECOVER_BREAK(cond) KIS_ASSERT_RECOVER(cond) { break; } +#define KIS_ASSERT_RECOVER_BREAK(cond) do { KIS_ASSERT_RECOVER(cond) { break; } } while (0) /** * Equivalent of the following: * * KIS_ASSERT_RECOVER(cond) { * return; * } * */ -#define KIS_ASSERT_RECOVER_RETURN(cond) KIS_ASSERT_RECOVER(cond) { return; } +#define KIS_ASSERT_RECOVER_RETURN(cond) do { KIS_ASSERT_RECOVER(cond) { return; } } while (0) /** * Equivalent of the following: * * KIS_ASSERT_RECOVER(cond) { * return val; * } * */ -#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val) KIS_ASSERT_RECOVER(cond) { return (val); } +#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val) do { KIS_ASSERT_RECOVER(cond) { return (val); } } while (0) /** * Does nothing in case of a failure. Just continues execution. * * Equivalent of the following: * * KIS_ASSERT_RECOVER(cond) { * qt_noop(); * } * */ -#define KIS_ASSERT_RECOVER_NOOP(cond) KIS_ASSERT_RECOVER(cond) { qt_noop(); } +#define KIS_ASSERT_RECOVER_NOOP(cond) do { KIS_ASSERT_RECOVER(cond) { qt_noop(); } } while (0) /** * This set of macros work in exactly the same way as their non-safe * counterparts, but they are automatically converted into console * warnings in release builds. That is the user will not see any * message box, just a warning message will be printed in a terminal * and a recovery branch will be taken automatically. * * Rules when to use "safe" asserts. Use them if the following * conditions are met: * * 1) The condition in the assert shows that a real *bug* has * happened. It is not just a warning. It is a bug that should be * fixed. * * 2) The recovery branch will *workaround* the bug and the user will * be able to continue his work *as if nothing has * happened*. Again, please mark the assert "safe" if and only if * you are 100% sure Krita will not crash in a minute after you * faced that bug. The user is not notified about this bug, so he * is not going to take any emergency steps like saving his work * and restarting Krita! * * 3) If you think that Krita should be better restarted after this * bug, please use a usual KIS_ASSERT_RECOVER. */ #define KIS_SAFE_ASSERT_RECOVER(cond) if (!(cond) && (kis_safe_assert_recoverable(#cond,__FILE__,__LINE__), true)) -#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond) KIS_SAFE_ASSERT_RECOVER(cond) { break; } -#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond) KIS_SAFE_ASSERT_RECOVER(cond) { return; } -#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val) KIS_SAFE_ASSERT_RECOVER(cond) { return (val); } -#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond) KIS_SAFE_ASSERT_RECOVER(cond) { qt_noop(); } +#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond) do { KIS_SAFE_ASSERT_RECOVER(cond) { break; } } while (0) +#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond) do { KIS_SAFE_ASSERT_RECOVER(cond) { return; } } while (0) +#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val) do { KIS_SAFE_ASSERT_RECOVER(cond) { return (val); } } while (0) +#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond) do { KIS_SAFE_ASSERT_RECOVER(cond) { qt_noop(); } } while (0) #endif /* __KIS_ASSERT_H */ diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt index a28bbf86ca..a7a17550ab 100644 --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -1,380 +1,383 @@ 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() 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 + commands_new/KisChangeChannelFlagsCommand.cpp + commands_new/KisChangeChannelLockFlagsCommand.cpp processing/kis_do_nothing_processing_visitor.cpp processing/kis_simple_processing_visitor.cpp + processing/kis_convert_color_space_processing_visitor.cpp + processing/kis_assign_profile_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 KisCroppedOriginalLayerInterface.cpp + KisDecoratedNodeInterface.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 layerstyles/KisLayerStyleKnockoutBlower.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/kis_spontaneous_job.cpp b/libs/image/KisDecoratedNodeInterface.cpp similarity index 74% rename from libs/image/kis_spontaneous_job.cpp rename to libs/image/KisDecoratedNodeInterface.cpp index cb612d41a9..b67179b8d4 100644 --- a/libs/image/kis_spontaneous_job.cpp +++ b/libs/image/KisDecoratedNodeInterface.cpp @@ -1,30 +1,28 @@ /* - * Copyright (c) 2018 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. */ -#include "kis_spontaneous_job.h" +#include "KisDecoratedNodeInterface.h" - -bool KisSpontaneousJob::isExclusive() const +KisDecoratedNodeInterface::~KisDecoratedNodeInterface() { - return m_isExclusive; } -void KisSpontaneousJob::setExclusive(bool value) +void KisDecoratedNodeInterface::setDecorationsVisible(bool value) { - + setDecorationsVisible(value, true); } diff --git a/libs/image/KisDecoratedNodeInterface.h b/libs/image/KisDecoratedNodeInterface.h new file mode 100644 index 0000000000..c79da00604 --- /dev/null +++ b/libs/image/KisDecoratedNodeInterface.h @@ -0,0 +1,53 @@ +/* + * 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 KISDECORATEDNODEINTERFACE_H +#define KISDECORATEDNODEINTERFACE_H + +#include "kritaimage_export.h" + +/** + * A special interface for layer that have a "decorations", + * that is, a data that is written into layer stack, but is + * not a part of user's image. + */ +class KRITAIMAGE_EXPORT KisDecoratedNodeInterface +{ +public: + virtual ~KisDecoratedNodeInterface(); + + /** + * \return true is the layer is allowed to write + * its decorative information into the stack. The + * value should be "true" by default. + */ + virtual bool decorationsVisible() const = 0; + + /** + * Enable or disable writing decorative information into + * layer stack. + */ + virtual void setDecorationsVisible(bool value, bool update) = 0; + + /** + * Convenience override for setDecorationsVisible() + */ + void setDecorationsVisible(bool value); +}; + +#endif // KISDECORATEDNODEINTERFACE_H diff --git a/libs/image/KisRecycleProjectionsJob.cpp b/libs/image/KisRecycleProjectionsJob.cpp index c2041b0da8..0d46b0bdf9 100644 --- a/libs/image/KisRecycleProjectionsJob.cpp +++ b/libs/image/KisRecycleProjectionsJob.cpp @@ -1,48 +1,53 @@ /* * 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 "KisRecycleProjectionsJob.h" #include "KisSafeNodeProjectionStore.h" #include "kis_paint_device.h" KisRecycleProjectionsJob::KisRecycleProjectionsJob(KisSafeNodeProjectionStoreBaseWSP projectionStore) : m_projectionStore(projectionStore) { setExclusive(true); } bool KisRecycleProjectionsJob::overrides(const KisSpontaneousJob *_otherJob) { const KisRecycleProjectionsJob *otherJob = dynamic_cast(_otherJob); return otherJob && otherJob->m_projectionStore == m_projectionStore; } void KisRecycleProjectionsJob::run() { KisSafeNodeProjectionStoreBaseSP store = m_projectionStore; if (store) { store->recycleProjectionsInSafety(); } } int KisRecycleProjectionsJob::levelOfDetail() const { return 0; } + +QString KisRecycleProjectionsJob::debugName() const +{ + return "KisRecycleProjectionsJob"; +} diff --git a/libs/image/KisRecycleProjectionsJob.h b/libs/image/KisRecycleProjectionsJob.h index 85c2a03332..c0a88183cc 100644 --- a/libs/image/KisRecycleProjectionsJob.h +++ b/libs/image/KisRecycleProjectionsJob.h @@ -1,48 +1,50 @@ /* * 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 KISRECYCLEPROJECTIONSJOB_H #define KISRECYCLEPROJECTIONSJOB_H #include "kis_types.h" #include "kis_spontaneous_job.h" class QMutex; class KisSafeNodeProjectionStoreBase; typedef KisWeakSharedPtr KisSafeNodeProjectionStoreBaseWSP; /** * This is a simple job for initiating recycling of KisLayer's * projections in an exclusive context. The problem is that the * projection might still be used by update workers after it * has been disabled. So this cleaning should run in an exclusive * context. */ class KRITAIMAGE_EXPORT KisRecycleProjectionsJob : public KisSpontaneousJob { public: KisRecycleProjectionsJob(KisSafeNodeProjectionStoreBaseWSP projectionStore); bool overrides(const KisSpontaneousJob *otherJob) override; void run() override; int levelOfDetail() const override; + QString debugName() const override; + private: KisSafeNodeProjectionStoreBaseWSP m_projectionStore; }; #endif // KISRECYCLEPROJECTIONSJOB_H diff --git a/libs/image/KisRunnableBasedStrokeStrategy.cpp b/libs/image/KisRunnableBasedStrokeStrategy.cpp index 42e9f9b26d..53105f2da1 100644 --- a/libs/image/KisRunnableBasedStrokeStrategy.cpp +++ b/libs/image/KisRunnableBasedStrokeStrategy.cpp @@ -1,79 +1,79 @@ /* * 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 "KisRunnableBasedStrokeStrategy.h" #include #include #include "KisRunnableStrokeJobData.h" #include "KisRunnableStrokeJobsInterface.h" struct KisRunnableBasedStrokeStrategy::JobsInterface : public KisRunnableStrokeJobsInterface { JobsInterface(KisRunnableBasedStrokeStrategy *q) : m_q(q) { } void addRunnableJobs(const QVector &list) { QVector newList; Q_FOREACH (KisRunnableStrokeJobDataBase *item, list) { newList.append(item); } m_q->addMutatedJobs(newList); } private: KisRunnableBasedStrokeStrategy *m_q; }; -KisRunnableBasedStrokeStrategy::KisRunnableBasedStrokeStrategy(QString id, const KUndo2MagicString &name) +KisRunnableBasedStrokeStrategy::KisRunnableBasedStrokeStrategy(const QLatin1String &id, const KUndo2MagicString &name) : KisSimpleStrokeStrategy(id, name), m_jobsInterface(new JobsInterface(this)) { } KisRunnableBasedStrokeStrategy::KisRunnableBasedStrokeStrategy(const KisRunnableBasedStrokeStrategy &rhs) : KisSimpleStrokeStrategy(rhs), m_jobsInterface(new JobsInterface(this)) { } KisRunnableBasedStrokeStrategy::~KisRunnableBasedStrokeStrategy() { } void KisRunnableBasedStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { if (!data) return; KisRunnableStrokeJobDataBase *runnable = dynamic_cast(data); if (!runnable) return; runnable->run(); } KisRunnableStrokeJobsInterface *KisRunnableBasedStrokeStrategy::runnableJobsInterface() const { return m_jobsInterface.data(); } diff --git a/libs/image/KisRunnableBasedStrokeStrategy.h b/libs/image/KisRunnableBasedStrokeStrategy.h index 46cd031ae0..476ac466b1 100644 --- a/libs/image/KisRunnableBasedStrokeStrategy.h +++ b/libs/image/KisRunnableBasedStrokeStrategy.h @@ -1,44 +1,44 @@ /* * 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. */ #ifndef KISRUNNABLEBASEDSTROKESTRATEGY_H #define KISRUNNABLEBASEDSTROKESTRATEGY_H #include "kis_simple_stroke_strategy.h" class KisRunnableStrokeJobsInterface; class KRITAIMAGE_EXPORT KisRunnableBasedStrokeStrategy : public KisSimpleStrokeStrategy { private: struct JobsInterface; public: - KisRunnableBasedStrokeStrategy(QString id, const KUndo2MagicString &name = KUndo2MagicString()); + KisRunnableBasedStrokeStrategy(const QLatin1String &id, const KUndo2MagicString &name = KUndo2MagicString()); KisRunnableBasedStrokeStrategy(const KisRunnableBasedStrokeStrategy &rhs); ~KisRunnableBasedStrokeStrategy(); void doStrokeCallback(KisStrokeJobData *data) override; KisRunnableStrokeJobsInterface *runnableJobsInterface() const; private: const QScopedPointer m_jobsInterface; }; #endif // KISRUNNABLEBASEDSTROKESTRATEGY_H diff --git a/libs/image/KisSafeNodeProjectionStore.cpp b/libs/image/KisSafeNodeProjectionStore.cpp index 1c1677fb56..ee754419cf 100644 --- a/libs/image/KisSafeNodeProjectionStore.cpp +++ b/libs/image/KisSafeNodeProjectionStore.cpp @@ -1,284 +1,284 @@ /* * 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 "KisSafeNodeProjectionStore.h" #include #include #include #include "kis_image.h" #include "kis_paint_device.h" #include "kis_selection.h" #include "KisRecycleProjectionsJob.h" /**********************************************************************/ /* StoreImplementaionInterface */ /**********************************************************************/ struct StoreImplementaionInterface { virtual ~StoreImplementaionInterface() {} virtual StoreImplementaionInterface* clone() const = 0; virtual bool releaseDevice() = 0; virtual void discardCaches() = 0; virtual void recycleProjectionsInSafety() = 0; }; /**********************************************************************/ /* StoreImplementaion */ /**********************************************************************/ template struct StoreImplementation : public StoreImplementaionInterface { - bool releaseDevice() { + bool releaseDevice() override { bool hasDeletedProjection = false; if (m_projection) { m_dirtyProjections.append(m_projection); m_projection = 0; hasDeletedProjection = true; // qDebug() << "release a device"; } return hasDeletedProjection; } - virtual void discardCaches() { + virtual void discardCaches() override { // qDebug() << "discard caches"; m_dirtyProjections.clear(); } - virtual void recycleProjectionsInSafety() { + virtual void recycleProjectionsInSafety() override { // qDebug() << "recycle caches"; Q_FOREACH (DeviceSP projection, m_dirtyProjections) { projection->clear(); m_cleanProjections.append(projection); } m_dirtyProjections.clear(); } protected: DeviceSP m_projection; QVector m_dirtyProjections; QVector m_cleanProjections; }; /**********************************************************************/ /* StoreImplementaionForDevice */ /**********************************************************************/ struct StoreImplementationForDevice : StoreImplementation { StoreImplementationForDevice() {} StoreImplementationForDevice(const KisPaintDevice &prototype) { m_projection = new KisPaintDevice(prototype); } - StoreImplementaionInterface* clone() const { + StoreImplementaionInterface* clone() const override { return m_projection ? new StoreImplementationForDevice(*m_projection) : new StoreImplementationForDevice(); } KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { if (!m_cleanProjections.isEmpty()) { m_projection = m_cleanProjections.takeLast(); m_projection->makeCloneFromRough(prototype, prototype->extent()); } else { m_projection = new KisPaintDevice(*prototype); } m_projection->setProjectionDevice(true); } return m_projection; } }; /**********************************************************************/ /* StoreImplementaionForSelection */ /**********************************************************************/ struct StoreImplementationForSelection : StoreImplementation { StoreImplementationForSelection() {} StoreImplementationForSelection(const KisSelection &prototype) { m_projection = new KisSelection(prototype); } - StoreImplementaionInterface* clone() const { + StoreImplementaionInterface* clone() const override { return m_projection ? new StoreImplementationForSelection(*m_projection) : new StoreImplementationForSelection(); } KisSelectionSP getDeviceLazy(KisSelectionSP prototype) { if(!m_projection) { if (!m_cleanProjections.isEmpty()) { m_projection = m_cleanProjections.takeLast(); m_projection->pixelSelection()->makeCloneFromRough(prototype->pixelSelection(), prototype->selectedRect()); } else { m_projection = new KisSelection(*prototype); } m_projection->pixelSelection()->setProjectionDevice(true); } return m_projection; } }; /**********************************************************************/ /* KisSafeNodeProjectionStoreBase */ /**********************************************************************/ struct KisSafeNodeProjectionStoreBase::Private { mutable QMutex lock; KisImageWSP image; QScopedPointer store; }; KisSafeNodeProjectionStoreBase::KisSafeNodeProjectionStoreBase(StoreImplementaionInterface *storeImpl) : m_d(new Private) { m_d->store.reset(storeImpl); moveToThread(qApp->thread()); connect(this, SIGNAL(internalInitiateProjectionsCleanup()), this, SLOT(slotInitiateProjectionsCleanup())); } KisSafeNodeProjectionStoreBase::KisSafeNodeProjectionStoreBase(const KisSafeNodeProjectionStoreBase &rhs) : QObject(), KisShared(), m_d(new Private) { { QMutexLocker rhsLocker(&rhs.m_d->lock); m_d->image = rhs.m_d->image; m_d->store.reset(rhs.m_d->store->clone()); } moveToThread(qApp->thread()); connect(this, SIGNAL(internalInitiateProjectionsCleanup()), this, SLOT(slotInitiateProjectionsCleanup())); } KisSafeNodeProjectionStoreBase::~KisSafeNodeProjectionStoreBase() { } void KisSafeNodeProjectionStoreBase::releaseDevice() { QMutexLocker locker(&m_d->lock); if (m_d->store->releaseDevice()) { locker.unlock(); emit internalInitiateProjectionsCleanup(); } } void KisSafeNodeProjectionStoreBase::setImage(KisImageWSP image) { m_d->image = image; } void KisSafeNodeProjectionStoreBase::slotInitiateProjectionsCleanup() { /** * After the projection has been used, we should clean it. But we cannot * clean it until all the workers accessing it have completed their job. * * Therefore we just schedule an exclusive job that will execute the * recycling action in an exclusive context, when no jobs are running. */ KisImageSP image = m_d->image; if (image) { image->addSpontaneousJob(new KisRecycleProjectionsJob(this)); } else { discardCaches(); } } void KisSafeNodeProjectionStoreBase::discardCaches() { QMutexLocker locker(&m_d->lock); m_d->store->discardCaches(); } void KisSafeNodeProjectionStoreBase::recycleProjectionsInSafety() { QMutexLocker locker(&m_d->lock); m_d->store->recycleProjectionsInSafety(); } /**********************************************************************/ /* KisSafeNodeProjectionStore */ /**********************************************************************/ KisSafeNodeProjectionStore::KisSafeNodeProjectionStore() : KisSafeNodeProjectionStoreBase(new StoreImplementationForDevice) { } KisSafeNodeProjectionStore::KisSafeNodeProjectionStore(const KisSafeNodeProjectionStore &rhs) : KisSafeNodeProjectionStoreBase(rhs) { } KisPaintDeviceSP KisSafeNodeProjectionStore::getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_d->lock); StoreImplementationForDevice *store = dynamic_cast(m_d->store.data()); KIS_ASSERT(store); return store->getDeviceLazy(prototype); } /**********************************************************************/ /* KisSafeSelectionNodeProjectionStore */ /**********************************************************************/ KisSafeSelectionNodeProjectionStore::KisSafeSelectionNodeProjectionStore() : KisSafeNodeProjectionStoreBase(new StoreImplementationForSelection) { } KisSafeSelectionNodeProjectionStore::KisSafeSelectionNodeProjectionStore(const KisSafeSelectionNodeProjectionStore &rhs) : KisSafeNodeProjectionStoreBase(rhs) { } KisSelectionSP KisSafeSelectionNodeProjectionStore::getDeviceLazy(KisSelectionSP prototype) { QMutexLocker locker(&m_d->lock); StoreImplementationForSelection *store = dynamic_cast(m_d->store.data()); KIS_ASSERT(store); return store->getDeviceLazy(prototype); } diff --git a/libs/image/commands/kis_image_commands.h b/libs/image/commands/kis_image_commands.h index 3f401f4de7..7d253a84f0 100644 --- a/libs/image/commands/kis_image_commands.h +++ b/libs/image/commands/kis_image_commands.h @@ -1,31 +1,30 @@ /* * 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. */ #ifndef KIS_IMAGE_COMMANDS #define KIS_IMAGE_COMMANDS #include "kis_image_change_layers_command.h" #include "kis_image_command.h" -#include "kis_image_set_projection_color_space_command.h" #include "kis_image_layer_add_command.h" #include "kis_image_layer_move_command.h" #include "kis_image_layer_remove_command.h" #include "kis_image_lock_command.h" #include "kis_image_change_visibility_command.h" #endif diff --git a/libs/image/commands/kis_image_set_projection_color_space_command.cpp b/libs/image/commands/kis_image_set_projection_color_space_command.cpp deleted file mode 100644 index e4d2a3293f..0000000000 --- a/libs/image/commands/kis_image_set_projection_color_space_command.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2002 Patrick Julien - * Copyright (c) 2007 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_image_commands.h" - -#include "KoColorSpace.h" -#include "kis_image.h" - -#include - - -KisImageSetProjectionColorSpaceCommand::KisImageSetProjectionColorSpaceCommand(KisImageWSP image, const KoColorSpace * afterColorSpace) - : KisImageCommand(kundo2_i18n("Convert Image Type"), image) -{ - KisImageSP imageSP = image.toStrongRef(); - if (imageSP) { - m_beforeColorSpace = imageSP->colorSpace(); - m_afterColorSpace = afterColorSpace; - } -} - -void KisImageSetProjectionColorSpaceCommand::redo() -{ - KisImageSP image = m_image.toStrongRef(); - if (image) { - image->setProjectionColorSpace(m_afterColorSpace); - } -} - -void KisImageSetProjectionColorSpaceCommand::undo() -{ - KisImageSP image = m_image.toStrongRef(); - if (image) { - image->setProjectionColorSpace(m_beforeColorSpace); - } -} diff --git a/libs/image/commands/kis_image_set_projection_color_space_command.h b/libs/image/commands/kis_image_set_projection_color_space_command.h deleted file mode 100644 index bb6e096f9d..0000000000 --- a/libs/image/commands/kis_image_set_projection_color_space_command.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2002 Patrick Julien - * Copyright (c) 2007 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_IMAGE_SET_PROJECTION_COLOR_SPACE_COMMAND_H_ -#define KIS_IMAGE_SET_PROJECTION_COLOR_SPACE_COMMAND_H_ - -#include "kis_image_command.h" - -class KoColorSpace; - -class KisImageSetProjectionColorSpaceCommand : public KisImageCommand -{ -public: - KisImageSetProjectionColorSpaceCommand(KisImageWSP image, const KoColorSpace * afterColorSpace); - - void redo() override; - void undo() override; - -private: - const KoColorSpace * m_beforeColorSpace; - const KoColorSpace * m_afterColorSpace; -}; - - -#endif /* KIS_IMAGE_SET_PROJECTION_COLOR_SPACE_COMMAND_H_ */ diff --git a/libs/image/commands/kis_node_property_list_command.cpp b/libs/image/commands/kis_node_property_list_command.cpp index 94d48a5c6f..66a4ad11d2 100644 --- a/libs/image/commands/kis_node_property_list_command.cpp +++ b/libs/image/commands/kis_node_property_list_command.cpp @@ -1,227 +1,228 @@ /* * Copyright (c) 2009 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 #include "kis_node.h" #include "kis_layer.h" #include "kis_image.h" #include "kis_selection_mask.h" #include "kis_paint_layer.h" #include "commands/kis_node_property_list_command.h" #include "kis_undo_adapter.h" #include "kis_layer_properties_icons.h" // HACK! please refactor out! #include "kis_simple_stroke_strategy.h" KisNodePropertyListCommand::KisNodePropertyListCommand(KisNodeSP node, KisBaseNode::PropertyList newPropertyList) : KisNodeCommand(kundo2_i18n("Property Changes"), node), m_newPropertyList(newPropertyList), m_oldPropertyList(node->sectionModelProperties()) /** * TODO instead of "Property Changes" check which property * has been changed and display either lock/unlock, visible/hidden * or "Property Changes" (this require new strings) */ { } void KisNodePropertyListCommand::redo() { const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); const QRect oldExtent = m_node->extent(); m_node->setSectionModelProperties(m_newPropertyList); doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->extent()); } void KisNodePropertyListCommand::undo() { const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); const QRect oldExtent = m_node->extent(); m_node->setSectionModelProperties(m_oldPropertyList); doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->extent()); } bool checkOnionSkinChanged(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList) { if (oldPropertyList.size() != newPropertyList.size()) return false; bool oldOnionSkinsValue = false; bool newOnionSkinsValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { oldOnionSkinsValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) { newOnionSkinsValue = prop.state.toBool(); } } return oldOnionSkinsValue != newOnionSkinsValue; } void KisNodePropertyListCommand::doUpdate(const KisBaseNode::PropertyList &oldPropertyList, const KisBaseNode::PropertyList &newPropertyList, const QRect &totalUpdateExtent) { /** * Sometimes the node might refuse to change the property, e.g. needs-update for colorize * mask. In this case we should avoid issuing the update and set-modified call. */ if (oldPropertyList == newPropertyList) { return; } bool oldPassThroughValue = false; bool newPassThroughValue = false; bool oldVisibilityValue = false; bool newVisibilityValue = false; Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { oldPassThroughValue = prop.state.toBool(); } if (prop.id == KisLayerPropertiesIcons::visible.id()) { oldVisibilityValue = prop.state.toBool(); } } Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { newPassThroughValue = prop.state.toBool(); } if (prop.id == KisLayerPropertiesIcons::visible.id()) { newVisibilityValue = prop.state.toBool(); } } if (oldPassThroughValue && !newPassThroughValue) { KisLayerSP layer(qobject_cast(m_node.data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if ((m_node->parent() && !oldPassThroughValue && newPassThroughValue) || (oldPassThroughValue && newPassThroughValue && !oldVisibilityValue && newVisibilityValue)) { KisLayerSP layer(qobject_cast(m_node->parent().data())); KisImageSP image = layer->image().toStrongRef(); if (image) { image->refreshGraphAsync(layer); } } else if (checkOnionSkinChanged(oldPropertyList, newPropertyList)) { m_node->setDirtyDontResetAnimationCache(totalUpdateExtent); } else { m_node->setDirty(totalUpdateExtent); // TODO check if visibility was actually changed or not } } void KisNodePropertyListCommand::setNodePropertiesNoUndo(KisNodeSP node, KisImageSP image, PropertyList proplist) { QVector undo; Q_FOREACH (const KisBaseNode::Property &prop, proplist) { if (prop.isInStasis) undo << false; if (prop.name == i18n("Visible") && node->visible() != prop.state.toBool()) { undo << false; continue; } else if (prop.name == i18n("Locked") && node->userLocked() != prop.state.toBool()) { undo << false; continue; } else if (prop.name == i18n("Active")) { if (KisSelectionMask *m = dynamic_cast(node.data())) { if (m->active() != prop.state.toBool()) { undo << false; continue; } } } else if (prop.name == i18n("Alpha Locked")) { if (KisPaintLayer* l = dynamic_cast(node.data())) { if (l->alphaLocked() != prop.state.toBool()) { undo << false; continue; } } } // This property is known, but it hasn't got the same value, and it isn't one of // the previous properties, so we need to add the command to the undo list. Q_FOREACH(const KisBaseNode::Property &p2, node->sectionModelProperties()) { if (p2.name == prop.name && p2.state != prop.state) { undo << true; break; } } } QScopedPointer cmd(new KisNodePropertyListCommand(node, proplist)); image->setModified(); if (undo.contains(true)) { image->undoAdapter()->addCommand(cmd.take()); } else { /** * HACK ALERT! * * Here we start a fake legacy stroke, so that all the LoD planes would * be invalidated. Ideally, we should refactor this method and avoid * resetting LoD planes when node visibility changes, Instead there should * be two commands executes: LoD agnostic one (which sets the properties * themselves), and two LoD-specific update commands: one for lodN and * another one for lod0. */ struct SimpleLodResettingStroke : public KisSimpleStrokeStrategy { SimpleLodResettingStroke(KUndo2Command *cmd) - : m_cmd(cmd) + : KisSimpleStrokeStrategy(QLatin1String("SimpleLodResettingStroke")), + m_cmd(cmd) { setClearsRedoOnStart(false); this->enableJob(JOB_INIT, true); } void initStrokeCallback() { m_cmd->redo(); } private: QScopedPointer m_cmd; }; KisStrokeId strokeId = image->startStroke(new SimpleLodResettingStroke(cmd.take())); image->endStroke(strokeId); } } diff --git a/libs/image/commands_new/KisChangeChannelFlagsCommand.cpp b/libs/image/commands_new/KisChangeChannelFlagsCommand.cpp new file mode 100644 index 0000000000..ac835091cb --- /dev/null +++ b/libs/image/commands_new/KisChangeChannelFlagsCommand.cpp @@ -0,0 +1,46 @@ +/* + * 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 "KisChangeChannelFlagsCommand.h" +#include "kis_layer.h" + +#include + +KisChangeChannelFlagsCommand::KisChangeChannelFlagsCommand(const QBitArray &newFlags, const QBitArray &oldFlags, + KisLayerSP layer, KUndo2Command *parentCommand) + : KUndo2Command(kundo2_noi18n("change-channel-flags-command"), parentCommand), + m_layer(layer), + m_oldFlags(oldFlags), + m_newFlags(newFlags) +{ +} + +KisChangeChannelFlagsCommand::KisChangeChannelFlagsCommand(const QBitArray &newFlags, KisLayerSP layer, KUndo2Command *parentCommand) + : KisChangeChannelFlagsCommand(newFlags, layer->channelFlags(), layer, parentCommand) +{ +} + +void KisChangeChannelFlagsCommand::redo() +{ + m_layer->setChannelFlags(m_newFlags); +} + +void KisChangeChannelFlagsCommand::undo() +{ + m_layer->setChannelFlags(m_oldFlags); +} diff --git a/plugins/tools/basictools/KisMoveBoundsCalculationJob.h b/libs/image/commands_new/KisChangeChannelFlagsCommand.h similarity index 51% copy from plugins/tools/basictools/KisMoveBoundsCalculationJob.h copy to libs/image/commands_new/KisChangeChannelFlagsCommand.h index 0c1f2037c3..310a7888b4 100644 --- a/plugins/tools/basictools/KisMoveBoundsCalculationJob.h +++ b/libs/image/commands_new/KisChangeChannelFlagsCommand.h @@ -1,46 +1,53 @@ /* * 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 KISMOVEBOUNDSCALCULATIONJOB_H -#define KISMOVEBOUNDSCALCULATIONJOB_H -#include -#include "kis_spontaneous_job.h" +#ifndef KIS_CHANGE_CHANNEL_FLAGS_COMMAND_H_ +#define KIS_CHANGE_CHANNEL_FLAGS_COMMAND_H_ + +#include + +#include + #include "kis_types.h" -#include "kis_selection.h" +#include -class KisMoveBoundsCalculationJob : public QObject, public KisSpontaneousJob +class KisChangeChannelFlagsCommand : public KUndo2Command { - Q_OBJECT + public: - KisMoveBoundsCalculationJob(KisNodeList nodes, KisSelectionSP selection, QObject *requestedBy); + KisChangeChannelFlagsCommand(const QBitArray &newFlags, + KisLayerSP layer, + KUndo2Command *parentCommand = 0); + - void run() override; - bool overrides(const KisSpontaneousJob *otherJob) override; - int levelOfDetail() const override; + KisChangeChannelFlagsCommand(const QBitArray &newFlags, + const QBitArray &oldFlags, + KisLayerSP layer, + KUndo2Command *parentCommand = 0); -Q_SIGNALS: - void sigCalcualtionFinished(const QRect &bounds); + void redo() override; + void undo() override; -private: - KisNodeList m_nodes; - KisSelectionSP m_selection; - QObject *m_requestedBy; +protected: + KisLayerSP m_layer; + QBitArray m_oldFlags; + QBitArray m_newFlags; }; -#endif // KISMOVEBOUNDSCALCULATIONJOB_H +#endif diff --git a/libs/image/commands_new/KisChangeChannelLockFlagsCommand.cpp b/libs/image/commands_new/KisChangeChannelLockFlagsCommand.cpp new file mode 100644 index 0000000000..cdc12a464a --- /dev/null +++ b/libs/image/commands_new/KisChangeChannelLockFlagsCommand.cpp @@ -0,0 +1,46 @@ +/* + * 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 "KisChangeChannelLockFlagsCommand.h" +#include "kis_paint_layer.h" + +#include + +KisChangeChannelLockFlagsCommand::KisChangeChannelLockFlagsCommand(const QBitArray &newFlags, const QBitArray &oldFlags, + KisPaintLayerSP layer, KUndo2Command *parentCommand) + : KUndo2Command(kundo2_noi18n("change-channel-lock-flags-command"), parentCommand), + m_layer(layer), + m_oldFlags(oldFlags), + m_newFlags(newFlags) +{ +} + +KisChangeChannelLockFlagsCommand::KisChangeChannelLockFlagsCommand(const QBitArray &newFlags, KisPaintLayerSP layer, KUndo2Command *parentCommand) + : KisChangeChannelLockFlagsCommand(newFlags, layer->channelLockFlags(), layer, parentCommand) +{ +} + +void KisChangeChannelLockFlagsCommand::redo() +{ + m_layer->setChannelLockFlags(m_newFlags); +} + +void KisChangeChannelLockFlagsCommand::undo() +{ + m_layer->setChannelLockFlags(m_oldFlags); +} diff --git a/libs/image/commands_new/KisChangeChannelLockFlagsCommand.h b/libs/image/commands_new/KisChangeChannelLockFlagsCommand.h new file mode 100644 index 0000000000..6d7530b6de --- /dev/null +++ b/libs/image/commands_new/KisChangeChannelLockFlagsCommand.h @@ -0,0 +1,53 @@ +/* + * 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 KIS_CHANGE_CHANNEL_LOCK_FLAGS_COMMAND_H_ +#define KIS_CHANGE_CHANNEL_LOCK_FLAGS_COMMAND_H_ + +#include + +#include + +#include "kis_types.h" +#include + +class KisChangeChannelLockFlagsCommand : public KUndo2Command +{ + +public: + KisChangeChannelLockFlagsCommand(const QBitArray &newFlags, + KisPaintLayerSP layer, + KUndo2Command *parentCommand = 0); + + + KisChangeChannelLockFlagsCommand(const QBitArray &newFlags, + const QBitArray &oldFlags, + KisPaintLayerSP layer, + KUndo2Command *parentCommand = 0); + + void redo() override; + void undo() override; + +protected: + KisPaintLayerSP m_layer; + QBitArray m_oldFlags; + QBitArray m_newFlags; +}; + +#endif diff --git a/libs/image/commands_new/kis_processing_command.cpp b/libs/image/commands_new/kis_processing_command.cpp index 8ce924afd4..d2ccf9420e 100644 --- a/libs/image/commands_new/kis_processing_command.cpp +++ b/libs/image/commands_new/kis_processing_command.cpp @@ -1,46 +1,47 @@ /* * 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_processing_command.h" #include "kis_node.h" #include "kis_processing_visitor.h" KisProcessingCommand::KisProcessingCommand(KisProcessingVisitorSP visitor, KisNodeSP node, KUndo2Command *parent) : KUndo2Command(kundo2_noi18n("processing_command"), parent), m_visitor(visitor), m_node(node), m_visitorExecuted(false) { } void KisProcessingCommand::redo() { if(!m_visitorExecuted) { m_node->accept(*m_visitor, &m_undoAdapter); m_visitorExecuted = true; + m_visitor = 0; } else { m_undoAdapter.redoAll(); } } void KisProcessingCommand::undo() { m_undoAdapter.undoAll(); } diff --git a/libs/image/commands_new/kis_saved_commands.cpp b/libs/image/commands_new/kis_saved_commands.cpp index b6e04b57a5..3308ed5765 100644 --- a/libs/image/commands_new/kis_saved_commands.cpp +++ b/libs/image/commands_new/kis_saved_commands.cpp @@ -1,317 +1,319 @@ /* * 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() || id() < 0 || other->id() < 0) 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); } } if (other->extraData()) { setExtraData(other->extraData()->clone()); } else { setExtraData(0); } 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() < 0 || otherIt->command->id() < 0 || 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; } if (other->extraData()) { setExtraData(other->extraData()->clone()); } else { setExtraData(0); } 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::getCommandExecutionJobs(QVector *jobs, bool undo) const +void KisSavedMacroCommand::getCommandExecutionJobs(QVector *jobs, bool undo, bool shouldGoToHistory) const { QVector::iterator it; if(!undo) { for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) { *jobs << new KisStrokeStrategyUndoCommandBased:: Data(it->command, undo, it->sequentiality, - it->exclusivity); + it->exclusivity, + shouldGoToHistory); } } else { for(it = m_d->commands.end(); it != m_d->commands.begin();) { --it; *jobs << new KisStrokeStrategyUndoCommandBased:: Data(it->command, undo, it->sequentiality, - it->exclusivity); + it->exclusivity, + shouldGoToHistory); } } } 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 d1b805c847..4f23de43fc 100644 --- a/libs/image/commands_new/kis_saved_commands.h +++ b/libs/image/commands_new/kis_saved_commands.h @@ -1,105 +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 getCommandExecutionJobs(QVector *jobs, bool undo, bool shouldGoToHistory = true) 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_abstract_projection_plane.cpp b/libs/image/kis_abstract_projection_plane.cpp index 59319a839b..90e227bb53 100644 --- a/libs/image/kis_abstract_projection_plane.cpp +++ b/libs/image/kis_abstract_projection_plane.cpp @@ -1,70 +1,75 @@ /* * 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. */ #include "kis_abstract_projection_plane.h" KisAbstractProjectionPlane::KisAbstractProjectionPlane() { } KisAbstractProjectionPlane::~KisAbstractProjectionPlane() { } QRect KisDumbProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode) { Q_UNUSED(filthyNode); return rect; } void KisDumbProjectionPlane::apply(KisPainter *painter, const QRect &rect) { Q_UNUSED(painter); Q_UNUSED(rect); } QRect KisDumbProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisDumbProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisDumbProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } QRect KisDumbProjectionPlane::needRectForOriginal(const QRect &rect) const { return rect; } +QRect KisDumbProjectionPlane::tightUserVisibleBounds() const +{ + return QRect(); +} + KisPaintDeviceList KisDumbProjectionPlane::getLodCapableDevices() const { // arghm... return KisPaintDeviceList(); } diff --git a/libs/image/kis_abstract_projection_plane.h b/libs/image/kis_abstract_projection_plane.h index dfc8420b2b..932fe1f9be 100644 --- a/libs/image/kis_abstract_projection_plane.h +++ b/libs/image/kis_abstract_projection_plane.h @@ -1,113 +1,120 @@ /* * 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_ABSTRACT_PROJECTION_PLANE_H #define __KIS_ABSTRACT_PROJECTION_PLANE_H #include "kis_types.h" #include "kis_layer.h" class QRect; class KisPainter; /** * An interface of the node to the compositioning * system. Compositioning system KisAsyncMerger knows nothing about * the internals of the layer, it just knows that the layer can: * * 1) recalculate() its internal representation if it is filthy * * 2) apply() itself onto a global projection using, writing all its data * to the projection. * * 3) report parameters of these operations using needRect(), * changeRect() and accessRect() methods, which mean the same as * the corresponding methods of KisNode, but include more * transformations for the layer, e.g. Layer Styles and * Pass-through mode. */ class KRITAIMAGE_EXPORT KisAbstractProjectionPlane { public: KisAbstractProjectionPlane(); virtual ~KisAbstractProjectionPlane(); /** * Is called by the async merger when the node is filthy and * should recalculate its internal representation. For usual * layers it means just calling updateProjection(). */ virtual QRect recalculate(const QRect& rect, KisNodeSP filthyNode) = 0; /** * Writes the data of the projection plane onto a global * projection using \p painter object. */ virtual void apply(KisPainter *painter, const QRect &rect) = 0; /** * Works like KisNode::needRect(), but includes more * transformations of the layer */ virtual QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0; /** * Works like KisNode::changeRect(), but includes more * transformations of the layer */ virtual QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0; /** * Works like KisNode::needRect(), but includes more * transformations of the layer */ virtual QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0; /** * Works like KisLayer::needRectForOriginal(), but includes needed * rects of layer styles */ virtual QRect needRectForOriginal(const QRect &rect) const = 0; + /** + * Return a tight rectange, where the contents of the plane + * is placed from user's point of view. It includes everything + * belonging to the plane (e.g. layer styles). + */ + virtual QRect tightUserVisibleBounds() const = 0; + /** * Returns a list of devices which should synchronize the lod cache on update */ virtual KisPaintDeviceList getLodCapableDevices() const = 0; }; /** * A simple implementation of KisAbstractProjectionPlane interface * that does nothing. */ class KisDumbProjectionPlane : public KisAbstractProjectionPlane { public: QRect recalculate(const QRect& rect, KisNodeSP filthyNode) override; void apply(KisPainter *painter, const QRect &rect) override; QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const override; QRect needRectForOriginal(const QRect &rect) const override; - + QRect tightUserVisibleBounds() const override; KisPaintDeviceList getLodCapableDevices() const override; }; #endif /* __KIS_ABSTRACT_PROJECTION_PLANE_H */ diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/kis_cached_paint_device.h index f83eb34480..2cbde7e427 100644 --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/kis_cached_paint_device.h @@ -1,114 +1,122 @@ /* * 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_CACHED_PAINT_DEVICE_H #define __KIS_CACHED_PAINT_DEVICE_H #include "tiles3/kis_lockless_stack.h" #include "kis_paint_device.h" #include "kis_selection.h" class KisCachedPaintDevice { public: KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { KisPaintDeviceSP device; if(!m_stack.pop(device)) { device = new KisPaintDevice(prototype->colorSpace()); } device->prepareClone(prototype); return device; } void putDevice(KisPaintDeviceSP device) { device->clear(); device->setDefaultBounds(new KisDefaultBounds()); m_stack.push(device); } + bool isEmpty() const { + return m_stack.isEmpty(); + } + struct Guard { Guard(KisPaintDeviceSP prototype, KisCachedPaintDevice &parent) : m_parent(parent) { m_device = m_parent.getDevice(prototype); } ~Guard() { m_parent.putDevice(m_device); } KisPaintDeviceSP device() const { return m_device; } private: KisCachedPaintDevice &m_parent; KisPaintDeviceSP m_device; }; private: KisLocklessStack m_stack; }; class KisCachedSelection { public: KisSelectionSP getSelection() { KisSelectionSP selection; if(!m_stack.pop(selection)) { selection = new KisSelection(new KisSelectionEmptyBounds(0)); } return selection; } void putSelection(KisSelectionSP selection) { selection->clear(); selection->setDefaultBounds(new KisSelectionEmptyBounds(0)); selection->pixelSelection()->moveTo(QPoint()); m_stack.push(selection); } + bool isEmpty() const { + return m_stack.isEmpty(); + } + struct Guard { Guard(KisCachedSelection &parent) : m_parent(parent) { m_selection = m_parent.getSelection(); } ~Guard() { m_parent.putSelection(m_selection); } KisSelectionSP selection() const { return m_selection; } private: KisCachedSelection &m_parent; KisSelectionSP m_selection; }; private: KisLocklessStack m_stack; }; #endif /* __KIS_CACHED_PAINT_DEVICE_H */ diff --git a/libs/image/kis_change_profile_visitor.h b/libs/image/kis_change_profile_visitor.h deleted file mode 100644 index 0f0928f3e7..0000000000 --- a/libs/image/kis_change_profile_visitor.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2006 Boudewijn Rempt - -/** - * The Change Profile visitor walks over all layers and if the current - * layer has the specified colorspace AND the specified old profile, sets - * the colorspace to the same colorspace with the NEW profile, without doing - * conversions. 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. - */ -class KRITAIMAGE_EXPORT KisChangeProfileVisitor : public KisNodeVisitor -{ -public: - - using KisNodeVisitor::visit; - - KisChangeProfileVisitor(const KoColorSpace * oldColorSpace, - const KoColorSpace *dstColorSpace) - : KisNodeVisitor() - , m_oldColorSpace(oldColorSpace) - , m_dstColorSpace(dstColorSpace) { - } - - ~KisChangeProfileVisitor() override { - } - - bool visit(KisExternalLayer *) override { - return true; - } - - bool visit(KisGroupLayer * layer) override { - // Clear the projection, we will have to re-render everything. - layer->resetCache(); - - KisLayerSP child = dynamic_cast(layer->firstChild().data()); - while (child) { - child->accept(*this); - child = dynamic_cast(child->nextSibling().data()); - } - return true; - } - - - bool visit(KisPaintLayer *layer) override { - return updatePaintDevice(layer); - } - - bool visit(KisGeneratorLayer *layer) override { - return updatePaintDevice(layer); - } - - bool visit(KisAdjustmentLayer * layer) override { - layer->resetCache(); - return true; - } - - bool visit(KisNode*) override { - return true; - } - bool visit(KisCloneLayer*) override { - return true; - } - bool visit(KisFilterMask*) override { - return true; - } - bool visit(KisTransformMask*) override { - return true; - } - bool visit(KisTransparencyMask*) override { - return true; - } - bool visit(KisSelectionMask*) override { - return true; - } - - bool visit(KisColorizeMask *mask) override { - if (mask->colorSpace()->colorModelId() == m_oldColorSpace->colorModelId()) { - mask->setProfile(m_dstColorSpace->profile()); - } - return true; - } - -private: - - bool updatePaintDevice(KisLayer *layer) { - if (!layer) return false; - if (!layer->paintDevice()) return false; - if (!layer->paintDevice()->colorSpace()) return false; - - const KoColorSpace *cs = layer->paintDevice()->colorSpace(); - - if (cs->colorModelId() == m_oldColorSpace->colorModelId()) { - layer->paintDevice()->setProfile(m_dstColorSpace->profile()); - if (layer->projection() != layer->paintDevice()) { - layer->projection()->setProfile(m_dstColorSpace->profile()); - } - } - - - return true; - } - - const KoColorSpace *m_oldColorSpace; - const KoColorSpace *m_dstColorSpace; -}; - -#endif // KIS_CHANGE_PROFILE_VISITOR_H_ - diff --git a/libs/image/kis_clone_layer.cpp b/libs/image/kis_clone_layer.cpp index 6f1ec35f69..7ae7424dc9 100644 --- a/libs/image/kis_clone_layer.cpp +++ b/libs/image/kis_clone_layer.cpp @@ -1,327 +1,337 @@ /* * 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_clone_layer.h" #include #include #include #include #include #include #include "kis_default_bounds.h" #include "kis_paint_device.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_paint_layer.h" #include #include #include "kis_lod_capable_layer_offset.h" struct Q_DECL_HIDDEN KisCloneLayer::Private { Private(KisDefaultBoundsBaseSP defaultBounds) : offset(defaultBounds) { } KisPaintDeviceSP fallback; KisLodCapableLayerOffset offset; KisLayerSP copyFrom; KisNodeUuidInfo copyFromInfo; CopyLayerType type; }; KisCloneLayer::KisCloneLayer(KisLayerSP from, KisImageWSP image, const QString &name, quint8 opacity) : KisLayer(image, name, opacity) , m_d(new Private(new KisDefaultBounds(image))) { KisImageSP imageSP = image.toStrongRef(); if (!imageSP) { return; } - m_d->fallback = new KisPaintDevice(imageSP->colorSpace()); + m_d->fallback = new KisPaintDevice(this, + imageSP->colorSpace(), + new KisDefaultBounds(imageSP)); m_d->copyFrom = from; m_d->type = COPY_PROJECTION; // When loading the layer we copy from might not exist yet if (m_d->copyFrom) { m_d->copyFrom->registerClone(this); } } KisCloneLayer::KisCloneLayer(const KisCloneLayer& rhs) : KisLayer(rhs) , m_d(new Private(new KisDefaultBounds(rhs.image()))) { - m_d->fallback = new KisPaintDevice(rhs.m_d->fallback->colorSpace()); + m_d->fallback = new KisPaintDevice(this, + rhs.m_d->fallback->colorSpace(), + new KisDefaultBounds(rhs.image())); m_d->copyFrom = rhs.copyFrom(); m_d->type = rhs.copyType(); m_d->offset = rhs.m_d->offset; if (m_d->copyFrom) { m_d->copyFrom->registerClone(this); } } KisCloneLayer::~KisCloneLayer() { if (m_d->copyFrom) { m_d->copyFrom->unregisterClone(this); } delete m_d; } KisLayerSP KisCloneLayer::reincarnateAsPaintLayer() const { KisPaintDeviceSP newOriginal = new KisPaintDevice(*original()); KisPaintLayerSP newLayer = new KisPaintLayer(image(), name(), opacity(), newOriginal); newLayer->setX(newLayer->x() + x()); newLayer->setY(newLayer->y() + y()); newLayer->setCompositeOpId(compositeOpId()); newLayer->mergeNodeProperties(nodeProperties()); return newLayer; } +void KisCloneLayer::setImage(KisImageWSP image) +{ + m_d->fallback->setDefaultBounds(new KisDefaultBounds(image)); + KisLayer::setImage(image); +} + bool KisCloneLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } KisPaintDeviceSP KisCloneLayer::paintDevice() const { return 0; } KisPaintDeviceSP KisCloneLayer::original() const { if (!m_d->copyFrom || !m_d->copyFrom->projection()) return m_d->fallback; KisPaintDeviceSP retval; switch (m_d->type) { case COPY_PROJECTION: retval = m_d->copyFrom->projection(); break; case COPY_ORIGINAL: default: retval = m_d->copyFrom->original(); } return retval; } bool KisCloneLayer::needProjection() const { return m_d->offset.x() || m_d->offset.y(); } void KisCloneLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { QRect copyRect = rect; copyRect.translate(-m_d->offset.x(), -m_d->offset.y()); KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, copyRect); } void KisCloneLayer::setDirtyOriginal(const QRect &rect) { /** * The original will be updated when the clone becomes visible * again. */ if (!visible(true)) return; /** * HINT: this method is present for historical reasons only. * Long time ago the updates were calculated in * "copyOriginalToProjection" coordinate system. Now * everything is done in "original()" space. */ KisLayer::setDirty(rect); } void KisCloneLayer::notifyParentVisibilityChanged(bool value) { KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } KisLayer::setDirty(imageSP->bounds()); KisLayer::notifyParentVisibilityChanged(value); } QRect KisCloneLayer::needRectOnSourceForMasks(const QRect &rc) const { QStack applyRects_unused; bool rectVariesFlag; QList effectMasks = this->effectMasks(); if (effectMasks.isEmpty()) return QRect(); QRect needRect = this->masksNeedRect(effectMasks, rc, applyRects_unused, rectVariesFlag); if (needRect.isEmpty() || (!rectVariesFlag && needRect == rc)) { return QRect(); } return needRect; } qint32 KisCloneLayer::x() const { return m_d->offset.x(); } qint32 KisCloneLayer::y() const { return m_d->offset.y(); } void KisCloneLayer::setX(qint32 x) { m_d->offset.setX(x); } void KisCloneLayer::setY(qint32 y) { m_d->offset.setY(y); } QRect KisCloneLayer::extent() const { QRect rect = original()->extent(); // HINT: no offset now. See a comment in setDirtyOriginal() return rect | projection()->extent(); } QRect KisCloneLayer::exactBounds() const { QRect rect = original()->exactBounds(); // HINT: no offset now. See a comment in setDirtyOriginal() return rect | projection()->exactBounds(); } QRect KisCloneLayer::accessRect(const QRect &rect, PositionToFilthy pos) const { QRect resultRect = rect; if(pos & (N_FILTHY_PROJECTION | N_FILTHY)) { if (m_d->offset.x() || m_d->offset.y()) { resultRect |= rect.translated(-m_d->offset.x(), -m_d->offset.y()); } /** * KisUpdateOriginalVisitor will try to recalculate some area * on the clone's source, so this extra rectangle should also * be taken into account */ resultRect |= needRectOnSourceForMasks(rect); } return resultRect; } QRect KisCloneLayer::outgoingChangeRect(const QRect &rect) const { return rect.translated(m_d->offset.x(), m_d->offset.y()); } bool KisCloneLayer::accept(KisNodeVisitor & v) { return v.visit(this); } void KisCloneLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } void KisCloneLayer::setCopyFrom(KisLayerSP fromLayer) { if (m_d->copyFrom) { m_d->copyFrom->unregisterClone(this); } m_d->copyFrom = fromLayer; if (m_d->copyFrom) { m_d->copyFrom->registerClone(this); } } KisLayerSP KisCloneLayer::copyFrom() const { return m_d->copyFrom; } void KisCloneLayer::setCopyType(CopyLayerType type) { m_d->type = type; } CopyLayerType KisCloneLayer::copyType() const { return m_d->type; } KisNodeUuidInfo KisCloneLayer::copyFromInfo() const { return m_d->copyFrom ? KisNodeUuidInfo(m_d->copyFrom) : m_d->copyFromInfo; } void KisCloneLayer::setCopyFromInfo(KisNodeUuidInfo info) { Q_ASSERT(!m_d->copyFrom); m_d->copyFromInfo = info; } QIcon KisCloneLayer::icon() const { return KisIconUtils::loadIcon("cloneLayer"); } KisBaseNode::PropertyList KisCloneLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisLayer::sectionModelProperties(); if (m_d->copyFrom) l << KisBaseNode::Property(KoID("copy_from", i18n("Copy From")), m_d->copyFrom->name()); return l; } void KisCloneLayer::syncLodCache() { KisLayer::syncLodCache(); m_d->offset.syncLodOffset(); } diff --git a/libs/image/kis_clone_layer.h b/libs/image/kis_clone_layer.h index 785099684c..60e8ebf3bd 100644 --- a/libs/image/kis_clone_layer.h +++ b/libs/image/kis_clone_layer.h @@ -1,135 +1,137 @@ /* * 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_CLONE_LAYER_H_ #define KIS_CLONE_LAYER_H_ #include #include "kis_types.h" #include "kis_layer.h" #include #include "kis_node_uuid_info.h" class KisNodeVisitor; enum CopyLayerType { COPY_PROJECTION, COPY_ORIGINAL }; /** * A copy layer adds the contents of another layer in another place in * the layer stack. It is possible to add more effect masks to the * copy. You can either copy the original data or the projection data * produced by the original layer + original effect masks. There is no * physical copy of the data; if the original changes, the copy * changes too. The copy layer can be positioned differently from the * original layer. **/ class KRITAIMAGE_EXPORT KisCloneLayer : public KisLayer { Q_OBJECT public: KisCloneLayer(KisLayerSP from, KisImageWSP image, const QString &name, quint8 opacity); KisCloneLayer(const KisCloneLayer& rhs); ~KisCloneLayer() override; KisNodeSP clone() const override { return KisNodeSP(new KisCloneLayer(*this)); } /** * When the source layer of the clone is removed from the stack * we should substitute the clone with a usual paint layer, * because the source might become unreachable quite soon. This * method builds a paint layer representation of this clone. */ KisLayerSP reincarnateAsPaintLayer() const; + void setImage(KisImageWSP image) override; + bool allowAsChild(KisNodeSP) const override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; bool needProjection() const override; QIcon icon() const override; KisBaseNode::PropertyList sectionModelProperties() const override; qint32 x() const override; qint32 y() const override; void setX(qint32) override; void setY(qint32) override; /// Returns an approximation of where the bounds on actual data are in this layer QRect extent() const override; /// Returns the exact bounds of where the actual data resides in this layer QRect exactBounds() const override; bool accept(KisNodeVisitor &) override; void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override; /** * Used when loading: loading is done in two passes, and the copy * from layer is set when all layers have been created, not during * loading. */ void setCopyFromInfo(KisNodeUuidInfo info); KisNodeUuidInfo copyFromInfo() const; void setCopyFrom(KisLayerSP layer); KisLayerSP copyFrom() const; void setCopyType(CopyLayerType type); CopyLayerType copyType() const; /** * This function is called by the original to notify * us that it is dirty */ void setDirtyOriginal(const QRect &rect); QRect needRectOnSourceForMasks(const QRect &rc) const; void syncLodCache() override; protected: // override from KisNode QRect accessRect(const QRect &rect, PositionToFilthy pos) const override; // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const override; void notifyParentVisibilityChanged(bool value) override; QRect outgoingChangeRect(const QRect &rect) const override; private: struct Private; Private * const m_d; }; #endif // KIS_CLONE_LAYER_H_ diff --git a/libs/image/kis_colorspace_convert_visitor.cpp b/libs/image/kis_colorspace_convert_visitor.cpp deleted file mode 100644 index ce003e8967..0000000000 --- a/libs/image/kis_colorspace_convert_visitor.cpp +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2005 C. Boemann - * - * 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_colorspace_convert_visitor.h" -#include "kis_image.h" -#include "kis_paint_device.h" -#include "kis_undo_adapter.h" -#include "kis_adjustment_layer.h" -#include "kis_paint_layer.h" -#include "kis_group_layer.h" -#include "lazybrush/kis_colorize_mask.h" -#include "kis_external_layer_iface.h" -#include "filter/kis_filter_configuration.h" -#include "filter/kis_filter_registry.h" -#include "filter/kis_filter.h" -#include "kis_generator.h" -#include "kis_generator_registry.h" -#include "generator/kis_generator_layer.h" -#include "kis_time_range.h" -#include - -KisColorSpaceConvertVisitor::KisColorSpaceConvertVisitor(KisImageWSP image, - const KoColorSpace *srcColorSpace, - const KoColorSpace *dstColorSpace, - KoColorConversionTransformation::Intent renderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags) - : KisNodeVisitor() - , m_image(image) - , m_srcColorSpace(srcColorSpace) - , m_dstColorSpace(dstColorSpace) - , m_renderingIntent(renderingIntent) - , m_conversionFlags(conversionFlags) -{ -} - -KisColorSpaceConvertVisitor::~KisColorSpaceConvertVisitor() -{ -} - -bool KisColorSpaceConvertVisitor::visit(KisGroupLayer * layer) -{ - convertPaintDevice(layer); - KisLayerSP child = qobject_cast(layer->firstChild().data()); - while (child) { - child->accept(*this); - child = qobject_cast(child->nextSibling().data()); - } - - layer->resetCache(); - - return true; -} - -bool KisColorSpaceConvertVisitor::visit(KisPaintLayer *layer) -{ - return convertPaintDevice(layer); -} - -bool KisColorSpaceConvertVisitor::visit(KisGeneratorLayer *layer) -{ - layer->resetCache(); - return true; -} - -bool KisColorSpaceConvertVisitor::visit(KisAdjustmentLayer * layer) -{ - // XXX: Make undoable! - if (layer->filter()->name() == "perchannel") { - // Per-channel filters need to be reset because of different number - // of channels. This makes undo very tricky, but so be it. - // XXX: Make this more generic for after 1.6, when we'll have many - // channel-specific filters. - KisFilterSP f = KisFilterRegistry::instance()->value("perchannel"); - layer->setFilter(f->defaultConfiguration()); - } - - layer->resetCache(); - return true; -} - -bool KisColorSpaceConvertVisitor::convertPaintDevice(KisLayer* layer) -{ - - if (*m_dstColorSpace == *layer->colorSpace()) return true; - - bool alphaLock = false; - - if (m_srcColorSpace->colorModelId() != m_dstColorSpace->colorModelId()) { - layer->setChannelFlags(m_emptyChannelFlags); - KisPaintLayer *paintLayer = 0; - if ((paintLayer = dynamic_cast(layer))) { - alphaLock = paintLayer->alphaLocked(); - paintLayer->setChannelLockFlags(QBitArray()); - } - } - - KisImageSP image = m_image.toStrongRef(); - if (!image) { - return false; - } - - KUndo2Command *parentConversionCommand = new KUndo2Command(); - - if (layer->original()) { - layer->original()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags, parentConversionCommand); - } - - if (layer->paintDevice()) { - layer->paintDevice()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags, parentConversionCommand); - } - - if (layer->projection()) { - layer->projection()->convertTo(m_dstColorSpace, m_renderingIntent, m_conversionFlags, parentConversionCommand); - } - - image->undoAdapter()->addCommand(parentConversionCommand); - - KisPaintLayer *paintLayer = 0; - if ((paintLayer = dynamic_cast(layer))) { - paintLayer->setAlphaLocked(alphaLock); - } - layer->setDirty(); - layer->invalidateFrames(KisTimeRange::infinite(0), layer->extent()); - - return true; - -} - -bool KisColorSpaceConvertVisitor::visit(KisColorizeMask *mask) -{ - KisImageSP image = m_image.toStrongRef(); - if (!image) { - return false; - } - KUndo2Command* cmd = mask->setColorSpace(m_dstColorSpace, m_renderingIntent, m_conversionFlags); - if (cmd) { - image->undoAdapter()->addCommand(cmd); - } - return true; -} diff --git a/libs/image/kis_colorspace_convert_visitor.h b/libs/image/kis_colorspace_convert_visitor.h deleted file mode 100644 index 348e5331ba..0000000000 --- a/libs/image/kis_colorspace_convert_visitor.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2005 C. Boemann - * - * 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_COLORSPACE_CONVERT_VISITOR_H_ -#define KIS_COLORSPACE_CONVERT_VISITOR_H_ - - -#include -#include - -#include -#include "kis_types.h" -#include "kis_node_visitor.h" - - -/** - * This will convert all layers to the destination color space. - */ -class KRITAIMAGE_EXPORT KisColorSpaceConvertVisitor : public KisNodeVisitor -{ -public: - KisColorSpaceConvertVisitor(KisImageWSP image, - const KoColorSpace *srcColorSpace, - const KoColorSpace *dstColorSpace, - KoColorConversionTransformation::Intent renderingIntent, - KoColorConversionTransformation::ConversionFlags conversionFlags); - ~KisColorSpaceConvertVisitor() override; - -public: - - bool visit(KisPaintLayer *layer) override; - bool visit(KisGroupLayer *layer) override; - bool visit(KisAdjustmentLayer* layer) override; - bool visit(KisGeneratorLayer * layer) override; - - bool visit(KisExternalLayer *) override { - return true; - } - - bool visit(KisNode*) override { - return true; - } - - bool visit(KisCloneLayer*) override { - return true; - } - - bool visit(KisFilterMask*) override { - return true; - } - - bool visit(KisTransformMask*) override { - return true; - } - - bool visit(KisTransparencyMask*) override { - return true; - } - - bool visit(KisSelectionMask*) override { - return true; - } - - bool visit(KisColorizeMask *mask) override; - -private: - - bool convertPaintDevice(KisLayer* layer); - - KisImageWSP m_image; - const KoColorSpace *m_srcColorSpace; - const KoColorSpace *m_dstColorSpace; - KoColorConversionTransformation::Intent m_renderingIntent; - KoColorConversionTransformation::ConversionFlags m_conversionFlags; - QBitArray m_emptyChannelFlags; -}; - - -#endif // KIS_COLORSPACE_CONVERT_VISITOR_H_ - diff --git a/libs/image/kis_default_bounds.cpp b/libs/image/kis_default_bounds.cpp index bf2a3880aa..a84cdb9ee3 100644 --- a/libs/image/kis_default_bounds.cpp +++ b/libs/image/kis_default_bounds.cpp @@ -1,149 +1,159 @@ /* * Copyright (c) 2010 Boudewijn Rempt * 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_global.h" #include "kis_default_bounds.h" #include "kis_paint_device.h" #include "kis_image_animation_interface.h" #include "kis_image.h" const QRect KisDefaultBounds::infiniteRect = QRect(qint32_MIN/2, qint32_MIN/2, qint32_MAX, qint32_MAX); /******************************************************************/ /* KisDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisDefaultBounds::Private { KisImageWSP image; }; KisDefaultBounds::KisDefaultBounds(KisImageWSP image) : m_d(new Private()) { m_d->image = image; } KisDefaultBounds::~KisDefaultBounds() { delete m_d; } QRect KisDefaultBounds::bounds() const { /** * By default return infinite rect to cover everything */ return m_d->image ? m_d->image->effectiveLodBounds() : infiniteRect; } bool KisDefaultBounds::wrapAroundMode() const { return m_d->image ? m_d->image->wrapAroundModeActive() : false; } int KisDefaultBounds::currentLevelOfDetail() const { return m_d->image ? m_d->image->currentLevelOfDetail() : 0; } int KisDefaultBounds::currentTime() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->currentTime() : 0; } bool KisDefaultBounds::externalFrameActive() const { KisImageAnimationInterface *interface = m_d->image ? m_d->image->animationInterface() : 0; return interface ? interface->externalFrameActive() : false; } +void *KisDefaultBounds::sourceCookie() const +{ + return m_d->image.data(); +} + /******************************************************************/ /* KisSelectionDefaultBounds */ /******************************************************************/ struct Q_DECL_HIDDEN KisSelectionDefaultBounds::Private { KisPaintDeviceWSP parentDevice; }; KisSelectionDefaultBounds::KisSelectionDefaultBounds(KisPaintDeviceSP parentDevice) : m_d(new Private()) { m_d->parentDevice = parentDevice; } KisSelectionDefaultBounds::~KisSelectionDefaultBounds() { delete m_d; } QRect KisSelectionDefaultBounds::bounds() const { return m_d->parentDevice ? m_d->parentDevice->extent() | m_d->parentDevice->defaultBounds()->bounds() : QRect(); } bool KisSelectionDefaultBounds::wrapAroundMode() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->wrapAroundMode() : false; } int KisSelectionDefaultBounds::currentLevelOfDetail() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->currentLevelOfDetail() : 0; } int KisSelectionDefaultBounds::currentTime() const { return m_d->parentDevice ? m_d->parentDevice->defaultBounds()->currentTime() : 0; } bool KisSelectionDefaultBounds::externalFrameActive() const { return m_d->parentDevice ? - m_d->parentDevice->defaultBounds()->externalFrameActive() : false; + m_d->parentDevice->defaultBounds()->externalFrameActive() : false; +} + +void *KisSelectionDefaultBounds::sourceCookie() const +{ + return m_d->parentDevice.data(); } /******************************************************************/ /* KisSelectionEmptyBounds */ /******************************************************************/ KisSelectionEmptyBounds::KisSelectionEmptyBounds(KisImageWSP image) : KisDefaultBounds(image) { } KisSelectionEmptyBounds::~KisSelectionEmptyBounds() { } QRect KisSelectionEmptyBounds::bounds() const { return QRect(0, 0, 0, 0); } diff --git a/libs/image/kis_default_bounds.h b/libs/image/kis_default_bounds.h index c3493225ff..c234633a2d 100644 --- a/libs/image/kis_default_bounds.h +++ b/libs/image/kis_default_bounds.h @@ -1,83 +1,85 @@ /* * Copyright (c) 2010 Boudewijn Rempt * 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. */ #ifndef KIS_DEFAULT_BOUNDS_H #define KIS_DEFAULT_BOUNDS_H #include #include "kis_types.h" #include "kis_default_bounds_base.h" class KisDefaultBounds; class KisSelectionDefaultBounds; class KisSelectionEmptyBounds; typedef KisSharedPtr KisDefaultBoundsSP; typedef KisSharedPtr KisSelectionDefaultBoundsSP; typedef KisSharedPtr KisSelectionEmptyBoundsSP; class KRITAIMAGE_EXPORT KisDefaultBounds : public KisDefaultBoundsBase { public: KisDefaultBounds(KisImageWSP image = 0); ~KisDefaultBounds() override; QRect bounds() const override; bool wrapAroundMode() const override; int currentLevelOfDetail() const override; int currentTime() const override; bool externalFrameActive() const override; + void * sourceCookie() const override; protected: friend class KisPaintDeviceTest; static const QRect infiniteRect; private: Q_DISABLE_COPY(KisDefaultBounds) struct Private; Private * const m_d; }; class KRITAIMAGE_EXPORT KisSelectionDefaultBounds : public KisDefaultBoundsBase { public: KisSelectionDefaultBounds(KisPaintDeviceSP parentPaintDevice); ~KisSelectionDefaultBounds() override; QRect bounds() const override; bool wrapAroundMode() const override; int currentLevelOfDetail() const override; int currentTime() const override; bool externalFrameActive() const override; + void * sourceCookie() const override; private: Q_DISABLE_COPY(KisSelectionDefaultBounds) struct Private; Private * const m_d; }; class KRITAIMAGE_EXPORT KisSelectionEmptyBounds : public KisDefaultBounds { public: KisSelectionEmptyBounds(KisImageWSP image); ~KisSelectionEmptyBounds() override; QRect bounds() const override; }; #endif // KIS_DEFAULT_BOUNDS_H diff --git a/libs/image/kis_default_bounds_base.h b/libs/image/kis_default_bounds_base.h index 464b17ea07..b4c1cdfd5f 100644 --- a/libs/image/kis_default_bounds_base.h +++ b/libs/image/kis_default_bounds_base.h @@ -1,43 +1,56 @@ /* * Copyright (c) 2010 Boudewijn Rempt * 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. */ #ifndef KIS_DEFAULT_BOUNDS_BASE_H #define KIS_DEFAULT_BOUNDS_BASE_H #include #include "kis_shared.h" #include "kis_shared_ptr.h" #include "kritaimage_export.h" class KisDefaultBoundsBase; typedef KisSharedPtr KisDefaultBoundsBaseSP; class KRITAIMAGE_EXPORT KisDefaultBoundsBase : public KisShared { public: virtual ~KisDefaultBoundsBase(); virtual QRect bounds() const = 0; virtual bool wrapAroundMode() const = 0; virtual int currentLevelOfDetail() const = 0; virtual int currentTime() const = 0; virtual bool externalFrameActive() const = 0; + + /** + * Return an abstract pointer to the source object, + * where default bounds takes its data from. It the + * cookie is nullptr, then the default bounds is not + * connected to anything. One can also compare if two + * default bounds are connected to the same source by + * comparing two pointers. + * + * NOTE: It is intended to be used for debugging + * purposes only! + */ + virtual void* sourceCookie() const = 0; }; #endif // KIS_DEFAULT_BOUNDS_BASE_H diff --git a/libs/image/kis_do_something_command.h b/libs/image/kis_do_something_command.h index b66f9c01ab..a196cb7ed1 100644 --- a/libs/image/kis_do_something_command.h +++ b/libs/image/kis_do_something_command.h @@ -1,70 +1,71 @@ /* * 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_DO_SOMETHING_COMMAND_H #define __KIS_DO_SOMETHING_COMMAND_H #include template